Merge "Merge remote-tracking branch 'origin/qds/dev'"

This commit is contained in:
The Qt Project
2023-12-15 09:53:53 +00:00
151 changed files with 3628 additions and 2036 deletions

View File

@@ -22,6 +22,9 @@ function(setup_dependencies_component)
set(_elfutils_arg "--elfutils \"${_elfutils_path}\"") set(_elfutils_arg "--elfutils \"${_elfutils_path}\"")
endif() endif()
install(CODE " install(CODE "
if (CMAKE_VERSION GREATER_EQUAL 3.19)
set(QTC_COMMAND_ERROR_IS_FATAL COMMAND_ERROR_IS_FATAL ANY)
endif()
# DESTDIR is set for e.g. the cpack DEB generator, but is empty in other situations # DESTDIR is set for e.g. the cpack DEB generator, but is empty in other situations
if(DEFINED ENV{DESTDIR}) if(DEFINED ENV{DESTDIR})
set(DESTDIR_WITH_SEP \"\$ENV{DESTDIR}/\") set(DESTDIR_WITH_SEP \"\$ENV{DESTDIR}/\")
@@ -48,7 +51,7 @@ function(setup_dependencies_component)
\"\${_ide_app_target}\" \"\${_ide_app_target}\"
\"${_qmake_binary}\" \"${_qmake_binary}\"
COMMAND_ECHO STDOUT COMMAND_ECHO STDOUT
\${QTC_COMMAND_ERROR_IS_FATAL} ${QTC_COMMAND_ERROR_IS_FATAL}
) )
" "
COMPONENT Dependencies COMPONENT Dependencies

View File

@@ -23,16 +23,23 @@
necessary files to a location in a device where you want to run the necessary files to a location in a device where you want to run the
executable at. executable at.
\note To preview on a wirelessly connected device, select \preferences > \uicontrol Devices
and connect the device.
To preview a UI on a device: To preview a UI on a device:
\list 1 \list 1
\if defined(qtcreator)
\li In \uicontrol Projects > \uicontrol {Build & Run}, enable the kit \li In \uicontrol Projects > \uicontrol {Build & Run}, enable the kit
predefined for the device type (1). predefined for the device type (1).
\li Select the kit for the device in the kit selector (2). \li Select the kit for the device in the kit selector (2).
\if defined(qtcreator)
\image qtcreator-live-preview-kit.png \image qtcreator-live-preview-kit.png
\else \else
\image studio-kit-selector.png "Kit selector" \li In the bottom toolbar, select \inlineimage icons/settings.png
and enable the kit predefined for the device type.
\image design-studio-kit-selection.webp.
\li Select the kit for the device in the bottom toolbar.
\image design-studio-select-kit.webp
\endif \endif
\li Select \uicontrol Build > \uicontrol {QML Preview} or \li Select \uicontrol Build > \uicontrol {QML Preview} or
press \key {Alt+P}. press \key {Alt+P}.

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 348 B

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -9,12 +9,12 @@
\title Creating UIs for MCUs \title Creating UIs for MCUs
As a technical artist or a designer, you can use specialized UI design tools, As a technical artist or a designer, you can use specialized UI design tools,
such as Adobe Photoshop, Sketch, Figma, Blender, or Maya, to create the such as Adobe Photoshop, Sketch, or Figma, to create the original UI design
original UI design files for your MCU application. After the initial design files for your MCU application. After the initial design work, export your
work, export your design from the design tools, and import your 2D and 3D UI design from the design tools, and import your 2D UI design assets into \QDS,
design assets into \QDS, which can convert them into code for developers. which can convert them into code for developers. For more information on
For more information on managing the original assets created with managing the original assets created with specialized UI design tools, see
specialized UI design tools, see \l {Asset Creation with Other Tools}. \l {Asset Creation with Other Tools}.
Once your UI design assets are in \QDS, use it to \l {Wireframing} {wireframe} Once your UI design assets are in \QDS, use it to \l {Wireframing} {wireframe}
your MCU application, to visualize its structure. To modify the look and feel your MCU application, to visualize its structure. To modify the look and feel

View File

@@ -104,6 +104,16 @@
selected, the camera is pointed at the world origin. This does not affect selected, the camera is pointed at the world origin. This does not affect
the camera zoom level. the camera zoom level.
To view the scene in a split view of four different point of views, select
\inlineimage icons/split-view.png.
\image studio-3d-split-view.webp "Split view in the 3D view"
To select one of the four panes, click on it. The selected pane is marked with
a blue frame. Use the world axis helper to change the point of view for each pane
independently. Navigate each split by panning, rotating, and zooming, as
described above.
\image studio-3d-editor-axis-helper.webp "Axis helper in the 3D view" \image studio-3d-editor-axis-helper.webp "Axis helper in the 3D view"
You can use scene cameras (2) to view the \uicontrol View3D component from a You can use scene cameras (2) to view the \uicontrol View3D component from a
@@ -143,6 +153,13 @@
For more information about the available scene light types and their For more information about the available scene light types and their
properties, see \l{Lights}. properties, see \l{Lights}.
\section1 Baking Lights
Bake lights to light static elements in your scene. To bake lights,
select \inlineimage icons/bakelights.png to open the
\uicontrol {Lights Baking Setup} dialog. For more information, see
\l {Baking Lightmaps}.
\section1 Selecting Components \section1 Selecting Components
To move, rotate, or scale components in the scene, you need to select them To move, rotate, or scale components in the scene, you need to select them
@@ -358,6 +375,54 @@
\inlineimage icons/particles-seek.png \inlineimage icons/particles-seek.png
to manually seek forward or backward in the particle animation. to manually seek forward or backward in the particle animation.
\section1 Using Viewport Shading
Use \uicontrol {Viewport Shading} to change the rendering of the materials to only
reflect a particular aspect of the overall rendering process. Use shading also as a
debugging tool to understand why a material looks the way it does. In split view,
view the scene using different shading properties in each split.
To use the \uicontrol {Viewport Shading}, right-click the \uicontrol 3D view to open the
context menu, select \uicontrol {Viewport Shading} and then select \uicontrol Wireframe,
one of the material properties, or \uicontrol{Reset All Viewports}.
Select \uicontrol Wireframe to only show the edges of the objects in the scene.
Select one of the material properties available for shading:
\table
\header
\li Property
\li Description
\row
\li Base Color
\li Shows only the base color of a material passed through without any lighting.
\row
\li Roughness
\li Shows only the roughness of a material passed through as an unlit greyscale
value.
\row
\li Metalness
\li Shows only the metalness of a material passed through as an unlit greyscale
value.
\row
\li Normals
\li Shows only the interpolated world space normal value of the material mapped
to an RGB color.
\row
\li Ambient Occlusion
\li Shows only the ambient occlusion of the material.
\row
\li Diffuse
\li Shows only the diffuse contribution of the material after all lighting.
\row
\li Specular
\li Shows only the specular contribution of the material after all
lighting.
\endtable
Select \uicontrol{Reset All Viewports} to reset the shading of the scene in all of the
splits.
\section1 Summary of the 3D View Toolbar Buttons \section1 Summary of the 3D View Toolbar Buttons
The \uicontrol{3D} view toolbar contains the following buttons: The \uicontrol{3D} view toolbar contains the following buttons:
@@ -441,6 +506,11 @@
\li Background Color Actions \li Background Color Actions
\li \li
\li \l{Changing Colors} \li \l{Changing Colors}
\row
\li \inlineimage icons/split-view.png
\li Toggle Split View On/Off
\li \key Ctrl + \key Alt + \key Q
\li \l{Using Split View}
\row \row
\li \inlineimage icons/particles-seek.png \li \inlineimage icons/particles-seek.png
\li Seek Particle System Time \li Seek Particle System Time

View File

@@ -213,10 +213,11 @@
\note Lightmaps baking is released as technical preview in \QDS 4.1. \note Lightmaps baking is released as technical preview in \QDS 4.1.
Baked lightmaps allow pre-generating the direct lighting from lights such as DirectionalLight, Baked lightmaps allow pre-generating the direct lighting from lights,
PointLight, and SpotLight, including the shadows cast by the lights. At run time, instead of such as DirectionalLight, PointLight, and SpotLight, including the shadows
performing the appropriate calculations in the fragment shader, and, in case of shadows, cast by the lights. At run time, instead of performing the appropriate
generating the potentially costly shadow maps in real time, the pre-generated light map is calculations in the fragment shader, and, in case of shadows, generating
the potentially costly shadow maps in real time, the pre-generated light map is
sampled instead. sampled instead.
\section2 Baking Lightmaps \section2 Baking Lightmaps
@@ -224,40 +225,49 @@
To bake lightmaps for models in your 3D scene: To bake lightmaps for models in your 3D scene:
\list 1 \list 1
\li Right-click anywhere in the \uicontrol 3D view and select \uicontrol {Bake Lights}. \li Select \inlineimage icons/bakelights.png in the \uicontrol 3D view
\li In the \uicontrol {Lights Baking Setup} dialog: toolbar or right-click anywhere in the \uicontrol 3D view and select
\uicontrol {Bake Lights} to open the \uicontrol {Lights Baking Setup}
dialog to define settings for baking lights.
\li Set \uicontrol {Bake Mode} for each light source:
\list \list
\li For every light you want to use to bake lightmaps, set \uicontrol {Bake Mode} to BakeModeAll. \li To bake both direct (diffuse and shadow) and indirect lighting,
\li For every 3D model you want to bake lightmaps, select \uicontrol {In Use} and select \uicontrol {Bake All}.
\uicontrol {Enabled}, and set the desired \uicontrol {Resolution}. \li To bake only indirect lighting, select \uicontrol {Bake Indirect}.
\li To not include a light source in the baking of lights, select
\uicontrol {Baking Disabled}.
\endlist \endlist
\li Optional. If you have components with unexposed models or lights (for example, imported \li For every 3D model you want to bake into a lightmap, select
3D models created in other software), select \uicontrol {Expose models and lights} to add the \uicontrol {In Use} and \uicontrol {Enabled}, and set the desired
models and light of that component to the \uicontrol Models and \uicontrol Lights sections of \uicontrol {Resolution}.
the dialog. \li Optional. If you have components with unexposed models or lights
(for example, imported 3D models created in other software), select
\uicontrol {Expose models and lights} to add the models and light of
that component to the \uicontrol Models and \uicontrol Lights sections
of the dialog.
\li Select \uicontrol Bake. \li Select \uicontrol Bake.
\endlist \endlist
\image bake-lights-dialog.png \image bake-lights-dialog.png
\section2 Manually Baking Lightmaps for a 3D Model \section2 Manually Baking Lightmaps for a 3D Model
Baked lightmap components are not visible in the \uicontrol Navigator view by default. To make \note Baked lightmap components are not visible in the \uicontrol Navigator view by
them visible, select \inlineimage icons/visibilityon.png default. To make them visible, select \inlineimage icons/visibilityon.png
in the \uicontrol Navigator view. in the \uicontrol Navigator view.
To bake lightmaps for a 3D model: To bake lightmaps for a 3D model:
\list 1 \list 1
\li From \uicontrol Components, drag a \uicontrol {Baked Lightmap} component to \li From \uicontrol Components, drag a \uicontrol {Baked Lightmap} component
the 3D model in the \uicontrol Navigator view. to the 3D model in the \uicontrol Navigator view.
\image baked-lightmaps-navigator.png \image baked-lightmaps-navigator.png
\li In the \uicontrol Navigator view, select \e bakedLightmap and in the \uicontrol Properties \li In the \uicontrol Navigator view, select \e bakedLightmap, and in the
view: \uicontrol Properties view:
\list \list
\li Select \uicontrol Enabled. \li Select \uicontrol Enabled.
\li In \uicontrol Key, set the filename base for the generated light maps. This must be \li In \uicontrol Key, set the filename base for the generated light maps.
a unique name. This must be a unique name.
\li In \uicontrol {Load Prefix}, set the relative path to the folder where the generated \li In \uicontrol {Load Prefix}, set the relative path to the folder where
light map files are saved. the generated light map files are saved.
\endlist \endlist
\li In the \uicontrol Navigator view, select the 3D model and in the \uicontrol Properties \li In the \uicontrol Navigator view, select the 3D model and in the \uicontrol Properties
view, select \uicontrol {Used in Baked Lighting}. view, select \uicontrol {Used in Baked Lighting}.

View File

@@ -147,20 +147,20 @@ def copy_qt_libs(target_qt_prefix_path, qt_bin_dir, qt_libs_dir):
shutil.copy(library, lib_dest) shutil.copy(library, lib_dest)
def deploy_qtdiag(qtc_binary_path, qt_install): def deploy_binary(binary_name, qtc_binary_path, qt_install):
print("Copying qtdiag") print(f"Copying {binary_name}")
qtdiag_src = os.path.join(qt_install.bin, with_exe_ext('qtdiag')) binary_src = os.path.join(qt_install.bin, with_exe_ext(binary_name))
destdir = (qtc_binary_path if common.is_windows_platform() destdir = (qtc_binary_path if common.is_windows_platform()
else os.path.join(qtc_binary_path, 'Contents', 'MacOS') if common.is_mac_platform() else os.path.join(qtc_binary_path, 'Contents', 'MacOS') if common.is_mac_platform()
else os.path.join(qtc_binary_path, '..', 'lib', 'Qt', 'bin')) else os.path.join(qtc_binary_path, '..', 'lib', 'Qt', 'bin'))
if not os.path.exists(destdir): if not os.path.exists(destdir):
os.makedirs(destdir) os.makedirs(destdir)
shutil.copy(qtdiag_src, destdir) shutil.copy(binary_src, destdir)
if common.is_mac_platform(): if common.is_mac_platform():
# fix RPATHs # fix RPATHs
qtdiag_dest = os.path.join(destdir, 'qtdiag') binary_dest = os.path.join(destdir, binary_name)
subprocess.check_call(['xcrun', 'install_name_tool', '-add_rpath', '@loader_path/../Frameworks', qtdiag_dest]) subprocess.check_call(['xcrun', 'install_name_tool', '-add_rpath', '@loader_path/../Frameworks', binary_dest])
subprocess.check_call(['xcrun', 'install_name_tool', '-delete_rpath', '@loader_path/../lib', qtdiag_dest]) subprocess.check_call(['xcrun', 'install_name_tool', '-delete_rpath', '@loader_path/../lib', binary_dest])
def deploy_plugins(qtc_binary_path, qt_install): def deploy_plugins(qtc_binary_path, qt_install):
@@ -469,7 +469,8 @@ def main():
qtcreator_binary_path = (args.qtcreator_binary if common.is_mac_platform() qtcreator_binary_path = (args.qtcreator_binary if common.is_mac_platform()
else os.path.dirname(args.qtcreator_binary)) else os.path.dirname(args.qtcreator_binary))
deploy_qtdiag(qtcreator_binary_path, qt_install) deploy_binary('qtdiag', qtcreator_binary_path, qt_install)
deploy_binary('qsb', qtcreator_binary_path, qt_install)
deploy_plugins(qtcreator_binary_path, qt_install) deploy_plugins(qtcreator_binary_path, qt_install)
deploy_imports(qtcreator_binary_path, qt_install) deploy_imports(qtcreator_binary_path, qt_install)
deploy_translations(qtcreator_binary_path, qt_install) deploy_translations(qtcreator_binary_path, qt_install)

View File

@@ -99,45 +99,17 @@ Item {
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
IconButton { IconButton {
icon: StudioTheme.Constants.updateContent_medium icon: StudioTheme.Constants.save_medium
tooltip: qsTr("Update existing file with changes") tooltip: qsTr("Save changes")
enabled: root.model.collectionName !== "" enabled: root.model.collectionName !== ""
onClicked: onClicked: root.model.saveCurrentCollection()
{
if (root.backend.selectedSourceAddress().indexOf("json") !== -1)
root.model.exportCollection(root.backend.selectedSourceAddress(), root.model.collectionName, "JSON")
else
root.model.exportCollection(root.backend.selectedSourceAddress(), root.model.collectionName, "CSV")
}
} }
IconButton { IconButton {
icon: StudioTheme.Constants.export_medium icon: StudioTheme.Constants.export_medium
tooltip: qsTr("Export the model to a new file") tooltip: qsTr("Export model")
enabled: root.model.collectionName !== "" enabled: root.model.collectionName !== ""
onClicked: exportMenu.popup() onClicked: fileDialog.open()
}
StudioControls.Menu {
id: exportMenu
StudioControls.MenuItem {
text: qsTr("Export as JSON")
onTriggered:
{
fileDialog.defaultSuffix = "json"
fileDialog.open()
}
}
StudioControls.MenuItem {
text: qsTr("Export as CSV")
onTriggered:
{
fileDialog.defaultSuffix = "csv"
fileDialog.open()
}
}
} }
} }
} }
@@ -145,17 +117,18 @@ Item {
PlatformWidgets.FileDialog { PlatformWidgets.FileDialog {
id: fileDialog id: fileDialog
fileMode: PlatformWidgets.FileDialog.SaveFile fileMode: PlatformWidgets.FileDialog.SaveFile
onAccepted:
{
var fileAddress = file.toString()
if (fileAddress.indexOf("json") !== -1) nameFilters: ["JSON Files (*.json)",
root.model.exportCollection(fileAddress, root.model.collectionName, "JSON") "Comma-Separated Values (*.csv)"
else if (fileAddress.indexOf("csv") !== -1) ]
root.model.exportCollection(fileAddress, root.model.collectionName, "CSV")
fileDialog.reject() selectedNameFilter.index: 0
onAccepted: {
let filePath = fileDialog.file.toString()
root.model.exportCollection(filePath)
} }
} }

View File

@@ -4,6 +4,7 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import CollectionDetails 1.0 as CollectionDetails
import HelperWidgets 2.0 as HelperWidgets import HelperWidgets 2.0 as HelperWidgets
import StudioTheme 1.0 as StudioTheme import StudioTheme 1.0 as StudioTheme
import StudioControls 1.0 as StudioControls import StudioControls 1.0 as StudioControls
@@ -109,15 +110,9 @@ Rectangle {
clip: true clip: true
delegate: HeaderDelegate { delegate: HeaderDelegate {
id: horizontalHeaderItem
selectedItem: tableView.model.selectedColumn selectedItem: tableView.model.selectedColumn
color: StudioTheme.Values.themeControlBackgroundInteraction color: StudioTheme.Values.themeControlBackgroundInteraction
function getGlobalBottomLeft() {
return mapToGlobal(0, horizontalHeaderItem.height)
}
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
anchors.margins: 5 anchors.margins: 5
@@ -125,47 +120,51 @@ Rectangle {
onClicked: (mouse) => { onClicked: (mouse) => {
tableView.model.selectColumn(index) tableView.model.selectColumn(index)
if (mouse.button === Qt.RightButton) if (mouse.button === Qt.RightButton) {
headerMenu.popIndex(index, horizontalHeaderItem.getGlobalBottomLeft()) let posX = index === root.model.columnCount() - 1 ? parent.width - editProperyDialog.width : 0
headerMenu.clickedHeaderIndex = index
headerMenu.dialogPos = parent.mapToGlobal(posX, parent.height)
headerMenu.popup()
} }
} }
} }
HelperWidgets.ToolTipArea {
anchors.fill: parent
text: root.model.propertyType(index)
}
}
StudioControls.Menu { StudioControls.Menu {
id: headerMenu id: headerMenu
property int clickedHeader: -1 property int clickedHeaderIndex: -1
property point initialPosition property point dialogPos
function popIndex(clickedIndex, clickedRect)
{
headerMenu.clickedHeader = clickedIndex
headerMenu.initialPosition = clickedRect
headerMenu.popup()
}
onClosed: { onClosed: {
headerMenu.clickedHeader = -1 headerMenu.clickedHeaderIndex = -1
} }
StudioControls.MenuItem { StudioControls.MenuItem {
text: qsTr("Edit") text: qsTr("Edit")
onTriggered: editProperyDialog.editProperty(headerMenu.clickedHeader, headerMenu.initialPosition) onTriggered: editProperyDialog.openDialog(headerMenu.clickedHeaderIndex,
headerMenu.dialogPos)
} }
StudioControls.MenuItem { StudioControls.MenuItem {
text: qsTr("Delete") text: qsTr("Delete")
onTriggered: deleteColumnDialog.popUp(headerMenu.clickedHeader) onTriggered: deleteColumnDialog.popUp(headerMenu.clickedHeaderIndex)
} }
StudioControls.MenuItem { StudioControls.MenuItem {
text: qsTr("Sort Ascending") text: qsTr("Sort Ascending")
onTriggered: sortedModel.sort(headerMenu.clickedHeader, Qt.AscendingOrder) onTriggered: sortedModel.sort(headerMenu.clickedHeaderIndex, Qt.AscendingOrder)
} }
StudioControls.MenuItem { StudioControls.MenuItem {
text: qsTr("Sort Descending") text: qsTr("Sort Descending")
onTriggered: sortedModel.sort(headerMenu.clickedHeader, Qt.DescendingOrder) onTriggered: sortedModel.sort(headerMenu.clickedHeaderIndex, Qt.DescendingOrder)
} }
} }
} }
@@ -209,13 +208,23 @@ Rectangle {
id: itemCell id: itemCell
implicitWidth: 100 implicitWidth: 100
implicitHeight: itemText.height implicitHeight: itemText.height
border.color: dataTypeWarning !== CollectionDetails.Warning.None ?
StudioTheme.Values.themeWarning : StudioTheme.Values.themeControlBackgroundInteraction
border.width: 1 border.width: 1
HelperWidgets.ToolTipArea {
anchors.fill: parent
text: root.model.warningToString(dataTypeWarning)
enabled: dataTypeWarning !== CollectionDetails.Warning.None && text !== ""
hoverEnabled: true
acceptedButtons: Qt.NoButton
}
Text { Text {
id: itemText id: itemText
text: display ? display : "" text: display
color: StudioTheme.Values.themePlaceholderTextColorInteraction
width: parent.width width: parent.width
leftPadding: 5 leftPadding: 5
topPadding: 3 topPadding: 3
@@ -241,7 +250,6 @@ Rectangle {
PropertyChanges { PropertyChanges {
target: itemCell target: itemCell
color: StudioTheme.Values.themeControlBackground color: StudioTheme.Values.themeControlBackground
border.color: StudioTheme.Values.themeControlBackgroundInteraction
} }
PropertyChanges { PropertyChanges {
@@ -306,14 +314,11 @@ Rectangle {
} }
Text { Text {
anchors.fill: parent anchors.centerIn: parent
text: qsTr("Select a model to continue") text: qsTr("Select a model to continue")
visible: !topRow.visible visible: !topRow.visible
textFormat: Text.RichText
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
font.pixelSize: StudioTheme.Values.mediumFontSize font.pixelSize: StudioTheme.Values.mediumFontSize
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
} }
TextMetrics { TextMetrics {

View File

@@ -12,10 +12,11 @@ Item {
id: root id: root
implicitWidth: 300 implicitWidth: 300
implicitHeight: innerRect.height + 3 implicitHeight: boundingRect.height + 3
property color textColor property color textColor
property string sourceType property string sourceType
property bool hasSelectedTarget
signal selectItem(int itemIndex) signal selectItem(int itemIndex)
signal deleteItem() signal deleteItem()
@@ -23,9 +24,8 @@ Item {
Item { Item {
id: boundingRect id: boundingRect
anchors.centerIn: root
width: parent.width width: parent.width
height: nameHolder.height height: itemLayout.height
clip: true clip: true
MouseArea { MouseArea {
@@ -49,39 +49,24 @@ Item {
} }
RowLayout { RowLayout {
id: itemLayout
width: parent.width width: parent.width
Text {
id: moveTool
property StudioTheme.ControlStyle style: StudioTheme.Values.viewBarButtonStyle
Layout.preferredWidth: moveTool.style.squareControlSize.width
Layout.preferredHeight: nameHolder.height
Layout.leftMargin: 12
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
text: StudioTheme.Constants.dragmarks
font.family: StudioTheme.Constants.iconFont.family
font.pixelSize: moveTool.style.baseIconFontSize
color: root.textColor
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
Text { Text {
id: nameHolder id: nameHolder
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.leftMargin: StudioTheme.Values.collectionItemTextSideMargin
Layout.topMargin: StudioTheme.Values.collectionItemTextMargin
Layout.bottomMargin: StudioTheme.Values.collectionItemTextMargin
text: collectionName text: collectionName
font.pixelSize: StudioTheme.Values.baseFontSize font.pixelSize: StudioTheme.Values.baseFontSize
color: root.textColor color: root.textColor
leftPadding: 5 topPadding: StudioTheme.Values.collectionItemTextPadding
topPadding: 8 bottomPadding: StudioTheme.Values.collectionItemTextPadding
rightPadding: 8
bottomPadding: 8
elide: Text.ElideMiddle elide: Text.ElideMiddle
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
@@ -90,13 +75,16 @@ Item {
id: threeDots id: threeDots
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
Layout.topMargin: StudioTheme.Values.collectionItemTextMargin
Layout.bottomMargin: StudioTheme.Values.collectionItemTextMargin
Layout.rightMargin: StudioTheme.Values.collectionItemTextSideMargin
text: StudioTheme.Constants.more_medium text: StudioTheme.Constants.more_medium
font.family: StudioTheme.Constants.iconFont.family font.family: StudioTheme.Constants.iconFont.family
font.pixelSize: StudioTheme.Values.baseIconFontSize font.pixelSize: StudioTheme.Values.baseIconFontSize
color: root.textColor color: root.textColor
rightPadding: 12 padding: StudioTheme.Values.collectionItemTextPadding
topPadding: nameHolder.topPadding
bottomPadding: nameHolder.bottomPadding
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
@@ -125,6 +113,12 @@ Item {
shortcut: StandardKey.Replace shortcut: StandardKey.Replace
onTriggered: renameDialog.open() onTriggered: renameDialog.open()
} }
StudioControls.MenuItem {
text: qsTr("Assign to the selected node")
enabled: root.hasSelectedTarget
onTriggered: rootView.assignCollectionToSelectedNode(collectionName)
}
} }
component Spacer: Item { component Spacer: Item {
@@ -137,6 +131,7 @@ Item {
title: qsTr("Deleting the model") title: qsTr("Deleting the model")
clip: true clip: true
implicitWidth: 300
contentItem: ColumnLayout { contentItem: ColumnLayout {
spacing: 2 spacing: 2

View File

@@ -24,15 +24,8 @@ Item {
warningDialog.open() warningDialog.open()
} }
JsonImport { ImportDialog {
id: jsonImporter id: importDialog
backendValue: root.rootView
anchors.centerIn: parent
}
CsvImport {
id: csvImporter
backendValue: root.rootView backendValue: root.rootView
anchors.centerIn: parent anchors.centerIn: parent
@@ -62,46 +55,37 @@ Item {
ColumnLayout { ColumnLayout {
id: collectionsSideBar id: collectionsSideBar
spacing: 0
Layout.alignment: Qt.AlignTop | Qt.AlignLeft Layout.alignment: Qt.AlignTop | Qt.AlignLeft
Layout.minimumWidth: 300 Layout.minimumWidth: 300
Layout.fillWidth: !grid.isHorizontal Layout.fillWidth: !grid.isHorizontal
RowLayout { Rectangle {
spacing: StudioTheme.Values.sectionRowSpacing color: StudioTheme.Values.themeToolbarBackground
Layout.preferredHeight: StudioTheme.Values.toolbarHeight
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 50
Text { Text {
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter anchors.verticalCenter: parent.verticalCenter
Layout.fillWidth: true anchors.left: parent.left
anchors.leftMargin: StudioTheme.Values.toolbarHorizontalMargin
text: qsTr("Data Models") text: qsTr("Data Models")
font.pixelSize: StudioTheme.Values.baseFontSize font.pixelSize: StudioTheme.Values.baseFontSize
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
leftPadding: 15
} }
IconTextButton { HelperWidgets.AbstractButton {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: StudioTheme.Values.toolbarHorizontalMargin
icon: StudioTheme.Constants.import_medium style: StudioTheme.Values.viewBarButtonStyle
text: qsTr("JSON") buttonIcon: StudioTheme.Constants.import_medium
tooltip: qsTr("Import JSON") tooltip: qsTr("Import a model")
radius: StudioTheme.Values.smallRadius
onClicked: jsonImporter.open() onClicked: importDialog.open()
}
IconTextButton {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
icon: StudioTheme.Constants.import_medium
text: qsTr("CSV")
tooltip: qsTr("Import CSV")
radius: StudioTheme.Values.smallRadius
onClicked: csvImporter.open()
} }
} }
@@ -128,9 +112,7 @@ Item {
delegate: ModelSourceItem { delegate: ModelSourceItem {
implicitWidth: sourceListView.width implicitWidth: sourceListView.width
onDeleteItem: root.model.removeRow(index)
hasSelectedTarget: root.rootView.targetNodeSelected hasSelectedTarget: root.rootView.targetNodeSelected
onAssignToSelected: root.rootView.assignSourceNodeToSelectedItem(sourceNode)
} }
} }
} }
@@ -138,7 +120,7 @@ Item {
HelperWidgets.IconButton { HelperWidgets.IconButton {
id: addCollectionButton id: addCollectionButton
iconSize:16 iconSize: 16
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumWidth: 24 Layout.minimumWidth: 24
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter Layout.alignment: Qt.AlignTop | Qt.AlignHCenter

View File

@@ -12,26 +12,22 @@ StudioControls.Dialog {
required property var model required property var model
property int __propertyIndex: -1 property int __propertyIndex: -1
property string __oldName property string __currentName
title: qsTr("Edit Property") title: qsTr("Edit Column")
clip: true
function editProperty(index, initialPosition) { function openDialog(index, initialPosition) {
root.__propertyIndex = index root.__propertyIndex = index
if (root.__propertyIndex < 0) if (root.__propertyIndex < 0)
return return
let previousName = root.model.propertyName(root.__propertyIndex) root.__currentName = root.model.propertyName(root.__propertyIndex)
let previousType = root.model.propertyType(root.__propertyIndex) nameTextField.text = root.__currentName
nameTextField.selectAll()
nameTextField.forceActiveFocus()
root.__oldName = previousName typeComboBox.initialType = root.model.propertyType(root.__propertyIndex)
newNameField.text = previousName
propertyType.initialType = previousType
forceChangeType.checked = false
let newPoint = mapFromGlobal(initialPosition.x, initialPosition.y) let newPoint = mapFromGlobal(initialPosition.x, initialPosition.y)
x = newPoint.x x = newPoint.x
@@ -41,35 +37,31 @@ StudioControls.Dialog {
} }
onAccepted: { onAccepted: {
if (newNameField.text !== "" && newNameField.text !== root.__oldName) if (nameTextField.text !== "" && nameTextField.text !== root.__currentName)
root.model.renameColumn(root.__propertyIndex, newNameField.text) root.model.renameColumn(root.__propertyIndex, nameTextField.text)
if (propertyType.changed || forceChangeType.checked) if (typeComboBox.initialType !== typeComboBox.currentText)
root.model.setPropertyType(root.__propertyIndex, propertyType.currentText, forceChangeType.checked) root.model.setPropertyType(root.__propertyIndex, typeComboBox.currentText)
} }
onRejected: { contentItem: Column {
let currentDatatype = propertyType.initialType spacing: 5
propertyType.currentIndex = propertyType.find(currentDatatype)
}
component Spacer: Item { Grid {
implicitWidth: 1 columns: 2
implicitHeight: StudioTheme.Values.columnGap rows: 2
}
contentItem: ColumnLayout {
spacing: 2 spacing: 2
verticalItemAlignment: Grid.AlignVCenter
Text { Text {
text: qsTr("Name") text: qsTr("Name")
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
width: 50
verticalAlignment: Text.AlignVCenter
} }
StudioControls.TextField { StudioControls.TextField {
id: newNameField id: nameTextField
Layout.fillWidth: true
actionIndicator.visible: false actionIndicator.visible: false
translationIndicator.visible: false translationIndicator.visible: false
@@ -81,13 +73,7 @@ StudioControls.Dialog {
validator: RegularExpressionValidator { validator: RegularExpressionValidator {
regularExpression: /^\w+$/ regularExpression: /^\w+$/
} }
onTextChanged: {
editButton.enabled = newNameField.text !== ""
} }
}
Spacer {}
Text { Text {
text: qsTr("Type") text: qsTr("Type")
@@ -95,90 +81,72 @@ StudioControls.Dialog {
} }
StudioControls.ComboBox { StudioControls.ComboBox {
id: propertyType id: typeComboBox
Layout.fillWidth: true
property string initialType property string initialType
readonly property bool changed: propertyType.initialType !== propertyType.currentText
model: root.model.typesList() model: root.model.typesList()
actionIndicatorVisible: false actionIndicatorVisible: false
onInitialTypeChanged: propertyType.currentIndex = propertyType.find(initialType) onInitialTypeChanged: typeComboBox.currentIndex = typeComboBox.find(initialType)
} }
Spacer {}
RowLayout {
spacing: StudioTheme.Values.sectionRowSpacing
StudioControls.CheckBox {
id: forceChangeType
actionIndicatorVisible: false
}
Text {
text: qsTr("Force update values")
color: StudioTheme.Values.themeTextColor
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
}
}
Spacer {
visible: warningBox.visible
implicitHeight: StudioTheme.Values.controlLabelGap
} }
Rectangle { Rectangle {
id: warningBox id: warningBox
Layout.fillWidth: true visible: typeComboBox.initialType !== typeComboBox.currentText
Layout.preferredHeight: warning.height
visible: propertyType.initialType !== propertyType.currentText
color: "transparent" color: "transparent"
clip: true clip: true
border.color: StudioTheme.Values.themeWarning border.color: StudioTheme.Values.themeWarning
width: parent.width
height: warning.height
RowLayout { Row {
id: warning id: warning
width: parent.width padding: 5
spacing: 5
HelperWidgets.IconLabel { HelperWidgets.IconLabel {
icon: StudioTheme.Constants.warning_medium icon: StudioTheme.Constants.warning_medium
Layout.leftMargin: 10 anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: qsTr("Conversion from %1 to %2 may lead to irreversible data loss") text: qsTr("Conversion from %1 to %2 may lead to data loss")
.arg(propertyType.initialType) .arg(typeComboBox.initialType)
.arg(propertyType.currentText) .arg(typeComboBox.currentText)
width: warningBox.width - 20
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
Layout.fillWidth: true
Layout.margins: 8
} }
} }
} }
Spacer {} Row {
height: 40
RowLayout { spacing: 5
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter anchors.right: parent.right
spacing: StudioTheme.Values.sectionRowSpacing
HelperWidgets.Button { HelperWidgets.Button {
id: editButton id: editButton
text: qsTr("Edit") text: qsTr("Apply")
enabled: nameTextField.text !== ""
width: 70
anchors.bottom: parent.bottom
onClicked: root.accept() onClicked: root.accept()
} }
HelperWidgets.Button { HelperWidgets.Button {
text: qsTr("Cancel") text: qsTr("Cancel")
anchors.bottom: parent.bottom
width: 70
onClicked: root.reject() onClicked: root.reject()
} }
} }

View File

@@ -13,7 +13,7 @@ import StudioTheme as StudioTheme
StudioControls.Dialog { StudioControls.Dialog {
id: root id: root
title: qsTr("Import A CSV File") title: qsTr("Import a model")
anchors.centerIn: parent anchors.centerIn: parent
closePolicy: Popup.CloseOnEscape closePolicy: Popup.CloseOnEscape
modal: true modal: true
@@ -23,8 +23,8 @@ StudioControls.Dialog {
property bool fileExists: false property bool fileExists: false
onOpened: { onOpened: {
collectionName.text = "Collection_" collectionName.text = "Model"
fileName.text = qsTr("New CSV File") fileName.text = qsTr("Model path")
fileName.selectAll() fileName.selectAll()
fileName.forceActiveFocus() fileName.forceActiveFocus()
} }
@@ -40,6 +40,14 @@ StudioControls.Dialog {
PlatformWidgets.FileDialog { PlatformWidgets.FileDialog {
id: fileDialog id: fileDialog
nameFilters : ["All Model Files (*.json *.csv)",
"JSON Files (*.json)",
"Comma-Separated Values (*.csv)"]
title: qsTr("Select a model file")
fileMode: PlatformWidgets.FileDialog.OpenFile
acceptLabel: qsTr("Open")
onAccepted: fileName.text = fileDialog.file onAccepted: fileName.text = fileDialog.file
} }
@@ -61,7 +69,7 @@ StudioControls.Dialog {
spacing: 2 spacing: 2
Text { Text {
text: qsTr("File name: ") text: qsTr("File name")
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
} }
@@ -80,11 +88,11 @@ StudioControls.Dialog {
translationIndicator.visible: false translationIndicator.visible: false
validator: fileNameValidator validator: fileNameValidator
Keys.onEnterPressed: btnCreate.onClicked() Keys.onEnterPressed: btnImport.onClicked()
Keys.onReturnPressed: btnCreate.onClicked() Keys.onReturnPressed: btnImport.onClicked()
Keys.onEscapePressed: root.reject() Keys.onEscapePressed: root.reject()
onTextChanged: root.fileExists = root.backendValue.isCsvFile(fileName.text) onTextChanged: root.fileExists = root.backendValue.isValidUrlToImport(fileName.text)
} }
HelperWidgets.Button { HelperWidgets.Button {
@@ -100,7 +108,7 @@ StudioControls.Dialog {
Spacer {} Spacer {}
Text { Text {
text: qsTr("The model name: ") text: qsTr("The model name")
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
} }
@@ -115,8 +123,8 @@ StudioControls.Dialog {
regularExpression: /^\w+$/ regularExpression: /^\w+$/
} }
Keys.onEnterPressed: btnCreate.onClicked() Keys.onEnterPressed: btnImport.onClicked()
Keys.onReturnPressed: btnCreate.onClicked() Keys.onReturnPressed: btnImport.onClicked()
Keys.onEscapePressed: root.reject() Keys.onEscapePressed: root.reject()
} }
@@ -179,15 +187,17 @@ StudioControls.Dialog {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
HelperWidgets.Button { HelperWidgets.Button {
id: btnCreate id: btnImport
text: qsTr("Import") text: qsTr("Import")
enabled: root.fileExists && collectionName.text !== "" enabled: root.fileExists && collectionName.text !== ""
onClicked: { onClicked: {
let csvLoaded = root.backendValue.loadCsvFile(fileName.text, collectionName.text) let collectionImported = root.backendValue.importCollectionToDataStore(
collectionName.text,
fileName.text)
if (csvLoaded) if (collectionImported)
root.accept() root.accept()
else else
creationFailedDialog.open() creationFailedDialog.open()

View File

@@ -1,145 +0,0 @@
// 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 QtQuick.Layouts
import QtQuickDesignerTheme 1.0
import Qt.labs.platform as PlatformWidgets
import HelperWidgets 2.0 as HelperWidgets
import StudioControls 1.0 as StudioControls
import StudioTheme as StudioTheme
StudioControls.Dialog {
id: root
title: qsTr("Import Models")
anchors.centerIn: parent
closePolicy: Popup.CloseOnEscape
modal: true
required property var backendValue
property bool fileExists: false
onOpened: {
fileName.text = qsTr("New JSON File")
fileName.selectAll()
fileName.forceActiveFocus()
}
onRejected: {
fileName.text = ""
}
RegularExpressionValidator {
id: fileNameValidator
regularExpression: /^(\w[^*><?|]*)[^/\\:*><?|]$/
}
PlatformWidgets.FileDialog {
id: fileDialog
onAccepted: fileName.text = fileDialog.file
}
Message {
id: creationFailedDialog
title: qsTr("Could not load the file")
message: qsTr("An error occurred while trying to load the file.")
onClosed: root.reject()
}
contentItem: ColumnLayout {
spacing: 2
Text {
text: qsTr("File name: ")
color: StudioTheme.Values.themeTextColor
}
RowLayout {
spacing: StudioTheme.Values.sectionRowSpacing
Layout.fillWidth: true
StudioControls.TextField {
id: fileName
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillWidth: true
actionIndicator.visible: false
translationIndicator.visible: false
validator: fileNameValidator
Keys.onEnterPressed: btnCreate.onClicked()
Keys.onReturnPressed: btnCreate.onClicked()
Keys.onEscapePressed: root.reject()
onTextChanged: root.fileExists = root.backendValue.isJsonFile(fileName.text)
}
HelperWidgets.Button {
id: fileDialogButton
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
text: qsTr("Open")
onClicked: fileDialog.open()
}
}
Item { // spacer
implicitWidth: 1
implicitHeight: StudioTheme.Values.controlLabelGap
}
Label {
Layout.fillWidth: true
padding: 5
text: qsTr("File name cannot be empty.")
wrapMode: Label.WordWrap
color: StudioTheme.Values.themeTextColor
visible: fileName.text === ""
background: Rectangle {
color: "transparent"
border.width: StudioTheme.Values.border
border.color: StudioTheme.Values.themeWarning
}
}
Item { // spacer
implicitWidth: 1
implicitHeight: StudioTheme.Values.sectionColumnSpacing
}
RowLayout {
spacing: StudioTheme.Values.sectionRowSpacing
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
HelperWidgets.Button {
id: btnCreate
text: qsTr("Import")
enabled: root.fileExists
onClicked: {
let jsonLoaded = root.backendValue.loadJsonFile(fileName.text)
if (jsonLoaded)
root.accept()
else
creationFailedDialog.open()
}
}
HelperWidgets.Button {
text: qsTr("Cancel")
onClicked: root.reject()
}
}
}
}

View File

@@ -12,342 +12,25 @@ Item {
id: root id: root
implicitWidth: 300 implicitWidth: 300
implicitHeight: wholeColumn.height implicitHeight: collectionListView.height
property bool hasSelectedTarget property bool hasSelectedTarget
property color textColor
property var collectionModel
property bool expanded: false
signal selectItem(int itemIndex)
signal deleteItem()
signal assignToSelected()
function toggleExpanded() {
if (collectionListView.count > 0)
root.expanded = !root.expanded || sourceIsSelected;
}
ColumnLayout {
id: wholeColumn
width: parent.width
spacing: 0
Item {
id: boundingRect
Layout.fillWidth: true
Layout.preferredHeight: nameHolder.height
Layout.leftMargin: 6
clip: true
MouseArea {
id: itemMouse
anchors.fill: parent
acceptedButtons: Qt.LeftButton
propagateComposedEvents: true
hoverEnabled: true
onClicked: (event) => {
if (!sourceIsSelected) {
sourceIsSelected = true
event.accepted = true
}
}
onDoubleClicked: (event) => {
root.toggleExpanded()
}
}
Rectangle {
id: innerRect
anchors.fill: parent
}
RowLayout {
width: parent.width
Text {
id: expandButton
property StudioTheme.ControlStyle style: StudioTheme.Values.viewBarButtonStyle
Layout.preferredWidth: expandButton.style.squareControlSize.width
Layout.preferredHeight: nameHolder.height
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
text: StudioTheme.Constants.startNode
font.family: StudioTheme.Constants.iconFont.family
font.pixelSize: 6
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: textColor
rotation: root.expanded ? 90 : 0
visible: collectionListView.count > 0
Behavior on rotation {
SpringAnimation { spring: 2; damping: 0.2 }
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton | Qt.LeftButton
onClicked: root.toggleExpanded()
}
}
Text {
id: nameHolder
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
text: sourceName
font.pixelSize: StudioTheme.Values.baseFontSize
color: textColor
leftPadding: 5
topPadding: 8
rightPadding: 8
bottomPadding: 8
elide: Text.ElideMiddle
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
}
Text {
id: threeDots
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
text: StudioTheme.Constants.more_medium
font.family: StudioTheme.Constants.iconFont.family
font.pixelSize: StudioTheme.Values.baseIconFontSize
color: textColor
rightPadding: 12
topPadding: nameHolder.topPadding
bottomPadding: nameHolder.bottomPadding
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton | Qt.LeftButton
onClicked: collectionMenu.popup()
}
}
}
}
ListView { ListView {
id: collectionListView id: collectionListView
Layout.fillWidth: true width: parent.width
Layout.preferredHeight: root.expanded ? contentHeight : 0 implicitHeight: contentHeight
Layout.leftMargin: 6 leftMargin: 6
model: internalModels model: internalModels
clip: true clip: true
Behavior on Layout.preferredHeight {
NumberAnimation {duration: 500}
}
delegate: CollectionItem { delegate: CollectionItem {
width: collectionListView.width width: collectionListView.width
sourceType: collectionListView.model.sourceType sourceType: collectionListView.model.sourceType
hasSelectedTarget: root.hasSelectedTarget
onDeleteItem: collectionListView.model.removeRow(index) onDeleteItem: collectionListView.model.removeRow(index)
} }
} }
}
StudioControls.Menu {
id: collectionMenu
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
StudioControls.MenuItem {
text: qsTr("Delete")
shortcut: StandardKey.Delete
onTriggered: deleteDialog.open()
}
StudioControls.MenuItem {
text: qsTr("Rename")
shortcut: StandardKey.Replace
onTriggered: renameDialog.open()
}
StudioControls.MenuItem {
text: qsTr("Assign to the selected node")
enabled: root.hasSelectedTarget
onTriggered: root.assignToSelected()
}
}
component Spacer: Item {
implicitWidth: 1
implicitHeight: StudioTheme.Values.sectionColumnSpacing
}
StudioControls.Dialog {
id: deleteDialog
title: qsTr("Deleting source")
contentItem: ColumnLayout {
spacing: StudioTheme.Values.sectionColumnSpacing
Text {
text: qsTr("Are you sure that you want to delete source \"" + sourceName + "\"?")
color: StudioTheme.Values.themeTextColor
}
RowLayout {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
spacing: StudioTheme.Values.sectionRowSpacing
HelperWidgets.Button {
id: btnDelete
text: qsTr("Delete")
onClicked: root.deleteItem(index)
}
HelperWidgets.Button {
text: qsTr("Cancel")
onClicked: deleteDialog.reject()
}
}
}
}
StudioControls.Dialog {
id: renameDialog
title: qsTr("Rename source")
onAccepted: {
if (newNameField.text !== "")
sourceName = newNameField.text
}
onOpened: {
newNameField.text = sourceName
}
contentItem: ColumnLayout {
spacing: 2
Text {
text: qsTr("Previous name: " + sourceName)
color: StudioTheme.Values.themeTextColor
}
Spacer {}
Text {
text: qsTr("New name:")
color: StudioTheme.Values.themeTextColor
}
StudioControls.TextField {
id: newNameField
Layout.fillWidth: true
actionIndicator.visible: false
translationIndicator.visible: false
validator: newNameValidator
Keys.onEnterPressed: renameDialog.accept()
Keys.onReturnPressed: renameDialog.accept()
Keys.onEscapePressed: renameDialog.reject()
onTextChanged: {
btnRename.enabled = newNameField.text !== ""
}
}
Spacer {}
RowLayout {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
spacing: StudioTheme.Values.sectionRowSpacing
HelperWidgets.Button {
id: btnRename
text: qsTr("Rename")
onClicked: renameDialog.accept()
}
HelperWidgets.Button {
text: qsTr("Cancel")
onClicked: renameDialog.reject()
}
}
}
}
RegularExpressionValidator {
id: newNameValidator
regularExpression: /^\w+$/
}
states: [
State {
name: "default"
when: !sourceIsSelected && !itemMouse.containsMouse
PropertyChanges {
target: innerRect
opacity: 0.4
color: StudioTheme.Values.themeControlBackground
}
PropertyChanges {
target: root
textColor: StudioTheme.Values.themeTextColor
}
},
State {
name: "hovered"
when: !sourceIsSelected && itemMouse.containsMouse
PropertyChanges {
target: innerRect
opacity: 0.5
color: StudioTheme.Values.themeControlBackgroundHover
}
PropertyChanges {
target: root
textColor: StudioTheme.Values.themeTextColor
}
},
State {
name: "selected"
when: sourceIsSelected
PropertyChanges {
target: innerRect
opacity: 0.6
color: StudioTheme.Values.themeControlBackgroundInteraction
}
PropertyChanges {
target: root
textColor: StudioTheme.Values.themeIconColorSelected
expanded: true
}
PropertyChanges {
target: expandButton
enabled: false
}
}
]
} }

View File

@@ -14,15 +14,12 @@ import CollectionEditor 1.0
StudioControls.Dialog { StudioControls.Dialog {
id: root id: root
enum SourceType { NewJson, NewCsv, ExistingCollection, NewCollectionToJson } enum SourceType { NewJson, NewCsv, ExistingCollection }
required property var backendValue required property var backendValue
required property var sourceModel required property var sourceModel
readonly property alias collectionType: typeMode.collectionType
readonly property bool isValid: collectionName.isValid readonly property bool isValid: collectionName.isValid
&& jsonCollections.isValid
&& newCollectionPath.isValid
title: qsTr("Add a new Model") title: qsTr("Add a new Model")
anchors.centerIn: parent anchors.centerIn: parent
@@ -31,8 +28,6 @@ StudioControls.Dialog {
onOpened: { onOpened: {
collectionName.text = qsTr("Model") collectionName.text = qsTr("Model")
updateType()
updateJsonSourceIndex()
updateCollectionExists() updateCollectionExists()
} }
@@ -41,57 +36,12 @@ StudioControls.Dialog {
} }
onAccepted: { onAccepted: {
if (root.isValid) { if (root.isValid)
root.backendValue.addCollection(collectionName.text, root.backendValue.addCollectionToDataStore(collectionName.text);
root.collectionType,
newCollectionPath.text,
jsonCollections.currentValue)
}
}
function updateType() {
newCollectionPath.text = ""
if (typeMode.currentValue === NewCollectionDialog.SourceType.NewJson) {
newCollectionFileDialog.nameFilters = ["JSON Files (*.json)"]
newCollectionFileDialog.fileMode = PlatformWidgets.FileDialog.SaveFile
newCollectionPath.enabled = true
jsonCollections.enabled = false
typeMode.collectionType = "json"
} else if (typeMode.currentValue === NewCollectionDialog.SourceType.NewCsv) {
newCollectionFileDialog.nameFilters = ["Comma-Separated Values (*.csv)"]
newCollectionFileDialog.fileMode = PlatformWidgets.FileDialog.SaveFile
newCollectionPath.enabled = true
jsonCollections.enabled = false
typeMode.collectionType = "csv"
} else if (typeMode.currentValue === NewCollectionDialog.SourceType.ExistingCollection) {
newCollectionFileDialog.nameFilters = ["All Model Group Files (*.json *.csv)",
"JSON Files (*.json)",
"Comma-Separated Values (*.csv)"]
newCollectionFileDialog.fileMode = PlatformWidgets.FileDialog.OpenFile
newCollectionPath.enabled = true
jsonCollections.enabled = false
typeMode.collectionType = "existing"
} else if (typeMode.currentValue === NewCollectionDialog.SourceType.NewCollectionToJson) {
newCollectionFileDialog.nameFilters = [""]
newCollectionPath.enabled = false
jsonCollections.enabled = true
typeMode.collectionType = "json"
}
}
function updateJsonSourceIndex() {
if (!jsonCollections.enabled) {
jsonCollections.currentIndex = -1
return
}
if (jsonCollections.currentIndex === -1 && jsonCollections.model.rowCount())
jsonCollections.currentIndex = 0
} }
function updateCollectionExists() { function updateCollectionExists() {
collectionName.alreadyExists = sourceModel.collectionExists(jsonCollections.currentValue, collectionName.alreadyExists = sourceModel.collectionExists(backendValue.dataStoreNode(),
collectionName.text) collectionName.text)
} }
@@ -119,118 +69,6 @@ StudioControls.Dialog {
contentItem: ColumnLayout { contentItem: ColumnLayout {
spacing: 5 spacing: 5
NameField {
text: qsTr("Type")
}
StudioControls.ComboBox {
id: typeMode
property string collectionType
Layout.minimumWidth: 300
Layout.fillWidth: true
model: ListModel {
ListElement { text: qsTr("New JSON model group"); value: NewCollectionDialog.SourceType.NewJson}
ListElement { text: qsTr("New CSV model"); value: NewCollectionDialog.SourceType.NewCsv}
ListElement { text: qsTr("Import an existing model group"); value: NewCollectionDialog.SourceType.ExistingCollection}
ListElement { text: qsTr("Add a model to an available JSON model group"); value: NewCollectionDialog.SourceType.NewCollectionToJson}
}
textRole: "text"
valueRole: "value"
actionIndicatorVisible: false
onCurrentValueChanged: root.updateType()
}
Spacer {}
RowLayout {
visible: newCollectionPath.enabled
NameField {
text: qsTr("File location")
visible: newCollectionPath.enabled
}
Text {
id: newCollectionPath
readonly property bool isValid: !newCollectionPath.enabled || newCollectionPath.text !== ""
Layout.fillWidth: true
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
elide: Text.ElideRight
font.family: StudioTheme.Constants.font.family
font.pixelSize: StudioTheme.Values.baseIconFontSize
color: StudioTheme.Values.themePlaceholderTextColor
}
HelperWidgets.Button {
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
text: qsTr("Select")
onClicked: newCollectionFileDialog.open()
PlatformWidgets.FileDialog {
id: newCollectionFileDialog
title: qsTr("Select source file")
fileMode: PlatformWidgets.FileDialog.OpenFile
acceptLabel: newCollectionFileDialog.fileMode === PlatformWidgets.FileDialog.OpenFile
? qsTr("Open")
: qsTr("Add")
onAccepted: newCollectionPath.text = newCollectionFileDialog.currentFile
}
}
}
ErrorField {
visible: !newCollectionPath.isValid
text: qsTr("Select a file to continue")
}
Spacer { visible: newCollectionPath.enabled }
NameField {
text: qsTr("JSON model group")
visible: jsonCollections.enabled
}
StudioControls.ComboBox {
id: jsonCollections
readonly property bool isValid: !jsonCollections.enabled || jsonCollections.currentIndex !== -1
Layout.fillWidth: true
implicitWidth: 300
textRole: "sourceName"
valueRole: "sourceNode"
visible: jsonCollections.enabled
actionIndicatorVisible: false
model: CollectionJsonSourceFilterModel {
sourceModel: root.sourceModel
onRowsInserted: root.updateJsonSourceIndex()
onModelReset: root.updateJsonSourceIndex()
onRowsRemoved: root.updateJsonSourceIndex()
}
onEnabledChanged: root.updateJsonSourceIndex()
onCurrentValueChanged: root.updateCollectionExists()
}
ErrorField {
visible: !jsonCollections.isValid
text: qsTr("Add a JSON resource to continue")
}
Spacer {visible: jsonCollections.visible }
NameField { NameField {
text: qsTr("The model name") text: qsTr("The model name")
visible: collectionName.enabled visible: collectionName.enabled

View File

@@ -49,7 +49,7 @@ StudioControls.PopupDialog {
root.close() root.close()
} }
function onPopupShouldOpen() { function onPopupShouldOpen() {
root.showGlobal() Qt.callLater(root.showGlobal)
} }
} }
} }

View File

@@ -0,0 +1,68 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: BSD-3-Clause
// This file should match the BlurHelper.qml in qtquickdesigner repository, except for shader paths
import QtQuick
Item {
id: rootItem
property alias blurSrc1: blurredItemSource1
property alias blurSrc2: blurredItemSource2
property alias blurSrc3: blurredItemSource3
property alias blurSrc4: blurredItemSource4
property alias blurSrc5: blurredItemSource5
component BlurItem: ShaderEffect {
property vector2d offset: Qt.vector2d((1.0 + rootItem.blurMultiplier) / width,
(1.0 + rootItem.blurMultiplier) / height)
visible: false
layer.enabled: true
layer.smooth: true
vertexShader: g_propertyData.blur_vs_path
fragmentShader: g_propertyData.blur_fs_path
}
QtObject {
id: priv
property bool useBlurItem1: true
property bool useBlurItem2: rootItem.blurMax > 2
property bool useBlurItem3: rootItem.blurMax > 8
property bool useBlurItem4: rootItem.blurMax > 16
property bool useBlurItem5: rootItem.blurMax > 32
}
BlurItem {
id: blurredItemSource1
property Item src: priv.useBlurItem1 ? source : null
// Size of the first blurred item is by default half of the source.
// Increase for quality and decrease for performance & more blur.
readonly property int blurItemSize: 8
width: src ? Math.ceil(src.width / 16) * blurItemSize : 0
height: src ? Math.ceil(src.height / 16) * blurItemSize : 0
}
BlurItem {
id: blurredItemSource2
property Item src: priv.useBlurItem2 ? blurredItemSource1 : null
width: blurredItemSource1.width * 0.5
height: blurredItemSource1.height * 0.5
}
BlurItem {
id: blurredItemSource3
property Item src: priv.useBlurItem3 ? blurredItemSource2 : null
width: blurredItemSource2.width * 0.5
height: blurredItemSource2.height * 0.5
}
BlurItem {
id: blurredItemSource4
property Item src: priv.useBlurItem4 ? blurredItemSource3 : null
width: blurredItemSource3.width * 0.5
height: blurredItemSource3.height * 0.5
}
BlurItem {
id: blurredItemSource5
property Item src: priv.useBlurItem5 ? blurredItemSource4 : null
width: blurredItemSource4.width * 0.5
height: blurredItemSource4.height * 0.5
}
}

View File

@@ -12,19 +12,22 @@ import EffectMakerBackend
HelperWidgets.Section { HelperWidgets.Section {
id: root id: root
property int modelIndex: 0
caption: nodeName caption: nodeName
category: "EffectMaker" category: "EffectMaker"
draggable: true draggable: !isDependency
fillBackground: true fillBackground: true
showCloseButton: true showCloseButton: !isDependency
closeButtonToolTip: qsTr("Remove") closeButtonToolTip: qsTr("Remove")
visible: repeater.count > 0 || !isDependency
onCloseButtonClicked: { onCloseButtonClicked: {
EffectMakerBackend.effectMakerModel.removeNode(root.index) EffectMakerBackend.effectMakerModel.removeNode(root.modelIndex)
} }
showEyeButton: true showEyeButton: !isDependency
eyeEnabled: nodeEnabled eyeEnabled: nodeEnabled
eyeButtonToolTip: qsTr("Enable/Disable Node") eyeButtonToolTip: qsTr("Enable/Disable Node")
@@ -36,6 +39,7 @@ HelperWidgets.Section {
spacing: 10 spacing: 10
Repeater { Repeater {
id: repeater
model: nodeUniformsModel model: nodeUniformsModel
EffectCompositionNodeUniform { EffectCompositionNodeUniform {

View File

@@ -15,6 +15,8 @@ Item {
height: layout.implicitHeight height: layout.implicitHeight
visible: !uniformUseCustomValue
Component.onCompleted: { Component.onCompleted: {
if (uniformType === "int") if (uniformType === "int")
valueLoader.source = "ValueInt.qml" valueLoader.source = "ValueInt.qml"

View File

@@ -16,14 +16,45 @@ Item {
property int moveToIdx: 0 property int moveToIdx: 0
property bool previewAnimationRunning: false property bool previewAnimationRunning: false
SaveDialog { // Invoked after save changes is done
id: saveDialog property var onSaveChangesCallback: () => {}
compositionName: EffectMakerBackend.effectMakerModel.currentComposition
// Invoked from C++ side when open composition is requested and there are unsaved changes
function promptToSaveBeforeOpen() {
root.onSaveChangesCallback = () => { EffectMakerBackend.rootView.doOpenComposition() }
saveChangesDialog.open()
}
Connections {
target: EffectMakerBackend.effectMakerModel
function onIsEmptyChanged() {
if (EffectMakerBackend.effectMakerModel.isEmpty)
saveAsDialog.close()
}
}
SaveAsDialog {
id: saveAsDialog
anchors.centerIn: parent anchors.centerIn: parent
onAccepted: { }
let name = saveDialog.compositionName
EffectMakerBackend.effectMakerModel.exportComposition(name) SaveChangesDialog {
EffectMakerBackend.effectMakerModel.exportResources(name) id: saveChangesDialog
anchors.centerIn: parent
onSave: {
if (EffectMakerBackend.effectMakerModel.currentComposition === "") {
// if current composition is unsaved, show save as dialog and clear afterwards
saveAsDialog.clearOnClose = true
saveAsDialog.open()
} else {
root.onSaveChangesCallback()
}
}
onDiscard: {
root.onSaveChangesCallback()
} }
} }
@@ -33,7 +64,29 @@ Item {
spacing: 1 spacing: 1
EffectMakerTopBar { EffectMakerTopBar {
onSaveClicked: saveDialog.open() onAddClicked: {
root.onSaveChangesCallback = () => { EffectMakerBackend.effectMakerModel.clear() }
if (EffectMakerBackend.effectMakerModel.hasUnsavedChanges)
saveChangesDialog.open()
else
EffectMakerBackend.effectMakerModel.clear()
}
onSaveClicked: {
let name = EffectMakerBackend.effectMakerModel.currentComposition
if (name === "")
saveAsDialog.open()
else
EffectMakerBackend.effectMakerModel.saveComposition(name)
}
onSaveAsClicked: saveAsDialog.open()
onAssignToSelectedClicked: {
EffectMakerBackend.effectMakerModel.assignToSelected()
}
} }
EffectMakerPreview { EffectMakerPreview {
@@ -55,6 +108,21 @@ Item {
mainRoot: root mainRoot: root
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
x: 5
width: parent.width - 50
}
HelperWidgets.AbstractButton {
anchors.right: parent.right
anchors.rightMargin: 5
anchors.verticalCenter: parent.verticalCenter
style: StudioTheme.Values.viewBarButtonStyle
buttonIcon: StudioTheme.Constants.clearList_medium
tooltip: qsTr("Remove all effect nodes.")
enabled: !EffectMakerBackend.effectMakerModel.isEmpty
onClicked: EffectMakerBackend.effectMakerModel.clear()
} }
HelperWidgets.AbstractButton { HelperWidgets.AbstractButton {
@@ -64,7 +132,7 @@ Item {
style: StudioTheme.Values.viewBarButtonStyle style: StudioTheme.Values.viewBarButtonStyle
buttonIcon: StudioTheme.Constants.code buttonIcon: StudioTheme.Constants.code
tooltip: qsTr("Open Shader in Code Editor") tooltip: qsTr("Open Shader in Code Editor.")
visible: false // TODO: to be implemented visible: false // TODO: to be implemented
onClicked: {} // TODO onClicked: {} // TODO
@@ -96,6 +164,7 @@ Item {
delegate: EffectCompositionNode { delegate: EffectCompositionNode {
width: root.width width: root.width
modelIndex: index
Behavior on y { Behavior on y {
PropertyAnimation { PropertyAnimation {
@@ -144,7 +213,8 @@ Item {
currItem.y = root.secsY[i] currItem.y = root.secsY[i]
} }
} else if (i < root.moveFromIdx) { } else if (i < root.moveFromIdx) {
if (root.draggedSec.y < currItem.y + (currItem.height - root.draggedSec.height) * .5) { if (!repeater.model.isDependencyNode(i)
&& root.draggedSec.y < currItem.y + (currItem.height - root.draggedSec.height) * .5) {
currItem.y = root.secsY[i] + root.draggedSec.height currItem.y = root.secsY[i] + root.draggedSec.height
root.moveToIdx = Math.min(root.moveToIdx, i) root.moveToIdx = Math.min(root.moveToIdx, i)
} else { } else {

View File

@@ -84,17 +84,6 @@ Column {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
HelperWidgets.AbstractButton {
enabled: sourceImage.scale > .4
style: StudioTheme.Values.viewBarButtonStyle
buttonIcon: StudioTheme.Constants.zoomOut_medium
tooltip: qsTr("Zoom out")
onClicked: {
sourceImage.scale -= .2
}
}
HelperWidgets.AbstractButton { HelperWidgets.AbstractButton {
enabled: sourceImage.scale < 2 enabled: sourceImage.scale < 2
style: StudioTheme.Values.viewBarButtonStyle style: StudioTheme.Values.viewBarButtonStyle
@@ -106,6 +95,17 @@ Column {
} }
} }
HelperWidgets.AbstractButton {
enabled: sourceImage.scale > .4
style: StudioTheme.Values.viewBarButtonStyle
buttonIcon: StudioTheme.Constants.zoomOut_medium
tooltip: qsTr("Zoom out")
onClicked: {
sourceImage.scale -= .2
}
}
HelperWidgets.AbstractButton { HelperWidgets.AbstractButton {
enabled: sourceImage.scale !== 1 enabled: sourceImage.scale !== 1
style: StudioTheme.Values.viewBarButtonStyle style: StudioTheme.Values.viewBarButtonStyle
@@ -194,6 +194,13 @@ Column {
} }
} }
BlurHelper {
id: blurHelper
anchors.fill: parent
property int blurMax: g_propertyData.blur_helper_max_level ? g_propertyData.blur_helper_max_level : 64
property real blurMultiplier: g_propertyData.blurMultiplier ? g_propertyData.blurMultiplier : 0
}
Item { Item {
id: componentParent id: componentParent
width: source.width width: source.width

View File

@@ -15,17 +15,61 @@ Rectangle {
height: StudioTheme.Values.toolbarHeight height: StudioTheme.Values.toolbarHeight
color: StudioTheme.Values.themeToolbarBackground color: StudioTheme.Values.themeToolbarBackground
signal addClicked
signal saveClicked signal saveClicked
signal saveAsClicked
signal assignToSelectedClicked
HelperWidgets.Button { Row {
spacing: 5
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
x: 5
text: qsTr("Save in Library") HelperWidgets.AbstractButton {
style: StudioTheme.Values.viewBarButtonStyle
buttonIcon: StudioTheme.Constants.add_medium
tooltip: qsTr("Add new composition")
onClicked: root.addClicked()
}
HelperWidgets.AbstractButton {
style: StudioTheme.Values.viewBarButtonStyle
buttonIcon: StudioTheme.Constants.save_medium
tooltip: qsTr("Save current composition")
enabled: EffectMakerBackend.effectMakerModel.hasUnsavedChanges
|| EffectMakerBackend.effectMakerModel.currentComposition === ""
onClicked: root.saveClicked() onClicked: root.saveClicked()
} }
HelperWidgets.AbstractButton {
style: StudioTheme.Values.viewBarButtonStyle
buttonIcon: StudioTheme.Constants.saveAs_medium
tooltip: qsTr("Save current composition with a new name")
enabled: !EffectMakerBackend.effectMakerModel.isEmpty
onClicked: root.saveAsClicked()
}
HelperWidgets.AbstractButton {
style: StudioTheme.Values.viewBarButtonStyle
buttonIcon: StudioTheme.Constants.assignTo_medium
tooltip: qsTr("Assign current composition to selected item")
enabled: EffectMakerBackend.effectMakerModel.currentComposition !== ""
onClicked: root.assignToSelectedClicked()
}
}
Text {
readonly property string compName: EffectMakerBackend.effectMakerModel.currentComposition
text: compName !== "" ? compName : qsTr("Untitled")
anchors.centerIn: parent
color: StudioTheme.Values.themeTextColor
}
HelperWidgets.AbstractButton { HelperWidgets.AbstractButton {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: 5 anchors.rightMargin: 5

View File

@@ -15,19 +15,21 @@ Rectangle {
width: 140 width: 140
height: 32 height: 32
color: mouseArea.containsMouse ? StudioTheme.Values.themeControlBackgroundInteraction color: mouseArea.containsMouse && modelData.canBeAdded
: "transparent" ? StudioTheme.Values.themeControlBackgroundInteraction : "transparent"
signal addEffectNode(var nodeQenPath) signal addEffectNode(var nodeQenPath)
MouseArea { ToolTipArea {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
tooltip: modelData.canBeAdded ? "" : qsTr("Existing effect has conflicting properties, this effect cannot be added.")
onClicked: { onClicked: {
if (modelData.canBeAdded)
root.addEffectNode(modelData.nodeQenPath) root.addEffectNode(modelData.nodeQenPath)
} }
} }
@@ -41,13 +43,15 @@ Rectangle {
width: 32 width: 32
height: 32 height: 32
color: StudioTheme.Values.themeTextColor color: modelData.canBeAdded ? StudioTheme.Values.themeTextColor
: StudioTheme.Values.themeTextColorDisabled
source: modelData.nodeIcon source: modelData.nodeIcon
} }
Text { Text {
text: modelData.nodeName text: modelData.nodeName
color: StudioTheme.Values.themeTextColor color: modelData.canBeAdded ? StudioTheme.Values.themeTextColor
: StudioTheme.Values.themeTextColorDisabled
font.pointSize: StudioTheme.Values.smallFontSize font.pointSize: StudioTheme.Values.smallFontSize
anchors.verticalCenter: nodeIcon.verticalCenter anchors.verticalCenter: nodeIcon.verticalCenter
} }

View File

@@ -12,8 +12,6 @@ StudioControls.ComboBox {
id: root id: root
actionIndicatorVisible: false actionIndicatorVisible: false
x: 5
width: parent.width - 50
model: [qsTr("+ Add Effect")] model: [qsTr("+ Add Effect")]
@@ -23,18 +21,48 @@ StudioControls.ComboBox {
required property Item mainRoot required property Item mainRoot
readonly property int popupHeight: Math.min(800, row.height + 2)
function calculateWindowGeometry() {
var globalPos = EffectMakerBackend.rootView.globalPos(mainRoot.mapFromItem(root, 0, 0))
var screenRect = EffectMakerBackend.rootView.screenRect();
window.width = row.width + 2 // 2: scrollView left and right 1px margins
var newX = globalPos.x + root.width - window.width
if (newX < screenRect.x)
newX = globalPos.x
var newY = Math.min(screenRect.y + screenRect.height,
Math.max(screenRect.y, globalPos.y + root.height - 1))
// Check if we have more space above or below the control, and put control on that side,
// unless we have enough room for maximum size popup under the control
var newHeight
var screenY = newY - screenRect.y
if (screenRect.height - screenY > screenY || screenRect.height - screenY > root.popupHeight) {
newHeight = Math.min(root.popupHeight, screenRect.height - screenY)
} else {
newHeight = Math.min(root.popupHeight, screenY - root.height)
newY = newY - newHeight - root.height + 1
}
window.height = newHeight
window.x = newX
window.y = newY
}
Connections { Connections {
target: root.popup target: root.popup
function onAboutToShow() { function onAboutToShow() {
var a = mainRoot.mapToGlobal(0, 0) root.calculateWindowGeometry()
var b = root.mapToItem(mainRoot, 0, 0)
window.x = a.x + b.x + root.width - window.width
window.y = a.y + b.y + root.height - 1
window.show() window.show()
window.requestActivate() window.requestActivate()
// Geometry can get corrupted by first show after screen change, so recalc it
root.calculateWindowGeometry()
} }
function onAboutToHide() { function onAboutToHide() {
@@ -45,12 +73,10 @@ StudioControls.ComboBox {
Window { Window {
id: window id: window
width: row.width + 2 // 2: scrollView left and right 1px margins
height: Math.min(800, Math.min(row.height + 2, Screen.height - y - 40)) // 40: some bottom margin to cover OS bottom toolbar
flags: Qt.Dialog | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint flags: Qt.Dialog | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint
onActiveFocusItemChanged: { onActiveFocusItemChanged: {
if (!window.activeFocusItem && !root.indicator.hover && root.popup.opened) if (!window.activeFocusItem && !root.hovered && root.popup.opened)
root.popup.close() root.popup.close()
} }
@@ -59,6 +85,7 @@ StudioControls.ComboBox {
color: StudioTheme.Values.themePanelBackground color: StudioTheme.Values.themePanelBackground
border.color: StudioTheme.Values.themeInteraction border.color: StudioTheme.Values.themeInteraction
border.width: 1 border.width: 1
focus: true
HelperWidgets.ScrollView { HelperWidgets.ScrollView {
anchors.fill: parent anchors.fill: parent
@@ -67,16 +94,6 @@ StudioControls.ComboBox {
Row { Row {
id: row id: row
onWidthChanged: {
// Needed to update on first window showing, as row.width only gets
// correct value after the window is shown, so first showing is off
var a = mainRoot.mapToGlobal(0, 0)
var b = root.mapToItem(mainRoot, 0, 0)
window.x = a.x + b.x + root.width - row.width
}
padding: 10 padding: 10
spacing: 10 spacing: 10
@@ -108,6 +125,11 @@ StudioControls.ComboBox {
} }
} }
} }
Keys.onPressed: function(event) {
if (event.key === Qt.Key_Escape && root.popup.opened)
root.popup.close()
}
} }
} }
} }

View File

@@ -30,18 +30,48 @@ StudioControls.ComboBox {
"images/preview4.png"] "images/preview4.png"]
property string selectedImage: images[0] property string selectedImage: images[0]
readonly property int popupHeight: Math.min(800, col.height + 2)
function calculateWindowGeometry() {
var globalPos = EffectMakerBackend.rootView.globalPos(mainRoot.mapFromItem(root, 0, 0))
var screenRect = EffectMakerBackend.rootView.screenRect();
window.width = col.width + 2 // 2: scrollView left and right 1px margins
var newX = globalPos.x + root.width - window.width
if (newX < screenRect.x)
newX = globalPos.x
var newY = Math.min(screenRect.y + screenRect.height,
Math.max(screenRect.y, globalPos.y + root.height - 1))
// Check if we have more space above or below the control, and put control on that side,
// unless we have enough room for maximum size popup under the control
var newHeight
var screenY = newY - screenRect.y
if (screenRect.height - screenY > screenY || screenRect.height - screenY > root.popupHeight) {
newHeight = Math.min(root.popupHeight, screenRect.height - screenY)
} else {
newHeight = Math.min(root.popupHeight, screenY - root.height)
newY = newY - newHeight - root.height + 1
}
window.height = newHeight
window.x = newX
window.y = newY
}
Connections { Connections {
target: root.popup target: root.popup
function onAboutToShow() { function onAboutToShow() {
var a = mainRoot.mapToGlobal(0, 0) root.calculateWindowGeometry()
var b = root.mapToItem(mainRoot, 0, 0)
window.x = a.x + b.x + root.width - window.width
window.y = a.y + b.y + root.height - 1
window.show() window.show()
window.requestActivate() window.requestActivate()
// Geometry can get corrupted by first show after screen change, so recalc it
root.calculateWindowGeometry()
} }
function onAboutToHide() { function onAboutToHide() {
@@ -60,17 +90,29 @@ StudioControls.ComboBox {
anchors.fill: parent anchors.fill: parent
anchors.margins: 1 anchors.margins: 1
} }
MouseArea {
anchors.fill: parent
enabled: true
acceptedButtons: Qt.LeftButton
cursorShape: Qt.PointingHandCursor
onPressed: (mouse) => {
if (root.popup.opened)
root.popup.close()
else
root.popup.open()
mouse.accepted = true
}
}
} }
Window { Window {
id: window id: window
width: col.width + 2 // 2: scrollView left and right 1px margins
height: Math.min(800, Math.min(col.height + 2, Screen.height - y - 40)) // 40: some bottom margin to cover OS bottom toolbar
flags: Qt.Dialog | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint flags: Qt.Dialog | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint
onActiveFocusItemChanged: { onActiveFocusItemChanged: {
if (!window.activeFocusItem && !root.indicator.hover && root.popup.opened) if (!window.activeFocusItem && !root.hovered && root.popup.opened)
root.popup.close() root.popup.close()
} }
@@ -79,6 +121,7 @@ StudioControls.ComboBox {
color: StudioTheme.Values.themePanelBackground color: StudioTheme.Values.themePanelBackground
border.color: StudioTheme.Values.themeInteraction border.color: StudioTheme.Values.themeInteraction
border.width: 1 border.width: 1
focus: true
HelperWidgets.ScrollView { HelperWidgets.ScrollView {
anchors.fill: parent anchors.fill: parent
@@ -88,16 +131,6 @@ StudioControls.ComboBox {
Column { Column {
id: col id: col
onWidthChanged: {
// Needed to update on first window showing, as row.width only gets
// correct value after the window is shown, so first showing is off
var a = mainRoot.mapToGlobal(0, 0)
var b = root.mapToItem(mainRoot, 0, 0)
window.x = a.x + b.x + root.width - col.width
}
padding: 10 padding: 10
spacing: 10 spacing: 10
@@ -135,6 +168,11 @@ StudioControls.ComboBox {
} }
} }
} }
Keys.onPressed: function(event) {
if (event.key === Qt.Key_Escape && root.popup.opened)
root.popup.close()
}
} }
} }
} }

View File

@@ -6,7 +6,7 @@ import QtQuick.Controls
import HelperWidgets as HelperWidgets import HelperWidgets as HelperWidgets
import StudioControls as StudioControls import StudioControls as StudioControls
import StudioTheme as StudioTheme import StudioTheme as StudioTheme
import AssetsLibraryBackend import EffectMakerBackend
StudioControls.Dialog { StudioControls.Dialog {
id: root id: root
@@ -18,12 +18,13 @@ StudioControls.Dialog {
implicitWidth: 250 implicitWidth: 250
implicitHeight: 160 implicitHeight: 160
property string compositionName: "" property bool clearOnClose: false // clear the effect maker after saving
onOpened: { onOpened: {
nameText.text = compositionName //TODO: Generate unique name nameText.text = EffectMakerBackend.effectMakerModel.getUniqueEffectName()
emptyText.text = "" nameText.selectAll()
nameText.forceActiveFocus() nameText.forceActiveFocus()
emptyText.text = ""
} }
contentItem: Item { contentItem: Item {
@@ -83,14 +84,28 @@ StudioControls.Dialog {
text: qsTr("Save") text: qsTr("Save")
enabled: nameText.text !== "" enabled: nameText.text !== ""
onClicked: { onClicked: {
root.compositionName = nameText.text EffectMakerBackend.effectMakerModel.saveComposition(nameText.text)
root.accept() //TODO: Check if name is unique
if (root.clearOnClose) {
EffectMakerBackend.effectMakerModel.clear()
root.clearOnClose = false
}
root.accept() // TODO: confirm before overriding effect with same name
} }
} }
HelperWidgets.Button { HelperWidgets.Button {
text: qsTr("Cancel") text: qsTr("Cancel")
onClicked: root.reject()
onClicked: {
if (root.clearOnClose) {
EffectMakerBackend.effectMakerModel.clear()
root.clearOnClose = false
}
root.reject()
}
} }
} }
} }

View File

@@ -0,0 +1,65 @@
// 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 StudioControls as StudioControls
import StudioTheme as StudioTheme
import EffectMakerBackend
StudioControls.Dialog {
id: root
title: qsTr("Save Changes")
closePolicy: Popup.CloseOnEscape
modal: true
implicitWidth: 300
implicitHeight: 130
signal save()
signal discard()
contentItem: Item {
Text {
text: qsTr("Current composition has unsaved changes.")
color: StudioTheme.Values.themeTextColor
}
HelperWidgets.Button {
width: 60
anchors.bottom: parent.bottom
text: qsTr("Cancel")
onClicked: root.reject()
}
Row {
anchors.right: parent.right
anchors.bottom: parent.bottom
spacing: 2
HelperWidgets.Button {
width: 50
text: qsTr("Save")
onClicked: {
let name = EffectMakerBackend.effectMakerModel.currentComposition
if (name !== "")
EffectMakerBackend.effectMakerModel.saveComposition(name)
root.save()
root.accept()
}
}
HelperWidgets.Button {
width: 110
text: qsTr("Discard Changes")
onClicked: {
root.discard()
root.accept()
}
}
}
}
}

View File

@@ -19,5 +19,19 @@ Row {
actionIndicatorVisible: false actionIndicatorVisible: false
onAbsoluteFilePathChanged: uniformValue = absoluteFilePath onAbsoluteFilePathChanged: uniformValue = absoluteFilePath
function defaultAsString() {
let urlStr = uniformDefaultValue.toString()
urlStr = urlStr.replace(/^(file:\/{3})/, "")
// Prepend slash if there is no drive letter
if (urlStr.length > 1 && urlStr[1] !== ':')
urlStr = '/' + urlStr;
return urlStr
}
defaultItems: [uniformDefaultValue.split('/').pop()]
defaultPaths: [defaultAsString(uniformDefaultValue)]
} }
} }

View File

@@ -166,7 +166,7 @@ Rectangle {
} }
} }
} }
/*
HelperWidgets.Section { HelperWidgets.Section {
id: predefinedSection id: predefinedSection
caption: qsTr("Predefined Categories") caption: qsTr("Predefined Categories")
@@ -412,6 +412,7 @@ Rectangle {
} }
} }
} }
*/
} }
} }
} }

View File

@@ -183,8 +183,9 @@ SecondColumnLayout {
property QtObject loaderItem: loader.item property QtObject loaderItem: loader.item
property string gradientPropertyName property string gradientPropertyName
keepOpen: loader.item?.eyeDropperActive ?? false
width: 260 width: 260
maximumHeight: Screen.desktopAvailableHeight * 0.7
function commitToGradient() { function commitToGradient() {
if (!loader.active) if (!loader.active)

View File

@@ -13,6 +13,8 @@ import QtQuickDesignerColorPalette
Column { Column {
id: root id: root
property bool eyeDropperActive: ColorPaletteBackend.eyeDropperActive
property bool supportGradient: false property bool supportGradient: false
property bool shapeGradients: false property bool shapeGradients: false
property alias gradientLine: gradientLine property alias gradientLine: gradientLine
@@ -552,9 +554,9 @@ Column {
textRole: "text" textRole: "text"
valueRole: "value" valueRole: "value"
model: [ model: [
{ value: ColorPicker.Mode.HSVA, text: "HSVA" }, { value: StudioControls.ColorPicker.Mode.HSVA, text: "HSVA" },
{ value: ColorPicker.Mode.RGBA, text: "RGBA" }, { value: StudioControls.ColorPicker.Mode.RGBA, text: "RGBA" },
{ value: ColorPicker.Mode.HSLA, text: "HSLA" } { value: StudioControls.ColorPicker.Mode.HSLA, text: "HSLA" }
] ]
onActivated: colorPicker.mode = colorMode.currentValue onActivated: colorPicker.mode = colorMode.currentValue
@@ -597,7 +599,7 @@ Column {
Row { Row {
id: rgbaRow id: rgbaRow
visible: colorPicker.mode === ColorPicker.Mode.RGBA visible: colorPicker.mode === StudioControls.ColorPicker.Mode.RGBA
spacing: StudioTheme.Values.controlGap spacing: StudioTheme.Values.controlGap
DoubleSpinBox { DoubleSpinBox {
@@ -683,7 +685,7 @@ Column {
Row { Row {
id: hslaRow id: hslaRow
visible: colorPicker.mode === ColorPicker.Mode.HSLA visible: colorPicker.mode === StudioControls.ColorPicker.Mode.HSLA
spacing: StudioTheme.Values.controlGap spacing: StudioTheme.Values.controlGap
DoubleSpinBox { DoubleSpinBox {
@@ -749,7 +751,7 @@ Column {
Row { Row {
id: hsvaRow id: hsvaRow
visible: colorPicker.mode === ColorPicker.Mode.HSVA visible: colorPicker.mode === StudioControls.ColorPicker.Mode.HSVA
spacing: StudioTheme.Values.controlGap spacing: StudioTheme.Values.controlGap
DoubleSpinBox { DoubleSpinBox {

View File

@@ -1,9 +1,9 @@
// Copyright (C) 2020 The Qt Company Ltd. // Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick 2.15 import QtQuick
import QtQuick.Controls 2.15 import QtQuick.Controls.Basic
import StudioTheme 1.0 as StudioTheme import StudioTheme as StudioTheme
ScrollBar { ScrollBar {
id: scrollBar id: scrollBar
@@ -13,9 +13,10 @@ ScrollBar {
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding) implicitContentHeight + topPadding + bottomPadding)
property bool scrollBarVisible: parent.childrenRect.width > parent.width property bool scrollBarVisible: parent.childrenRect.width > parent.width
minimumSize: orientation == Qt.Horizontal ? height / width : width / height
orientation: Qt.Horizontal orientation: Qt.Horizontal
policy: scrollBar.scrollBarVisible ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff policy: scrollBar.scrollBarVisible ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff
x: 0 x: 0
@@ -24,8 +25,6 @@ ScrollBar {
- (parent.bothVisible ? parent.verticalThickness : 0) - (parent.bothVisible ? parent.verticalThickness : 0)
padding: 0 padding: 0
minimumSize: orientation == Qt.Horizontal ? height / width : width / height
background: Rectangle { background: Rectangle {
color: StudioTheme.Values.themeScrollBarTrack color: StudioTheme.Values.themeScrollBarTrack
} }

View File

@@ -23,6 +23,7 @@ Section {
} }
SectionLayout { SectionLayout {
/*
PropertyLabel { text: qsTr("Category") } PropertyLabel { text: qsTr("Category") }
SecondColumnLayout { SecondColumnLayout {
@@ -84,7 +85,7 @@ Section {
ExpandingSpacer {} ExpandingSpacer {}
} }
*/
PropertyLabel { PropertyLabel {
text: qsTr("Object name") text: qsTr("Object name")
tooltip: qsTr("Sets the object name of the component.") tooltip: qsTr("Sets the object name of the component.")

View File

@@ -1,13 +1,13 @@
// Copyright (C) 2022 The Qt Company Ltd. // Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick 2.15 import QtQuick
import QtQuick.Layouts 1.15 import QtQuick.Layouts
import QtQuick.Controls 2.15 import QtQuick.Controls
import HelperWidgets 2.0 import HelperWidgets
import StudioControls 1.0 as StudioControls import StudioControls as StudioControls
import StudioTheme 1.0 as StudioTheme import StudioTheme as StudioTheme
import QtQuickDesignerTheme 1.0 import QtQuickDesignerTheme
Row { Row {
id: root id: root
@@ -22,6 +22,10 @@ Row {
// by QtQuick3D to add built-in primitives to the model. // by QtQuick3D to add built-in primitives to the model.
property var defaultItems property var defaultItems
// These paths will be used for default items if they are defined. Otherwise, default item
// itself is used as the path.
property var defaultPaths
// Current item // Current item
property string absoluteFilePath: "" property string absoluteFilePath: ""
@@ -40,6 +44,96 @@ Row {
backendValue: root.backendValue backendValue: root.backendValue
} }
component ThumbnailToolTip: ToolTip {
id: toolTip
property alias checkerVisible: checker.visible
property alias thumbnailSource: thumbnail.source
property alias titleText: title.text
property alias descriptionText: description.text
property int maximumWidth: 420
delay: StudioTheme.Values.toolTipDelay
background: Rectangle {
color: StudioTheme.Values.themeToolTipBackground
border.color: StudioTheme.Values.themeToolTipOutline
border.width: StudioTheme.Values.border
}
contentItem: Row {
id: row
readonly property real __epsilon: 2
height: Math.max(wrapper.visible ? wrapper.height : 0, column.height)
spacing: 10
Item {
id: wrapper
visible: thumbnail.status === Image.Ready
width: 96
height: 96
Image {
id: checker
anchors.fill: parent
fillMode: Image.Tile
source: "images/checkers.png"
}
Image {
id: thumbnail
anchors.fill: parent
sourceSize.width: wrapper.width
sourceSize.height: wrapper.height
asynchronous: true
fillMode: Image.PreserveAspectFit
}
}
Column {
id: column
property int thumbnailSize: wrapper.visible ? wrapper.width + row.spacing : 0
spacing: 10
anchors.verticalCenter: parent.verticalCenter
width: Math.min(toolTip.maximumWidth - column.thumbnailSize,
Math.max(titleTextMetrics.width + row.__epsilon,
descriptionTextMetrics.width + row.__epsilon))
Text {
id: title
font: toolTip.font
color: StudioTheme.Values.themeToolTipText
TextMetrics {
id: titleTextMetrics
text: title.text
font: title.font
}
}
Text {
id: description
width: column.width
font: toolTip.font
color: StudioTheme.Values.themeToolTipText
wrapMode: Text.Wrap
TextMetrics {
id: descriptionTextMetrics
text: description.text
font: description.font
}
}
}
}
}
StudioControls.FilterComboBox { StudioControls.FilterComboBox {
id: comboBox id: comboBox
@@ -86,41 +180,14 @@ Row {
} }
} }
ToolTip { ThumbnailToolTip {
id: toolTip id: rootToolTip
visible: comboBox.hover && toolTip.text !== ""
visible: comboBox.hover && rootToolTip.text !== ""
text: root.backendValue?.valueToString ?? "" text: root.backendValue?.valueToString ?? ""
delay: StudioTheme.Values.toolTipDelay
background: Rectangle { checkerVisible: !root.isMesh(root.absoluteFilePath)
color: StudioTheme.Values.themeToolTipBackground thumbnailSource: {
border.color: StudioTheme.Values.themeToolTipOutline
border.width: StudioTheme.Values.border
}
contentItem: RowLayout {
spacing: 10
Item {
visible: thumbnail.status === Image.Ready
Layout.preferredWidth: 96
Layout.preferredHeight: 96
Image {
id: checker
visible: !root.isMesh(root.absoluteFilePath)
anchors.fill: parent
fillMode: Image.Tile
source: "images/checkers.png"
}
Image {
id: thumbnail
asynchronous: true
height: 96
width: 96
fillMode: Image.PreserveAspectFit
source: {
if (root.isBuiltInPrimitive(root.absoluteFilePath)) if (root.isBuiltInPrimitive(root.absoluteFilePath))
return "image://qmldesigner_thumbnails/" return "image://qmldesigner_thumbnails/"
+ root.absoluteFilePath.substring(1, root.absoluteFilePath.length) + root.absoluteFilePath.substring(1, root.absoluteFilePath.length)
@@ -131,26 +198,10 @@ Row {
return root.absoluteFilePath return root.absoluteFilePath
} }
} titleText: root.fileName(rootToolTip.text)
} descriptionText: root.isBuiltInPrimitive(rootToolTip.text)
? qsTr("Built-in primitive")
ColumnLayout { : rootToolTip.text
Text {
text: root.fileName(toolTip.text)
color: StudioTheme.Values.themeToolTipText
font: toolTip.font
}
Text {
Layout.fillWidth: true
text: root.isBuiltInPrimitive(toolTip.text) ? qsTr("Built-in primitive")
: toolTip.text
font: toolTip.font
color: StudioTheme.Values.themeToolTipText
wrapMode: Text.WordWrap
}
}
}
} }
delegate: ItemDelegate { delegate: ItemDelegate {
@@ -213,43 +264,14 @@ Row {
} }
} }
ToolTip { ThumbnailToolTip {
id: delegateToolTip id: delegateToolTip
visible: delegateRoot.hovered visible: delegateRoot.hovered
text: delegateRoot.relativeFilePath text: delegateRoot.relativeFilePath
delay: StudioTheme.Values.toolTipDelay
background: Rectangle { checkerVisible: !root.isMesh(delegateRoot.absoluteFilePath)
color: StudioTheme.Values.themeToolTipBackground thumbnailSource: {
border.color: StudioTheme.Values.themeToolTipOutline
border.width: StudioTheme.Values.border
}
contentItem: RowLayout {
spacing: 10
Item {
visible: delegateThumbnail.status === Image.Ready
Layout.preferredWidth: 96
Layout.preferredHeight: 96
Image {
id: delegateChecker
visible: !root.isMesh(delegateRoot.absoluteFilePath)
anchors.fill: parent
fillMode: Image.Tile
source: "images/checkers.png"
}
Image {
id: delegateThumbnail
asynchronous: true
sourceSize.height: 96
sourceSize.width: 96
height: 96
width: 96
fillMode: Image.PreserveAspectFit
source: {
if (root.isBuiltInPrimitive(delegateRoot.name)) if (root.isBuiltInPrimitive(delegateRoot.name))
return "image://qmldesigner_thumbnails/" return "image://qmldesigner_thumbnails/"
+ delegateRoot.name.substring(1, delegateRoot.name.length) + delegateRoot.name.substring(1, delegateRoot.name.length)
@@ -257,27 +279,10 @@ Row {
return "image://qmldesigner_thumbnails/" + delegateRoot.absoluteFilePath return "image://qmldesigner_thumbnails/" + delegateRoot.absoluteFilePath
} }
} titleText: delegateRoot.name
} descriptionText: root.isBuiltInPrimitive(delegateToolTip.text)
ColumnLayout {
Text {
text: delegateRoot.name
color: StudioTheme.Values.themeToolTipText
font: delegateToolTip.font
}
Text {
Layout.fillWidth: true
text: root.isBuiltInPrimitive(delegateToolTip.text)
? qsTr("Built-in primitive") ? qsTr("Built-in primitive")
: delegateToolTip.text : delegateToolTip.text
font: delegateToolTip.font
color: StudioTheme.Values.themeToolTipText
wrapMode: Text.WordWrap
}
}
}
} }
} }
@@ -422,8 +427,10 @@ Row {
if (root.defaultItems !== undefined) { if (root.defaultItems !== undefined) {
for (var i = 0; i < root.defaultItems.length; ++i) { for (var i = 0; i < root.defaultItems.length; ++i) {
comboBox.listModel.append({ comboBox.listModel.append({
absoluteFilePath: "", absoluteFilePath: root.defaultPaths ? root.defaultPaths[i]
relativeFilePath: root.defaultItems[i], : "",
relativeFilePath: root.defaultPaths ? root.defaultPaths[i]
: root.defaultItems[i],
name: root.defaultItems[i], name: root.defaultItems[i],
group: 0 group: 0
}) })
@@ -454,6 +461,7 @@ Row {
} }
onDefaultItemsChanged: root.createModel() onDefaultItemsChanged: root.createModel()
onDefaultPathsChanged: root.createModel()
Component.onCompleted: { Component.onCompleted: {
root.createModel() root.createModel()

View File

@@ -1,9 +1,9 @@
// Copyright (C) 2020 The Qt Company Ltd. // Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick 2.15 import QtQuick
import QtQuick.Controls 2.15 import QtQuick.Controls.Basic
import StudioTheme 1.0 as StudioTheme import StudioTheme as StudioTheme
ScrollBar { ScrollBar {
id: scrollBar id: scrollBar

View File

@@ -25,19 +25,21 @@ QtObject {
property alias flags: window.flags property alias flags: window.flags
property alias visible: window.visible property alias visible: window.visible
property int anchorGap: 10
property int edge: Qt.LeftEdge property int edge: Qt.LeftEdge
property int actualEdge: root.edge property int actualEdge: root.edge
property alias chevronVisible: chevron.visible //property alias chevronVisible: chevron.visible
property rect __itemGlobal: Qt.rect(0, 0, 100, 100) property rect __itemGlobal: Qt.rect(0, 0, 100, 100)
property bool keepOpen: false
signal closing(close: var) signal closing(close: var)
function showGlobal() function showGlobal() {
{ var pos = WindowManager.globalCursorPosition()
var pos = WindowManager.globalCursorPosition();
root.__itemGlobal = Qt.rect(pos.x, pos.y, 300, 20) root.__itemGlobal = Qt.rect(pos.x, pos.y, 300, 20)
root.chevronVisible = false //root.chevronVisible = false
root.layout() root.layout()
window.show() window.show()
window.raise() window.raise()
@@ -46,7 +48,7 @@ QtObject {
function show(target: Item) { function show(target: Item) {
var originGlobal = target.mapToGlobal(0, 0) var originGlobal = target.mapToGlobal(0, 0)
root.__itemGlobal = Qt.rect(originGlobal.x, originGlobal.y, target.width, target.height) root.__itemGlobal = Qt.rect(originGlobal.x, originGlobal.y, target.width, target.height)
root.chevronVisible = true //root.chevronVisible = true
root.layout() root.layout()
window.show() window.show()
window.raise() window.raise()
@@ -57,11 +59,8 @@ QtObject {
} }
function layout() { function layout() {
// Setup let position = Qt.point(root.__itemGlobal.x, root.__itemGlobal.y)
var screen = Qt.rect(0, //Screen.virtualX, // TODO var screen = WindowManager.getScreenGeometry(position)
0, //Screen.virtualY, // TODO
Screen.desktopAvailableWidth,
Screen.desktopAvailableHeight)
// Collect region information // Collect region information
let edges = window.getRegions(screen, root.__itemGlobal) let edges = window.getRegions(screen, root.__itemGlobal)
@@ -78,8 +77,8 @@ QtObject {
let anchor = edges[edge].anchor let anchor = edges[edge].anchor
let popoverRect = window.popoverGeometry(edge, anchor, edges[edge].region) let popoverRect = window.popoverGeometry(edge, anchor, edges[edge].region)
if (chevron.visible) //if (chevron.visible)
chevron.layout(edge, popoverRect, anchor) // chevron.layout(edge, popoverRect, anchor)
window.x = popoverRect.x window.x = popoverRect.x
window.y = popoverRect.y window.y = popoverRect.y
@@ -88,7 +87,7 @@ QtObject {
property Window window: Window { property Window window: Window {
id: window id: window
property int margin: 20 property int margin: 0 //20
width: root.width + (2 * window.margin) width: root.width + (2 * window.margin)
height: root.height + (2 * window.margin) height: root.height + (2 * window.margin)
@@ -139,7 +138,7 @@ QtObject {
return Qt.LeftEdge // Default return Qt.LeftEdge // Default
} }
function contains(a: rect, b: rect): boolean { function contains(a: rect, b: rect): bool {
let halfSizeA = Qt.size(a.width * 0.5, a.height * 0.5) let halfSizeA = Qt.size(a.width * 0.5, a.height * 0.5)
let halfSizeB = Qt.size(b.width * 0.5, b.height * 0.5) let halfSizeB = Qt.size(b.width * 0.5, b.height * 0.5)
@@ -165,9 +164,18 @@ QtObject {
var targetCenter = Qt.point(target.x + (target.width * 0.5), var targetCenter = Qt.point(target.x + (target.width * 0.5),
target.y + (target.height * 0.5)) target.y + (target.height * 0.5))
// Just as a reminder why calculating custom right and bottom:
// > Note that for historical reasons this function returns top() + height() - 1;
// > use y() + height() to retrieve the true y-coordinate.
let sourceRight = source.x + source.width
let sourceBottom = source.y + source.height
// TOP // TOP
let topAnchor = Qt.point(targetCenter.x, target.y) let topAnchor = Qt.point(targetCenter.x, target.y - root.anchorGap)
let topRegion = Qt.rect(source.x, source.y, source.width, Math.max(0, topAnchor.y)) let topRegion = Qt.rect(source.x,
source.y,
source.width,
(topAnchor.y < source.top) ? 0 : Math.abs(topAnchor.y - source.top))
edges[Qt.TopEdge] = { edges[Qt.TopEdge] = {
anchor: topAnchor, anchor: topAnchor,
@@ -177,10 +185,10 @@ QtObject {
} }
// RIGHT // RIGHT
let rightAnchor = Qt.point(target.x + target.width, targetCenter.y) let rightAnchor = Qt.point(target.x + target.width + root.anchorGap, targetCenter.y)
let rightRegion = Qt.rect(rightAnchor.x, let rightRegion = Qt.rect(rightAnchor.x,
source.y, source.y,
Math.max(0, source.width - rightAnchor.x), (rightAnchor.x > sourceRight) ? 0 : Math.abs(sourceRight - rightAnchor.x),
source.height) source.height)
edges[Qt.RightEdge] = { edges[Qt.RightEdge] = {
@@ -191,11 +199,11 @@ QtObject {
} }
// BOTTOM // BOTTOM
let bottomAnchor = Qt.point(targetCenter.x, target.y + target.height) let bottomAnchor = Qt.point(targetCenter.x, target.y + target.height + root.anchorGap)
let bottomRegion = Qt.rect(source.x, let bottomRegion = Qt.rect(source.x,
bottomAnchor.y, bottomAnchor.y,
source.width, source.width,
Math.max(0, source.height - bottomAnchor.y)) (bottomAnchor.y > sourceBottom) ? 0 : Math.abs(sourceBottom - bottomAnchor.y))
edges[Qt.BottomEdge] = { edges[Qt.BottomEdge] = {
anchor: bottomAnchor, anchor: bottomAnchor,
@@ -205,8 +213,11 @@ QtObject {
} }
// LEFT // LEFT
let leftAnchor = Qt.point(target.x, targetCenter.y) let leftAnchor = Qt.point(target.x - root.anchorGap, targetCenter.y)
let leftRegion = Qt.rect(source.x, source.y, Math.max(0, leftAnchor.x), source.height) let leftRegion = Qt.rect(source.x,
source.y,
(leftAnchor.x < source.left) ? 0 : Math.abs(leftAnchor.x - source.left),
source.height)
edges[Qt.LeftEdge] = { edges[Qt.LeftEdge] = {
anchor: leftAnchor, anchor: leftAnchor,
@@ -221,7 +232,9 @@ QtObject {
function popoverGeometry(edge: int, anchor: point, region: rect) { function popoverGeometry(edge: int, anchor: point, region: rect) {
if (edge === Qt.TopEdge) { if (edge === Qt.TopEdge) {
let height = Math.min(window.height, region.height) let height = Math.min(window.height, region.height)
return Qt.rect(Math.max(0, Math.min(anchor.x - (window.width * 0.5), region.width - window.width)), return Qt.rect(Math.max(region.x,
Math.min(anchor.x - (window.width * 0.5),
region.x + region.width - window.width)),
anchor.y - height, anchor.y - height,
Math.min(window.width, region.width), Math.min(window.width, region.width),
height) height)
@@ -230,14 +243,18 @@ QtObject {
if (edge === Qt.RightEdge) { if (edge === Qt.RightEdge) {
let width = Math.min(window.width, region.width) let width = Math.min(window.width, region.width)
return Qt.rect(anchor.x, return Qt.rect(anchor.x,
Math.max(0, Math.min(anchor.y - (window.height * 0.5), region.height - window.height)), Math.max(region.y,
Math.min(anchor.y - (window.height * 0.5),
region.y + region.height - window.height)),
width, width,
Math.min(window.height, region.height)) Math.min(window.height, region.height))
} }
if (edge === Qt.BottomEdge) { if (edge === Qt.BottomEdge) {
let height = Math.min(window.height, region.height) let height = Math.min(window.height, region.height)
return Qt.rect(Math.max(0, Math.min(anchor.x - (window.width * 0.5), region.width - window.width)), return Qt.rect(Math.max(region.x,
Math.min(anchor.x - (window.width * 0.5),
region.x + region.width - window.width)),
anchor.y, anchor.y,
Math.min(window.width, region.width), Math.min(window.width, region.width),
height) height)
@@ -246,7 +263,9 @@ QtObject {
if (edge === Qt.LeftEdge) { if (edge === Qt.LeftEdge) {
let width = Math.min(window.width, region.width) let width = Math.min(window.width, region.width)
return Qt.rect(anchor.x - width, return Qt.rect(anchor.x - width,
Math.max(0, Math.min(anchor.y - (window.height * 0.5), region.height - window.height)), Math.max(region.y,
Math.min(anchor.y - (window.height * 0.5),
region.y + region.height - window.height)),
width, width,
Math.min(window.height, region.height)) Math.min(window.height, region.height))
} }
@@ -270,6 +289,9 @@ QtObject {
if (!focusWindow) if (!focusWindow)
return return
if (root.keepOpen)
return
if (focusWindow !== window && focusWindow.transientParent !== window) if (focusWindow !== window && focusWindow.transientParent !== window)
root.close() root.close()
} }
@@ -304,7 +326,10 @@ QtObject {
} }
} }
} }
/*
// The chevron will be reactivated when we fixed all the issues that where found during testing.
// * Potential Qt bug: black background instead of transparent border due to GPU selection on Windows
// * Ghost chevron on macOS after dragging the window
Shape { Shape {
id: chevron id: chevron
@@ -381,7 +406,7 @@ QtObject {
PathLine { id: end; x: 0; y: 0 } PathLine { id: end; x: 0; y: 0 }
} }
} }
*/
Column { Column {
id: column id: column
anchors.fill: parent anchors.fill: parent
@@ -392,6 +417,17 @@ QtObject {
width: parent.width width: parent.width
height: StudioTheme.Values.titleBarHeight height: StudioTheme.Values.titleBarHeight
DragHandler {
id: dragHandler
target: null
grabPermissions: PointerHandler.CanTakeOverFromAnything
onActiveChanged: {
if (dragHandler.active)
window.startSystemMove() // QTBUG-102488
}
}
Row { Row {
id: row id: row
anchors.fill: parent anchors.fill: parent

View File

@@ -34,7 +34,7 @@ Item {
SectionLabel { SectionLabel {
id: arrow id: arrow
style: control.style controlStyle: control.style
width: control.style.smallIconSize.width width: control.style.smallIconSize.width
height: control.style.smallIconSize.height height: control.style.smallIconSize.height
text: StudioTheme.Constants.sectionToggle text: StudioTheme.Constants.sectionToggle
@@ -56,7 +56,7 @@ Item {
SectionLabel { SectionLabel {
id: label id: label
style: control.style controlStyle: control.style
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: control.style.text.idle color: control.style.text.idle
x: 22 x: 22

View File

@@ -9,11 +9,11 @@ import StudioTheme 1.0 as StudioTheme
T.Label { T.Label {
id: control id: control
property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle property StudioTheme.ControlStyle controlStyle: StudioTheme.Values.controlStyle
width: Math.max(Math.min(240, parent.width - 220), 90) width: Math.max(Math.min(240, parent.width - 220), 90)
color: control.style.text.idle color: control.controlStyle.text.idle
font.pixelSize: control.style.baseFontSize font.pixelSize: control.controlStyle.baseFontSize
elide: Text.ElideRight elide: Text.ElideRight
Layout.preferredWidth: width Layout.preferredWidth: width

View File

@@ -79,300 +79,304 @@ QtObject {
readonly property string centerHorizontal: "\u0060" readonly property string centerHorizontal: "\u0060"
readonly property string centerVertical: "\u0061" readonly property string centerVertical: "\u0061"
readonly property string cleanLogs_medium: "\u0062" readonly property string cleanLogs_medium: "\u0062"
readonly property string closeCross: "\u0063" readonly property string clearList_large: "\u0063"
readonly property string closeFile_large: "\u0064" readonly property string clearList_medium: "\u0064"
readonly property string closeLink: "\u0065" readonly property string closeCross: "\u0065"
readonly property string close_small: "\u0066" readonly property string closeFile_large: "\u0066"
readonly property string code: "\u0067" readonly property string closeLink: "\u0067"
readonly property string codeEditor_medium: "\u0068" readonly property string close_small: "\u0068"
readonly property string codeview_medium: "\u0069" readonly property string code: "\u0069"
readonly property string colorPopupClose: "\u006A" readonly property string codeEditor_medium: "\u006A"
readonly property string colorSelection_medium: "\u006B" readonly property string codeview_medium: "\u006B"
readonly property string columnsAndRows: "\u006C" readonly property string colorPopupClose: "\u006C"
readonly property string comboBox_medium: "\u006D" readonly property string colorSelection_medium: "\u006D"
readonly property string cone_medium: "\u006E" readonly property string columnsAndRows: "\u006E"
readonly property string cone_small: "\u006F" readonly property string comboBox_medium: "\u006F"
readonly property string connection_small: "\u0070" readonly property string cone_medium: "\u0070"
readonly property string connections_medium: "\u0071" readonly property string cone_small: "\u0071"
readonly property string copyLink: "\u0072" readonly property string connection_small: "\u0072"
readonly property string copyStyle: "\u0073" readonly property string connections_medium: "\u0073"
readonly property string copy_small: "\u0074" readonly property string copyLink: "\u0074"
readonly property string cornerA: "\u0075" readonly property string copyStyle: "\u0075"
readonly property string cornerB: "\u0076" readonly property string copy_small: "\u0076"
readonly property string cornersAll: "\u0077" readonly property string cornerA: "\u0077"
readonly property string createComponent_large: "\u0078" readonly property string cornerB: "\u0078"
readonly property string createComponent_small: "\u0079" readonly property string cornersAll: "\u0079"
readonly property string createObject_medium: "\u007A" readonly property string createComponent_large: "\u007A"
readonly property string create_medium: "\u007B" readonly property string createComponent_small: "\u007B"
readonly property string create_small: "\u007C" readonly property string createObject_medium: "\u007C"
readonly property string cube_medium: "\u007D" readonly property string create_medium: "\u007D"
readonly property string cube_small: "\u007E" readonly property string create_small: "\u007E"
readonly property string curveDesigner: "\u007F" readonly property string cube_medium: "\u007F"
readonly property string curveDesigner_medium: "\u0080" readonly property string cube_small: "\u0080"
readonly property string curveEditor: "\u0081" readonly property string curveDesigner: "\u0081"
readonly property string customMaterialEditor: "\u0082" readonly property string curveDesigner_medium: "\u0082"
readonly property string cylinder_medium: "\u0083" readonly property string curveEditor: "\u0083"
readonly property string cylinder_small: "\u0084" readonly property string customMaterialEditor: "\u0084"
readonly property string decisionNode: "\u0085" readonly property string cylinder_medium: "\u0085"
readonly property string deleteColumn: "\u0086" readonly property string cylinder_small: "\u0086"
readonly property string deleteMaterial: "\u0087" readonly property string decisionNode: "\u0087"
readonly property string deleteRow: "\u0088" readonly property string deleteColumn: "\u0088"
readonly property string deleteTable: "\u0089" readonly property string deleteMaterial: "\u0089"
readonly property string delete_medium: "\u008A" readonly property string deleteRow: "\u008A"
readonly property string delete_small: "\u008B" readonly property string deleteTable: "\u008B"
readonly property string deletecolumn_medium: "\u008C" readonly property string delete_medium: "\u008C"
readonly property string deletepermanently_medium: "\u008D" readonly property string delete_small: "\u008D"
readonly property string deleterow_medium: "\u008E" readonly property string deletecolumn_medium: "\u008E"
readonly property string designMode_large: "\u008F" readonly property string deletepermanently_medium: "\u008F"
readonly property string detach: "\u0090" readonly property string deleterow_medium: "\u0090"
readonly property string directionalLight_small: "\u0091" readonly property string designMode_large: "\u0091"
readonly property string distributeBottom: "\u0092" readonly property string detach: "\u0092"
readonly property string distributeCenterHorizontal: "\u0093" readonly property string directionalLight_small: "\u0093"
readonly property string distributeCenterVertical: "\u0094" readonly property string distributeBottom: "\u0094"
readonly property string distributeLeft: "\u0095" readonly property string distributeCenterHorizontal: "\u0095"
readonly property string distributeOriginBottomRight: "\u0096" readonly property string distributeCenterVertical: "\u0096"
readonly property string distributeOriginCenter: "\u0097" readonly property string distributeLeft: "\u0097"
readonly property string distributeOriginNone: "\u0098" readonly property string distributeOriginBottomRight: "\u0098"
readonly property string distributeOriginTopLeft: "\u0099" readonly property string distributeOriginCenter: "\u0099"
readonly property string distributeRight: "\u009A" readonly property string distributeOriginNone: "\u009A"
readonly property string distributeSpacingHorizontal: "\u009B" readonly property string distributeOriginTopLeft: "\u009B"
readonly property string distributeSpacingVertical: "\u009D" readonly property string distributeRight: "\u009D"
readonly property string distributeTop: "\u009E" readonly property string distributeSpacingHorizontal: "\u009E"
readonly property string download: "\u009F" readonly property string distributeSpacingVertical: "\u009F"
readonly property string downloadUnavailable: "\u00A0" readonly property string distributeTop: "\u00A0"
readonly property string downloadUpdate: "\u00A1" readonly property string download: "\u00A1"
readonly property string downloaded: "\u00A2" readonly property string downloadUnavailable: "\u00A2"
readonly property string dragmarks: "\u00A3" readonly property string downloadUpdate: "\u00A3"
readonly property string duplicate_small: "\u00A4" readonly property string downloaded: "\u00A4"
readonly property string edit: "\u00A5" readonly property string dragmarks: "\u00A5"
readonly property string editComponent_large: "\u00A6" readonly property string duplicate_small: "\u00A6"
readonly property string editComponent_small: "\u00A7" readonly property string edit: "\u00A7"
readonly property string editLightOff_medium: "\u00A8" readonly property string editComponent_large: "\u00A8"
readonly property string editLightOn_medium: "\u00A9" readonly property string editComponent_small: "\u00A9"
readonly property string edit_medium: "\u00AA" readonly property string editLightOff_medium: "\u00AA"
readonly property string edit_small: "\u00AB" readonly property string editLightOn_medium: "\u00AB"
readonly property string effects: "\u00AC" readonly property string edit_medium: "\u00AC"
readonly property string events_small: "\u00AE" readonly property string edit_small: "\u00AE"
readonly property string export_medium: "\u00AF" readonly property string effects: "\u00AF"
readonly property string eyeDropper: "\u00B0" readonly property string events_small: "\u00B0"
readonly property string favorite: "\u00B1" readonly property string export_medium: "\u00B1"
readonly property string fitAll_medium: "\u00B2" readonly property string eyeDropper: "\u00B2"
readonly property string fitSelected_small: "\u00B3" readonly property string favorite: "\u00B3"
readonly property string fitSelection_medium: "\u00B4" readonly property string fitAll_medium: "\u00B4"
readonly property string fitToView_medium: "\u00B5" readonly property string fitSelected_small: "\u00B5"
readonly property string flowAction: "\u00B6" readonly property string fitSelection_medium: "\u00B6"
readonly property string flowTransition: "\u00B7" readonly property string fitToView_medium: "\u00B7"
readonly property string fontStyleBold: "\u00B8" readonly property string flowAction: "\u00B8"
readonly property string fontStyleItalic: "\u00B9" readonly property string flowTransition: "\u00B9"
readonly property string fontStyleStrikethrough: "\u00BA" readonly property string fontStyleBold: "\u00BA"
readonly property string fontStyleUnderline: "\u00BB" readonly property string fontStyleItalic: "\u00BB"
readonly property string forward_medium: "\u00BC" readonly property string fontStyleStrikethrough: "\u00BC"
readonly property string globalOrient_medium: "\u00BD" readonly property string fontStyleUnderline: "\u00BD"
readonly property string gradient: "\u00BE" readonly property string forward_medium: "\u00BE"
readonly property string gridView: "\u00BF" readonly property string globalOrient_medium: "\u00BF"
readonly property string grid_medium: "\u00C0" readonly property string gradient: "\u00C0"
readonly property string group_small: "\u00C1" readonly property string gridView: "\u00C1"
readonly property string help: "\u00C2" readonly property string grid_medium: "\u00C2"
readonly property string home_large: "\u00C3" readonly property string group_small: "\u00C3"
readonly property string idAliasOff: "\u00C4" readonly property string help: "\u00C4"
readonly property string idAliasOn: "\u00C5" readonly property string home_large: "\u00C5"
readonly property string import_medium: "\u00C6" readonly property string idAliasOff: "\u00C6"
readonly property string imported: "\u00C7" readonly property string idAliasOn: "\u00C7"
readonly property string importedModels_small: "\u00C8" readonly property string import_medium: "\u00C8"
readonly property string infinity: "\u00C9" readonly property string imported: "\u00C9"
readonly property string invisible_medium: "\u00CA" readonly property string importedModels_small: "\u00CA"
readonly property string invisible_small: "\u00CB" readonly property string infinity: "\u00CB"
readonly property string jumpToCode_medium: "\u00CC" readonly property string invisible_medium: "\u00CC"
readonly property string jumpToCode_small: "\u00CD" readonly property string invisible_small: "\u00CD"
readonly property string keyframe: "\u00CE" readonly property string jumpToCode_medium: "\u00CE"
readonly property string languageList_medium: "\u00CF" readonly property string jumpToCode_small: "\u00CF"
readonly property string layouts_small: "\u00D0" readonly property string keyframe: "\u00D0"
readonly property string lights_small: "\u00D1" readonly property string languageList_medium: "\u00D1"
readonly property string linear_medium: "\u00D2" readonly property string layouts_small: "\u00D2"
readonly property string linkTriangle: "\u00D3" readonly property string lights_small: "\u00D3"
readonly property string linked: "\u00D4" readonly property string linear_medium: "\u00D4"
readonly property string listView: "\u00D5" readonly property string linkTriangle: "\u00D5"
readonly property string listView_medium: "\u00D6" readonly property string linked: "\u00D6"
readonly property string list_medium: "\u00D7" readonly property string listView: "\u00D7"
readonly property string localOrient_medium: "\u00D8" readonly property string listView_medium: "\u00D8"
readonly property string lockOff: "\u00D9" readonly property string list_medium: "\u00D9"
readonly property string lockOn: "\u00DA" readonly property string localOrient_medium: "\u00DA"
readonly property string loopPlayback_medium: "\u00DB" readonly property string lockOff: "\u00DB"
readonly property string materialBrowser_medium: "\u00DC" readonly property string lockOn: "\u00DC"
readonly property string materialPreviewEnvironment: "\u00DD" readonly property string loopPlayback_medium: "\u00DD"
readonly property string materialPreviewModel: "\u00DE" readonly property string materialBrowser_medium: "\u00DE"
readonly property string material_medium: "\u00DF" readonly property string materialPreviewEnvironment: "\u00DF"
readonly property string maxBar_small: "\u00E0" readonly property string materialPreviewModel: "\u00E0"
readonly property string mergeCells: "\u00E1" readonly property string material_medium: "\u00E1"
readonly property string merge_small: "\u00E2" readonly property string maxBar_small: "\u00E2"
readonly property string minus: "\u00E3" readonly property string mergeCells: "\u00E3"
readonly property string mirror: "\u00E4" readonly property string merge_small: "\u00E4"
readonly property string more_medium: "\u00E5" readonly property string minus: "\u00E5"
readonly property string mouseArea_small: "\u00E6" readonly property string mirror: "\u00E6"
readonly property string moveDown_medium: "\u00E7" readonly property string more_medium: "\u00E7"
readonly property string moveInwards_medium: "\u00E8" readonly property string mouseArea_small: "\u00E8"
readonly property string moveUp_medium: "\u00E9" readonly property string moveDown_medium: "\u00E9"
readonly property string moveUpwards_medium: "\u00EA" readonly property string moveInwards_medium: "\u00EA"
readonly property string move_medium: "\u00EB" readonly property string moveUp_medium: "\u00EB"
readonly property string newMaterial: "\u00EC" readonly property string moveUpwards_medium: "\u00EC"
readonly property string nextFile_large: "\u00ED" readonly property string move_medium: "\u00ED"
readonly property string normalBar_small: "\u00EE" readonly property string newMaterial: "\u00EE"
readonly property string openLink: "\u00EF" readonly property string nextFile_large: "\u00EF"
readonly property string openMaterialBrowser: "\u00F0" readonly property string normalBar_small: "\u00F0"
readonly property string orientation: "\u00F1" readonly property string openLink: "\u00F1"
readonly property string orthCam_medium: "\u00F2" readonly property string openMaterialBrowser: "\u00F2"
readonly property string orthCam_small: "\u00F3" readonly property string orientation: "\u00F3"
readonly property string paddingEdge: "\u00F4" readonly property string orthCam_medium: "\u00F4"
readonly property string paddingFrame: "\u00F5" readonly property string orthCam_small: "\u00F5"
readonly property string particleAnimation_medium: "\u00F6" readonly property string paddingEdge: "\u00F6"
readonly property string pasteStyle: "\u00F7" readonly property string paddingFrame: "\u00F7"
readonly property string paste_small: "\u00F8" readonly property string particleAnimation_medium: "\u00F8"
readonly property string pause: "\u00F9" readonly property string pasteStyle: "\u00F9"
readonly property string pause_medium: "\u00FA" readonly property string paste_small: "\u00FA"
readonly property string perspectiveCam_medium: "\u00FB" readonly property string pause: "\u00FB"
readonly property string perspectiveCam_small: "\u00FC" readonly property string pause_medium: "\u00FC"
readonly property string pin: "\u00FD" readonly property string perspectiveCam_medium: "\u00FD"
readonly property string plane_medium: "\u00FE" readonly property string perspectiveCam_small: "\u00FE"
readonly property string plane_small: "\u00FF" readonly property string pin: "\u00FF"
readonly property string play: "\u0100" readonly property string plane_medium: "\u0100"
readonly property string playFill_medium: "\u0101" readonly property string plane_small: "\u0101"
readonly property string playOutline_medium: "\u0102" readonly property string play: "\u0102"
readonly property string plus: "\u0103" readonly property string playFill_medium: "\u0103"
readonly property string pointLight_small: "\u0104" readonly property string playOutline_medium: "\u0104"
readonly property string positioners_small: "\u0105" readonly property string plus: "\u0105"
readonly property string previewEnv_medium: "\u0106" readonly property string pointLight_small: "\u0106"
readonly property string previousFile_large: "\u0107" readonly property string positioners_small: "\u0107"
readonly property string promote: "\u0108" readonly property string previewEnv_medium: "\u0108"
readonly property string properties_medium: "\u0109" readonly property string previousFile_large: "\u0109"
readonly property string readOnly: "\u010A" readonly property string promote: "\u010A"
readonly property string recent_medium: "\u010B" readonly property string properties_medium: "\u010B"
readonly property string recordFill_medium: "\u010C" readonly property string readOnly: "\u010C"
readonly property string recordOutline_medium: "\u010D" readonly property string recent_medium: "\u010D"
readonly property string redo: "\u010E" readonly property string recordFill_medium: "\u010E"
readonly property string reload_medium: "\u010F" readonly property string recordOutline_medium: "\u010F"
readonly property string remove_medium: "\u0110" readonly property string redo: "\u0110"
readonly property string remove_small: "\u0111" readonly property string reload_medium: "\u0111"
readonly property string rename_small: "\u0112" readonly property string remove_medium: "\u0112"
readonly property string replace_small: "\u0113" readonly property string remove_small: "\u0113"
readonly property string resetView_small: "\u0114" readonly property string rename_small: "\u0114"
readonly property string restartParticles_medium: "\u0115" readonly property string replace_small: "\u0115"
readonly property string reverseOrder_medium: "\u0116" readonly property string resetView_small: "\u0116"
readonly property string roatate_medium: "\u0117" readonly property string restartParticles_medium: "\u0117"
readonly property string rotationFill: "\u0118" readonly property string reverseOrder_medium: "\u0118"
readonly property string rotationOutline: "\u0119" readonly property string roatate_medium: "\u0119"
readonly property string runProjFill_large: "\u011A" readonly property string rotationFill: "\u011A"
readonly property string runProjOutline_large: "\u011B" readonly property string rotationOutline: "\u011B"
readonly property string s_anchors: "\u011C" readonly property string runProjFill_large: "\u011C"
readonly property string s_annotations: "\u011D" readonly property string runProjOutline_large: "\u011D"
readonly property string s_arrange: "\u011E" readonly property string s_anchors: "\u011E"
readonly property string s_boundingBox: "\u011F" readonly property string s_annotations: "\u011F"
readonly property string s_component: "\u0120" readonly property string s_arrange: "\u0120"
readonly property string s_connections: "\u0121" readonly property string s_boundingBox: "\u0121"
readonly property string s_edit: "\u0122" readonly property string s_component: "\u0122"
readonly property string s_enterComponent: "\u0123" readonly property string s_connections: "\u0123"
readonly property string s_eventList: "\u0124" readonly property string s_edit: "\u0124"
readonly property string s_group: "\u0125" readonly property string s_enterComponent: "\u0125"
readonly property string s_layouts: "\u0126" readonly property string s_eventList: "\u0126"
readonly property string s_merging: "\u0127" readonly property string s_group: "\u0127"
readonly property string s_mouseArea: "\u0128" readonly property string s_layouts: "\u0128"
readonly property string s_positioners: "\u0129" readonly property string s_merging: "\u0129"
readonly property string s_selection: "\u012A" readonly property string s_mouseArea: "\u012A"
readonly property string s_snapping: "\u012B" readonly property string s_positioners: "\u012B"
readonly property string s_timeline: "\u012C" readonly property string s_selection: "\u012C"
readonly property string s_visibility: "\u012D" readonly property string s_snapping: "\u012D"
readonly property string saveLogs_medium: "\u012E" readonly property string s_timeline: "\u012E"
readonly property string scale_medium: "\u012F" readonly property string s_visibility: "\u012F"
readonly property string search: "\u0130" readonly property string saveAs_medium: "\u0130"
readonly property string search_small: "\u0131" readonly property string saveLogs_medium: "\u0131"
readonly property string sectionToggle: "\u0132" readonly property string save_medium: "\u0132"
readonly property string selectFill_medium: "\u0133" readonly property string scale_medium: "\u0133"
readonly property string selectOutline_medium: "\u0134" readonly property string search: "\u0134"
readonly property string selectParent_small: "\u0135" readonly property string search_small: "\u0135"
readonly property string selection_small: "\u0136" readonly property string sectionToggle: "\u0136"
readonly property string settings_medium: "\u0137" readonly property string selectFill_medium: "\u0137"
readonly property string signal_small: "\u0138" readonly property string selectOutline_medium: "\u0138"
readonly property string snapping_conf_medium: "\u0139" readonly property string selectParent_small: "\u0139"
readonly property string snapping_medium: "\u013A" readonly property string selection_small: "\u013A"
readonly property string snapping_small: "\u013B" readonly property string settings_medium: "\u013B"
readonly property string sortascending_medium: "\u013C" readonly property string signal_small: "\u013C"
readonly property string sortdescending_medium: "\u013D" readonly property string snapping_conf_medium: "\u013D"
readonly property string sphere_medium: "\u013E" readonly property string snapping_medium: "\u013E"
readonly property string sphere_small: "\u013F" readonly property string snapping_small: "\u013F"
readonly property string splitColumns: "\u0140" readonly property string sortascending_medium: "\u0140"
readonly property string splitRows: "\u0141" readonly property string sortdescending_medium: "\u0141"
readonly property string splitScreen_medium: "\u0142" readonly property string sphere_medium: "\u0142"
readonly property string spotLight_small: "\u0143" readonly property string sphere_small: "\u0143"
readonly property string stackedContainer_small: "\u0144" readonly property string splitColumns: "\u0144"
readonly property string startNode: "\u0145" readonly property string splitRows: "\u0145"
readonly property string step_medium: "\u0146" readonly property string splitScreen_medium: "\u0146"
readonly property string stop_medium: "\u0147" readonly property string spotLight_small: "\u0147"
readonly property string tableView_medium: "\u0148" readonly property string stackedContainer_small: "\u0148"
readonly property string testIcon: "\u0149" readonly property string startNode: "\u0149"
readonly property string textAlignBottom: "\u014A" readonly property string step_medium: "\u014A"
readonly property string textAlignCenter: "\u014B" readonly property string stop_medium: "\u014B"
readonly property string textAlignJustified: "\u014C" readonly property string tableView_medium: "\u014C"
readonly property string textAlignLeft: "\u014D" readonly property string testIcon: "\u014D"
readonly property string textAlignMiddle: "\u014E" readonly property string textAlignBottom: "\u014E"
readonly property string textAlignRight: "\u014F" readonly property string textAlignCenter: "\u014F"
readonly property string textAlignTop: "\u0150" readonly property string textAlignJustified: "\u0150"
readonly property string textBulletList: "\u0151" readonly property string textAlignLeft: "\u0151"
readonly property string textFullJustification: "\u0152" readonly property string textAlignMiddle: "\u0152"
readonly property string textNumberedList: "\u0153" readonly property string textAlignRight: "\u0153"
readonly property string textures_medium: "\u0154" readonly property string textAlignTop: "\u0154"
readonly property string tickIcon: "\u0155" readonly property string textBulletList: "\u0155"
readonly property string tickMark_small: "\u0156" readonly property string textFullJustification: "\u0156"
readonly property string timeline_small: "\u0157" readonly property string textNumberedList: "\u0157"
readonly property string toEndFrame_medium: "\u0158" readonly property string textures_medium: "\u0158"
readonly property string toNextFrame_medium: "\u0159" readonly property string tickIcon: "\u0159"
readonly property string toPrevFrame_medium: "\u015A" readonly property string tickMark_small: "\u015A"
readonly property string toStartFrame_medium: "\u015B" readonly property string timeline_small: "\u015B"
readonly property string topToolbar_annotations: "\u015C" readonly property string toEndFrame_medium: "\u015C"
readonly property string topToolbar_closeFile: "\u015D" readonly property string toNextFrame_medium: "\u015D"
readonly property string topToolbar_designMode: "\u015E" readonly property string toPrevFrame_medium: "\u015E"
readonly property string topToolbar_enterComponent: "\u015F" readonly property string toStartFrame_medium: "\u015F"
readonly property string topToolbar_home: "\u0160" readonly property string topToolbar_annotations: "\u0160"
readonly property string topToolbar_makeComponent: "\u0161" readonly property string topToolbar_closeFile: "\u0161"
readonly property string topToolbar_navFile: "\u0162" readonly property string topToolbar_designMode: "\u0162"
readonly property string topToolbar_runProject: "\u0163" readonly property string topToolbar_enterComponent: "\u0163"
readonly property string translationCreateFiles: "\u0164" readonly property string topToolbar_home: "\u0164"
readonly property string translationCreateReport: "\u0165" readonly property string topToolbar_makeComponent: "\u0165"
readonly property string translationExport: "\u0166" readonly property string topToolbar_navFile: "\u0166"
readonly property string translationImport: "\u0167" readonly property string topToolbar_runProject: "\u0167"
readonly property string translationSelectLanguages: "\u0168" readonly property string translationCreateFiles: "\u0168"
readonly property string translationTest: "\u0169" readonly property string translationCreateReport: "\u0169"
readonly property string transparent: "\u016A" readonly property string translationExport: "\u016A"
readonly property string triState: "\u016B" readonly property string translationImport: "\u016B"
readonly property string triangleArcA: "\u016C" readonly property string translationSelectLanguages: "\u016C"
readonly property string triangleArcB: "\u016D" readonly property string translationTest: "\u016D"
readonly property string triangleCornerA: "\u016E" readonly property string transparent: "\u016E"
readonly property string triangleCornerB: "\u016F" readonly property string triState: "\u016F"
readonly property string unLinked: "\u0170" readonly property string triangleArcA: "\u0170"
readonly property string undo: "\u0171" readonly property string triangleArcB: "\u0171"
readonly property string unify_medium: "\u0172" readonly property string triangleCornerA: "\u0172"
readonly property string unpin: "\u0173" readonly property string triangleCornerB: "\u0173"
readonly property string upDownIcon: "\u0174" readonly property string unLinked: "\u0174"
readonly property string upDownSquare2: "\u0175" readonly property string undo: "\u0175"
readonly property string updateAvailable_medium: "\u0176" readonly property string unify_medium: "\u0176"
readonly property string updateContent_medium: "\u0177" readonly property string unpin: "\u0177"
readonly property string visibilityOff: "\u0178" readonly property string upDownIcon: "\u0178"
readonly property string visibilityOn: "\u0179" readonly property string upDownSquare2: "\u0179"
readonly property string visible_medium: "\u017A" readonly property string updateAvailable_medium: "\u017A"
readonly property string visible_small: "\u017B" readonly property string updateContent_medium: "\u017B"
readonly property string warning_medium: "\u017C" readonly property string visibilityOff: "\u017C"
readonly property string wildcard: "\u017D" readonly property string visibilityOn: "\u017D"
readonly property string wizardsAutomotive: "\u017E" readonly property string visible_medium: "\u017E"
readonly property string wizardsDesktop: "\u017F" readonly property string visible_small: "\u017F"
readonly property string wizardsGeneric: "\u0180" readonly property string warning_medium: "\u0180"
readonly property string wizardsMcuEmpty: "\u0181" readonly property string wildcard: "\u0181"
readonly property string wizardsMcuGraph: "\u0182" readonly property string wizardsAutomotive: "\u0182"
readonly property string wizardsMobile: "\u0183" readonly property string wizardsDesktop: "\u0183"
readonly property string wizardsUnknown: "\u0184" readonly property string wizardsGeneric: "\u0184"
readonly property string zoomAll: "\u0185" readonly property string wizardsMcuEmpty: "\u0185"
readonly property string zoomIn: "\u0186" readonly property string wizardsMcuGraph: "\u0186"
readonly property string zoomIn_medium: "\u0187" readonly property string wizardsMobile: "\u0187"
readonly property string zoomOut: "\u0188" readonly property string wizardsUnknown: "\u0188"
readonly property string zoomOut_medium: "\u0189" readonly property string zoomAll: "\u0189"
readonly property string zoomSelection: "\u018A" readonly property string zoomIn: "\u018A"
readonly property string zoomIn_medium: "\u018B"
readonly property string zoomOut: "\u018C"
readonly property string zoomOut_medium: "\u018D"
readonly property string zoomSelection: "\u018E"
readonly property font iconFont: Qt.font({ readonly property font iconFont: Qt.font({
"family": controlIcons.name, "family": controlIcons.name,

View File

@@ -242,6 +242,11 @@ QtObject {
property real dialogButtonSpacing: 10 property real dialogButtonSpacing: 10
property real dialogButtonPadding: 4 property real dialogButtonPadding: 4
// Collection Editor
property real collectionItemTextSideMargin: 10
property real collectionItemTextMargin: 5
property real collectionItemTextPadding: 5
// NEW NEW NEW // NEW NEW NEW
readonly property int flowMargin: 7 readonly property int flowMargin: 7
readonly property int flowSpacing: 7 // Odd so cursor has a center location readonly property int flowSpacing: 7 // Odd so cursor has a center location

View File

@@ -63,7 +63,8 @@ Item {
model: backend.kits model: backend.kits
onActivated: backend.setCurrentKit(kits.currentIndex) onActivated: backend.setCurrentKit(kits.currentIndex)
openUpwards: true openUpwards: true
enabled: (backend.isInDesignMode || (backend.isInEditMode && backend.projectOpened)) && backend.isQt6 enabled: (backend.isInDesignMode || (backend.isInEditMode && backend.projectOpened))
&& backend.isQt6 && !backend.isMCUs
property int kitIndex: backend.currentKit property int kitIndex: backend.currentKit
onKitIndexChanged: kits.currentIndex = backend.currentKit onKitIndexChanged: kits.currentIndex = backend.currentKit
} }

View File

@@ -4,8 +4,8 @@
"id": "Z.QtStudio.JavaScript.2", "id": "Z.QtStudio.JavaScript.2",
"category": "R.StudioJSFiles", "category": "R.StudioJSFiles",
"trDescription": "Creates a JavaScript file.", "trDescription": "Creates a JavaScript file.",
"trDisplayName": "Java Script File", "trDisplayName": "JavaScript File",
"trDisplayCategory": "Java Script", "trDisplayCategory": "JavaScript",
"icon": "file_javascript.png", "icon": "file_javascript.png",
"platformIndependent": true, "platformIndependent": true,

View File

@@ -382,6 +382,22 @@
{ {
"source": "../shared-plugin/name/designer/plugin.metainfo", "source": "../shared-plugin/name/designer/plugin.metainfo",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/designer/plugin.metainfo" "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/designer/plugin.metainfo"
},
{
"source": "../shared-plugin/name/JsonData.qml.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/JsonData.qml"
},
{
"source": "../shared-plugin/name/DataStore.qml.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/DataStore.qml"
},
{
"source": "../shared-plugin/name/models.json.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/models.json"
},
{
"source": "../shared-plugin/name/data.json.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/data.json"
} }
] ]
} }

View File

@@ -395,6 +395,22 @@
{ {
"source": "../shared-plugin/name/designer/plugin.metainfo", "source": "../shared-plugin/name/designer/plugin.metainfo",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/designer/plugin.metainfo" "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/designer/plugin.metainfo"
},
{
"source": "../shared-plugin/name/JsonData.qml.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/JsonData.qml"
},
{
"source": "../shared-plugin/name/DataStore.qml.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/DataStore.qml"
},
{
"source": "../shared-plugin/name/models.json.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/models.json"
},
{
"source": "../shared-plugin/name/data.json.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/data.json"
} }
] ]
} }

View File

@@ -63,6 +63,10 @@ Project {
filter: "*.qsb" filter: "*.qsb"
} }
Files {
filter: "*.json"
}
Files { Files {
filter: "*.mesh" filter: "*.mesh"
directory: "asset_imports" directory: "asset_imports"
@@ -110,7 +114,7 @@ Project {
/* Required for deployment */ /* Required for deployment */
targetDirectory: "/opt/%{ProjectName}" targetDirectory: "/opt/%{ProjectName}"
qdsVersion: "4.4" qdsVersion: "4.3"
quickVersion: "%{QtQuickVersion}" quickVersion: "%{QtQuickVersion}"

View File

@@ -382,6 +382,22 @@
{ {
"source": "../shared-plugin/name/designer/plugin.metainfo", "source": "../shared-plugin/name/designer/plugin.metainfo",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/designer/plugin.metainfo" "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/designer/plugin.metainfo"
},
{
"source": "../shared-plugin/name/JsonData.qml.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/JsonData.qml"
},
{
"source": "../shared-plugin/name/DataStore.qml.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/DataStore.qml"
},
{
"source": "../shared-plugin/name/models.json.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/models.json"
},
{
"source": "../shared-plugin/name/data.json.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/data.json"
} }
] ]
} }

View File

@@ -341,6 +341,22 @@
{ {
"source": "../shared-plugin/name/designer/plugin.metainfo", "source": "../shared-plugin/name/designer/plugin.metainfo",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/designer/plugin.metainfo" "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/designer/plugin.metainfo"
},
{
"source": "../shared-plugin/name/JsonData.qml.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/JsonData.qml"
},
{
"source": "../shared-plugin/name/DataStore.qml.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/DataStore.qml"
},
{
"source": "../shared-plugin/name/models.json.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/models.json"
},
{
"source": "../shared-plugin/name/data.json.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/data.json"
} }
] ]
} }

View File

@@ -343,6 +343,22 @@
{ {
"source": "../shared-plugin/name/designer/plugin.metainfo", "source": "../shared-plugin/name/designer/plugin.metainfo",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/designer/plugin.metainfo" "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/designer/plugin.metainfo"
},
{
"source": "../shared-plugin/name/JsonData.qml.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/JsonData.qml"
},
{
"source": "../shared-plugin/name/DataStore.qml.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/DataStore.qml"
},
{
"source": "../shared-plugin/name/models.json.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/models.json"
},
{
"source": "../shared-plugin/name/data.json.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/data.json"
} }
] ]
} }

View File

@@ -343,6 +343,22 @@
{ {
"source": "../shared-plugin/name/designer/plugin.metainfo", "source": "../shared-plugin/name/designer/plugin.metainfo",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/designer/plugin.metainfo" "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/designer/plugin.metainfo"
},
{
"source": "../shared-plugin/name/JsonData.qml.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/JsonData.qml"
},
{
"source": "../shared-plugin/name/DataStore.qml.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/DataStore.qml"
},
{
"source": "../shared-plugin/name/models.json.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/models.json"
},
{
"source": "../shared-plugin/name/data.json.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/data.json"
} }
] ]
} }

View File

@@ -13,7 +13,12 @@ qt6_add_qml_module(%{ImportModuleName}
RESOURCE_PREFIX "/qt/qml" RESOURCE_PREFIX "/qt/qml"
QML_FILES QML_FILES
Constants.qml Constants.qml
DataStore.qml
DirectoryFontLoader.qml DirectoryFontLoader.qml
EventListModel.qml EventListModel.qml
EventListSimulator.qml EventListSimulator.qml
JsonData.qml
RESOURCES
data.json
models.json
) )

View File

@@ -0,0 +1,17 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
pragma Singleton
import QtQuick 6.5
import QtQuick.Studio.Utils 1.0
JsonListModel {
id: models
source: Qt.resolvedUrl("models.json")
property ChildListModel book: ChildListModel {
modelName: "book"
}
property JsonData backend: JsonData {}
}

View File

@@ -0,0 +1,9 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Studio.Utils 1.0
JsonBackend {
property string name: "someName"
property int number: 1
source: Qt.resolvedUrl("data.json")
}

View File

@@ -0,0 +1,4 @@
{
"name": "Christen Anderson",
"number": "+3455641"
}

View File

@@ -1,6 +1,6 @@
Module %{ImportModuleName} Module %{ImportModuleName}
singleton DataStore 1.0 DataStore.qml
singleton Constants 1.0 Constants.qml singleton Constants 1.0 Constants.qml
EventListSimulator 1.0 EventListSimulator.qml EventListSimulator 1.0 EventListSimulator.qml
EventListModel 1.0 EventListModel.qml EventListModel 1.0 EventListModel.qml
DirectoryFontLoader 1.0 DirectoryFontLoader.qml DirectoryFontLoader 1.0 DirectoryFontLoader.qml

View File

@@ -0,0 +1,30 @@
{
"book": [
{
"author": "Nigel Rees",
"category": "reference",
"price": 8.95,
"title": "Sayings of the Century"
},
{
"author": "Evelyn Waugh",
"category": "fiction",
"price": 12.99,
"title": "Sword of Honor"
},
{
"author": "Herman Melville",
"category": "fiction",
"isbn": "0-553-21311-3",
"price": 8.99,
"title": "Moby Dick"
},
{
"author": "J. R. R. Tolkien",
"category": "fiction",
"isbn": "0-395-19395-8",
"price": 22.99,
"title": "The Lord of the Rings"
}
]
}

View File

@@ -1,4 +1,5 @@
singleton Constants 1.0 Constants.qml singleton Constants 1.0 Constants.qml
singleton DataStore 1.0 DataStore.qml
EventListModel 1.0 EventListModel.qml EventListModel 1.0 EventListModel.qml
EventListSimulator 1.0 EventListSimulator.qml EventListSimulator 1.0 EventListSimulator.qml
DirectoryFontLoader 1.0 DirectoryFontLoader.qml DirectoryFontLoader 1.0 DirectoryFontLoader.qml

View File

@@ -261,6 +261,22 @@
"source": "%{QdsWizardPath}/shared-plugin/name/designer/plugin.metainfo", "source": "%{QdsWizardPath}/shared-plugin/name/designer/plugin.metainfo",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/designer/plugin.metainfo", "target": "%{ProjectDirectory}/imports/%{ImportModuleName}/designer/plugin.metainfo",
"condition": "%{QdsProjectStyle}" "condition": "%{QdsProjectStyle}"
},
{
"source": "../shared-plugin/name/JsonData.qml.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/JsonData.qml"
},
{
"source": "../shared-plugin/name/DataStore.qml.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/DataStore.qml"
},
{
"source": "../shared-plugin/name/models.json.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/models.json"
},
{
"source": "../shared-plugin/name/data.json.tpl",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/data.json"
} }
] ]
} }

View File

@@ -66,7 +66,8 @@ enum eTitleBarButton {
TitleBarButtonTabsMenu, TitleBarButtonTabsMenu,
TitleBarButtonUndock, TitleBarButtonUndock,
TitleBarButtonClose, TitleBarButtonClose,
TitleBarButtonAutoHide TitleBarButtonAutoHide,
TitleBarButtonMinimize
}; };
/** /**
@@ -88,6 +89,7 @@ enum eIcon {
DockAreaMenuIcon, //!< DockAreaMenuIcon DockAreaMenuIcon, //!< DockAreaMenuIcon
DockAreaUndockIcon, //!< DockAreaUndockIcon DockAreaUndockIcon, //!< DockAreaUndockIcon
DockAreaCloseIcon, //!< DockAreaCloseIcon DockAreaCloseIcon, //!< DockAreaCloseIcon
DockAreaMinimizeIcon,
FloatingWidgetCloseIcon, //!< FloatingWidgetCloseIcon FloatingWidgetCloseIcon, //!< FloatingWidgetCloseIcon
FloatingWidgetNormalIcon, //!< FloatingWidgetNormalIcon FloatingWidgetNormalIcon, //!< FloatingWidgetNormalIcon
FloatingWidgetMaximizeIcon, //!< FloatingWidgetMaximizeIcon FloatingWidgetMaximizeIcon, //!< FloatingWidgetMaximizeIcon
@@ -161,8 +163,8 @@ bool isSideBarArea(DockWidgetArea area);
/** /**
* Searches for the parent widget of the given type. Returns the parent widget of the given * Searches for the parent widget of the given type. Returns the parent widget of the given
* widget or 0 if the widget is not child of any widget of type T. * widget or nullptr if the widget is not child of any widget of type T.
* It is not safe to use this function in in DockWidget because only the current dock widget has a * It is not safe to use this function in DockWidget, because only the current dock widget has a
* parent. All dock widgets that are not the current dock widget in a dock area have no parent. * parent. All dock widgets that are not the current dock widget in a dock area have no parent.
*/ */
template<class T> template<class T>
@@ -171,12 +173,12 @@ T findParent(const QWidget *widget)
QWidget *parentWidget = widget->parentWidget(); QWidget *parentWidget = widget->parentWidget();
while (parentWidget) { while (parentWidget) {
T parentImpl = qobject_cast<T>(parentWidget); T parentImpl = qobject_cast<T>(parentWidget);
if (parentImpl) { if (parentImpl)
return parentImpl; return parentImpl;
}
parentWidget = parentWidget->parentWidget(); parentWidget = parentWidget->parentWidget();
} }
return 0; return nullptr;
} }
/** /**

View File

@@ -46,10 +46,11 @@ public:
QPointer<TitleBarButton> m_autoHideButton; QPointer<TitleBarButton> m_autoHideButton;
QPointer<TitleBarButton> m_undockButton; QPointer<TitleBarButton> m_undockButton;
QPointer<TitleBarButton> m_closeButton; QPointer<TitleBarButton> m_closeButton;
QPointer<TitleBarButton> m_minimizeButton;
QBoxLayout *m_layout = nullptr; QBoxLayout *m_layout = nullptr;
DockAreaWidget *m_dockArea = nullptr; DockAreaWidget *m_dockArea = nullptr;
DockAreaTabBar *m_tabBar = nullptr; DockAreaTabBar *m_tabBar = nullptr;
ElidingLabel *m_autoHideTitleLabel; ElidingLabel *m_autoHideTitleLabel = nullptr;
bool m_menuOutdated = true; bool m_menuOutdated = true;
QMenu *m_tabsMenu; QMenu *m_tabsMenu;
QList<TitleBarButtonType *> m_dockWidgetActionsButtons; QList<TitleBarButtonType *> m_dockWidgetActionsButtons;
@@ -198,6 +199,23 @@ void DockAreaTitleBarPrivate::createButtons()
q, q,
&DockAreaTitleBar::onAutoHideButtonClicked); &DockAreaTitleBar::onAutoHideButtonClicked);
// Minimize button
m_minimizeButton = new TitleBarButton(
testAutoHideConfigFlag(DockManager::AutoHideHasMinimizeButton));
m_minimizeButton->setObjectName("dockAreaMinimizeButton");
//m_minimizeButton->setAutoRaise(true);
m_minimizeButton->setVisible(false);
internal::setButtonIcon(m_minimizeButton,
QStyle::SP_TitleBarMinButton,
ADS::DockAreaMinimizeIcon);
internal::setToolTip(m_minimizeButton, QObject::tr("Minimize"));
m_minimizeButton->setSizePolicy(sizePolicy);
m_layout->addWidget(m_minimizeButton, 0);
QObject::connect(m_minimizeButton,
&QToolButton::clicked,
q,
&DockAreaTitleBar::minimizeAutoHideContainer);
// Close button // Close button
m_closeButton = new TitleBarButton(testConfigFlag(DockManager::DockAreaHasCloseButton)); m_closeButton = new TitleBarButton(testConfigFlag(DockManager::DockAreaHasCloseButton));
m_closeButton->setObjectName("dockAreaCloseButton"); m_closeButton->setObjectName("dockAreaCloseButton");
@@ -224,7 +242,10 @@ void DockAreaTitleBarPrivate::createAutoHideTitleLabel()
{ {
m_autoHideTitleLabel = new ElidingLabel(""); m_autoHideTitleLabel = new ElidingLabel("");
m_autoHideTitleLabel->setObjectName("autoHideTitleLabel"); m_autoHideTitleLabel->setObjectName("autoHideTitleLabel");
m_layout->addWidget(m_autoHideTitleLabel); // At position 0 is the tab bar - insert behind tab bar
m_layout->insertWidget(1, m_autoHideTitleLabel);
m_autoHideTitleLabel->setVisible(false); // Default hidden
m_layout->insertWidget(2, new SpacerWidget(q));
} }
void DockAreaTitleBarPrivate::createTabBar() void DockAreaTitleBarPrivate::createTabBar()
{ {
@@ -366,10 +387,8 @@ DockAreaTitleBar::DockAreaTitleBar(DockAreaWidget *parent)
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
d->createTabBar(); d->createTabBar();
d->createAutoHideTitleLabel();
d->m_autoHideTitleLabel->setVisible(false); // Default hidden
d->m_layout->addWidget(new SpacerWidget(this));
d->createButtons(); d->createButtons();
d->createAutoHideTitleLabel();
setFocusPolicy(Qt::NoFocus); setFocusPolicy(Qt::NoFocus);
} }
@@ -448,6 +467,18 @@ void DockAreaTitleBar::onCloseButtonClicked()
d->m_dockArea->closeArea(); d->m_dockArea->closeArea();
} }
void DockAreaTitleBar::onAutoHideCloseActionTriggered()
{
d->m_dockArea->closeArea();
}
void DockAreaTitleBar::minimizeAutoHideContainer()
{
auto autoHideContainer = d->m_dockArea->autoHideDockContainer();
if (autoHideContainer)
autoHideContainer->collapseView(true);
}
void DockAreaTitleBar::onUndockButtonClicked() void DockAreaTitleBar::onUndockButtonClicked()
{ {
if (d->m_dockArea->features().testFlag(DockWidget::DockWidgetFloatable)) if (d->m_dockArea->features().testFlag(DockWidget::DockWidgetFloatable))
@@ -533,6 +564,8 @@ TitleBarButton *DockAreaTitleBar::button(eTitleBarButton which) const
return d->m_closeButton; return d->m_closeButton;
case TitleBarButtonAutoHide: case TitleBarButtonAutoHide:
return d->m_autoHideButton; return d->m_autoHideButton;
case TitleBarButtonMinimize:
return d->m_minimizeButton;
} }
return nullptr; return nullptr;
} }
@@ -676,12 +709,28 @@ void DockAreaTitleBar::contextMenuEvent(QContextMenuEvent *event)
} }
menu.addSeparator(); menu.addSeparator();
} }
QAction *closeAction = menu.addAction(isAutoHide ? Tr::tr("Close") : Tr::tr("Close Group"));
if (isAutoHide) {
QAction *minimizeAction = menu.addAction(Tr::tr("Minimize"));
minimizeAction->connect(minimizeAction,
&QAction::triggered,
this,
&DockAreaTitleBar::minimizeAutoHideContainer);
QAction *closeAction = menu.addAction(Tr::tr("Close"));
closeAction->connect(closeAction,
&QAction::triggered,
this,
&DockAreaTitleBar::onAutoHideCloseActionTriggered);
closeAction->setEnabled(d->m_dockArea->features().testFlag(DockWidget::DockWidgetClosable));
} else {
QAction *closeAction = menu.addAction(Tr::tr("Close Group"));
closeAction->connect(closeAction, closeAction->connect(closeAction,
&QAction::triggered, &QAction::triggered,
this, this,
&DockAreaTitleBar::onCloseButtonClicked); &DockAreaTitleBar::onCloseButtonClicked);
closeAction->setEnabled(d->m_dockArea->features().testFlag(DockWidget::DockWidgetClosable)); closeAction->setEnabled(d->m_dockArea->features().testFlag(DockWidget::DockWidgetClosable));
}
if (!isAutoHide && !isTopLevelArea) { if (!isAutoHide && !isTopLevelArea) {
QAction *closeOthersAction = menu.addAction(Tr::tr("Close Other Groups")); QAction *closeOthersAction = menu.addAction(Tr::tr("Close Other Groups"));
@@ -718,8 +767,11 @@ QString DockAreaTitleBar::titleBarButtonToolTip(eTitleBarButton button) const
break; break;
case TitleBarButtonClose: case TitleBarButtonClose:
if (d->m_dockArea->isAutoHide()) if (d->m_dockArea->isAutoHide()) {
return Tr::tr("Close"); bool minimize = DockManager::testAutoHideConfigFlag(
DockManager::AutoHideCloseButtonCollapsesDock);
return minimize ? Tr::tr("Minimize") : Tr::tr("Close");
}
if (DockManager::testConfigFlag(DockManager::DockAreaCloseButtonClosesTab)) if (DockManager::testConfigFlag(DockManager::DockAreaCloseButtonClosesTab))
return Tr::tr("Close Active Tab"); return Tr::tr("Close Active Tab");
@@ -748,4 +800,11 @@ void DockAreaTitleBar::setAreaFloating()
d->makeAreaFloating(mapFromGlobal(QCursor::pos()), DraggingInactive); d->makeAreaFloating(mapFromGlobal(QCursor::pos()), DraggingInactive);
} }
void DockAreaTitleBar::showAutoHideControls(bool show)
{
d->m_tabBar->setVisible(!show); // Auto hide toolbar never has tabs
d->m_minimizeButton->setVisible(show);
d->m_autoHideTitleLabel->setVisible(show);
}
} // namespace ADS } // namespace ADS

View File

@@ -84,6 +84,8 @@ private:
void onTabsMenuAboutToShow(); void onTabsMenuAboutToShow();
void onCloseButtonClicked(); void onCloseButtonClicked();
void onAutoHideCloseActionTriggered();
void minimizeAutoHideContainer();
void onUndockButtonClicked(); void onUndockButtonClicked();
void onTabsMenuActionTriggered(QAction *action); void onTabsMenuActionTriggered(QAction *action);
void onCurrentTabChanged(int index); void onCurrentTabChanged(int index);
@@ -190,6 +192,11 @@ public:
*/ */
void setAreaFloating(); void setAreaFloating();
/**
* Call this function, to create all the required auto hide controls
*/
void showAutoHideControls(bool show);
signals: signals:
/** /**
* This signal is emitted if a tab in the tab bar is clicked by the user * This signal is emitted if a tab in the tab bar is clicked by the user

View File

@@ -295,13 +295,20 @@ void DockAreaWidgetPrivate::updateTitleBarButtonVisibility(bool isTopLevel)
if (!container) if (!container)
return; return;
if (isTopLevel) { bool isAutoHide = q->isAutoHide();
if (isAutoHide) {
bool showCloseButton = DockManager::autoHideConfigFlags().testFlag(
DockManager::AutoHideHasCloseButton);
m_titleBar->button(TitleBarButtonClose)->setVisible(showCloseButton);
m_titleBar->button(TitleBarButtonAutoHide)->setVisible(true);
m_titleBar->button(TitleBarButtonUndock)->setVisible(false);
m_titleBar->button(TitleBarButtonTabsMenu)->setVisible(false);
} else if (isTopLevel) {
m_titleBar->button(TitleBarButtonClose)->setVisible(!container->isFloating()); m_titleBar->button(TitleBarButtonClose)->setVisible(!container->isFloating());
m_titleBar->button(TitleBarButtonAutoHide)->setVisible(!container->isFloating()); m_titleBar->button(TitleBarButtonAutoHide)->setVisible(!container->isFloating());
// Undock and tabs should never show when auto hidden // Undock and tabs should never show when auto hidden
m_titleBar->button(TitleBarButtonUndock) m_titleBar->button(TitleBarButtonUndock)->setVisible(!container->isFloating());
->setVisible(!container->isFloating() && !q->isAutoHide()); m_titleBar->button(TitleBarButtonTabsMenu)->setVisible(true);
m_titleBar->button(TitleBarButtonTabsMenu)->setVisible(!q->isAutoHide());
} else { } else {
m_titleBar->button(TitleBarButtonClose)->setVisible(true); m_titleBar->button(TitleBarButtonClose)->setVisible(true);
m_titleBar->button(TitleBarButtonAutoHide)->setVisible(true); m_titleBar->button(TitleBarButtonAutoHide)->setVisible(true);
@@ -650,10 +657,7 @@ void DockAreaWidget::updateTitleBarVisibility()
} }
if (isAutoHideFeatureEnabled()) { if (isAutoHideFeatureEnabled()) {
auto tabBar = d->m_titleBar->tabBar(); d->m_titleBar->showAutoHideControls(autoHide);
tabBar->setVisible(!autoHide); // Never show tab bar when auto hidden
// Always show when auto hidden, never otherwise
d->m_titleBar->autoHideTitleLabel()->setVisible(autoHide);
updateTitleBarButtonVisibility(container->topLevelDockArea() == this); updateTitleBarButtonVisibility(container->topLevelDockArea() == this);
} }
} }

View File

@@ -484,9 +484,10 @@ void DockContainerWidgetPrivate::dropIntoSection(FloatingDockContainer *floating
if (!targetAreaSplitter) { if (!targetAreaSplitter) {
auto splitter = createSplitter(insertParam.orientation()); auto splitter = createSplitter(insertParam.orientation());
m_layout->replaceWidget(targetArea, splitter); QLayoutItem *layoutItem = m_layout->replaceWidget(targetArea, splitter);
splitter->addWidget(targetArea); splitter->addWidget(targetArea);
targetAreaSplitter = splitter; targetAreaSplitter = splitter;
delete layoutItem;
} }
int areaIndex = targetAreaSplitter->indexOf(targetArea); int areaIndex = targetAreaSplitter->indexOf(targetArea);
auto floatingSplitter = floatingContainer->rootSplitter(); auto floatingSplitter = floatingContainer->rootSplitter();
@@ -1532,10 +1533,11 @@ bool DockContainerWidget::restoreState(DockingStateReader &stateReader, bool tes
if (!newRootSplitter) if (!newRootSplitter)
newRootSplitter = d->createSplitter(Qt::Horizontal); newRootSplitter = d->createSplitter(Qt::Horizontal);
d->m_layout->replaceWidget(d->m_rootSplitter, newRootSplitter); QLayoutItem *layoutItem = d->m_layout->replaceWidget(d->m_rootSplitter, newRootSplitter);
auto oldRoot = d->m_rootSplitter; auto oldRoot = d->m_rootSplitter;
d->m_rootSplitter = qobject_cast<DockSplitter *>(newRootSplitter); d->m_rootSplitter = qobject_cast<DockSplitter *>(newRootSplitter);
oldRoot->deleteLater(); oldRoot->deleteLater();
delete layoutItem;
return true; return true;
} }

View File

@@ -87,6 +87,11 @@ public:
DockWidget *m_centralWidget = nullptr; DockWidget *m_centralWidget = nullptr;
bool m_isLeavingMinimized = false; bool m_isLeavingMinimized = false;
Qt::ToolButtonStyle m_toolBarStyleDocked = Qt::ToolButtonIconOnly;
Qt::ToolButtonStyle m_toolBarStyleFloating = Qt::ToolButtonTextUnderIcon;
QSize m_toolBarIconSizeDocked = QSize(16, 16);
QSize m_toolBarIconSizeFloating = QSize(24, 24);
QString m_workspacePresetsPath; QString m_workspacePresetsPath;
QList<Workspace> m_workspaces; QList<Workspace> m_workspaces;
Workspace m_workspace; Workspace m_workspace;
@@ -94,6 +99,7 @@ public:
QtcSettings *m_settings = nullptr; QtcSettings *m_settings = nullptr;
bool m_modeChangeState = false; bool m_modeChangeState = false;
bool m_wasShown = false;
bool m_workspaceOrderDirty = false; bool m_workspaceOrderDirty = false;
/** /**
@@ -364,8 +370,10 @@ DockManager::DockManager(QWidget *parent)
DockManager::~DockManager() DockManager::~DockManager()
{ {
if (d->m_wasShown) {
emit aboutToUnloadWorkspace(d->m_workspace.fileName()); emit aboutToUnloadWorkspace(d->m_workspace.fileName());
save(); save();
}
saveStartupWorkspace(); saveStartupWorkspace();
saveLockWorkspace(); saveLockWorkspace();
@@ -766,6 +774,38 @@ QString DockManager::floatingContainersTitle()
return g_floatingContainersTitle; return g_floatingContainersTitle;
} }
void DockManager::setDockWidgetToolBarStyle(Qt::ToolButtonStyle style, DockWidget::eState state)
{
if (DockWidget::StateFloating == state)
d->m_toolBarStyleFloating = style;
else
d->m_toolBarStyleDocked = style;
}
Qt::ToolButtonStyle DockManager::dockWidgetToolBarStyle(DockWidget::eState state) const
{
if (DockWidget::StateFloating == state)
return d->m_toolBarStyleFloating;
else
return d->m_toolBarStyleDocked;
}
void DockManager::setDockWidgetToolBarIconSize(const QSize &iconSize, DockWidget::eState state)
{
if (DockWidget::StateFloating == state)
d->m_toolBarIconSizeFloating = iconSize;
else
d->m_toolBarIconSizeDocked = iconSize;
}
QSize DockManager::dockWidgetToolBarIconSize(DockWidget::eState state) const
{
if (DockWidget::StateFloating == state)
return d->m_toolBarIconSizeFloating;
else
return d->m_toolBarIconSizeDocked;
}
DockWidget *DockManager::centralWidget() const DockWidget *DockManager::centralWidget() const
{ {
return d->m_centralWidget; return d->m_centralWidget;
@@ -1324,6 +1364,11 @@ bool DockManager::isModeChangeState() const
return d->m_modeChangeState; return d->m_modeChangeState;
} }
void DockManager::aboutToShow()
{
d->m_wasShown = true;
}
expected_str<QString> DockManager::importWorkspace(const QString &filePath) expected_str<QString> DockManager::importWorkspace(const QString &filePath)
{ {
qCInfo(adsLog) << "Import workspace" << filePath; qCInfo(adsLog) << "Import workspace" << filePath;

View File

@@ -186,10 +186,13 @@ public:
= 0x20, ///< show the auto hide window on mouse over tab and hide it if mouse leaves auto hide container = 0x20, ///< show the auto hide window on mouse over tab and hide it if mouse leaves auto hide container
AutoHideCloseButtonCollapsesDock AutoHideCloseButtonCollapsesDock
= 0x40, ///< Close button of an auto hide container collapses the dock instead of hiding it completely = 0x40, ///< Close button of an auto hide container collapses the dock instead of hiding it completely
AutoHideHasCloseButton
= 0x80, //< If the flag is set an auto hide title bar has a close button
AutoHideHasMinimizeButton
= 0x100, ///< if this flag is set, the auto hide title bar has a minimize button to collapse the dock widget
DefaultAutoHideConfig DefaultAutoHideConfig = AutoHideFeatureEnabled | DockAreaHasAutoHideButton
= AutoHideFeatureEnabled | DockAreaHasAutoHideButton | AutoHideCloseButtonCollapsesDock | AutoHideHasCloseButton
| AutoHideCloseButtonCollapsesDock ///< the default configuration for left and right side bars
}; };
Q_DECLARE_FLAGS(AutoHideFlags, eAutoHideFlag) Q_DECLARE_FLAGS(AutoHideFlags, eAutoHideFlag)
@@ -452,6 +455,31 @@ public:
*/ */
static QString floatingContainersTitle(); static QString floatingContainersTitle();
/**
* This function sets the tool button style for the given dock widget state. It is possible to
* switch the tool button style depending on the state. If a dock widget is floating, then here
* are more space and it is possible to select a style that requires more space like
* Qt::ToolButtonTextUnderIcon. For the docked state Qt::ToolButtonIconOnly might be better.
*/
void setDockWidgetToolBarStyle(Qt::ToolButtonStyle style, DockWidget::eState state);
/**
* Returns the tool button style for the given docking state. \see setToolBarStyle()
*/
Qt::ToolButtonStyle dockWidgetToolBarStyle(DockWidget::eState state) const;
/**
* This function sets the tool button icon size for the given state. If a dock widget is
* floating, there is more space and increasing the icon size is possible. For docked widgets,
* small icon sizes, eg. 16 x 16 might be better.
*/
void setDockWidgetToolBarIconSize(const QSize &iconSize, DockWidget::eState state);
/**
* Returns the icon size for a given docking state. \see setToolBarIconSize()
*/
QSize dockWidgetToolBarIconSize(DockWidget::eState state) const;
/** /**
* This function returns managers central widget or nullptr if no central widget is set. * This function returns managers central widget or nullptr if no central widget is set.
*/ */
@@ -733,6 +761,13 @@ public:
static QString readDisplayName(const Utils::FilePath &filePath); static QString readDisplayName(const Utils::FilePath &filePath);
static bool writeDisplayName(const Utils::FilePath &filePath, const QString &displayName); static bool writeDisplayName(const Utils::FilePath &filePath, const QString &displayName);
/**
* This is used to limit saving of workspaces to only when they were actually presented ones,
* otherwise it could lead to distorted workspace due to the correct windows sizes not being
* set when never presented/rendered.
*/
void aboutToShow();
signals: signals:
void aboutToUnloadWorkspace(QString fileName); void aboutToUnloadWorkspace(QString fileName);
void aboutToLoadWorkspace(QString fileName); void aboutToLoadWorkspace(QString fileName);

View File

@@ -50,10 +50,9 @@ bool DockSplitter::hasVisibleContent() const
{ {
// TODO Cache or precalculate this to speed up // TODO Cache or precalculate this to speed up
for (int i = 0; i < count(); ++i) { for (int i = 0; i < count(); ++i) {
if (!widget(i)->isHidden()) { if (!widget(i)->isHidden())
return true; return true;
} }
}
return false; return false;
} }

View File

@@ -64,6 +64,7 @@ public:
= DockWidget::MinimumSizeHintFromDockWidget; = DockWidget::MinimumSizeHintFromDockWidget;
WidgetFactory *m_factory = nullptr; WidgetFactory *m_factory = nullptr;
QPointer<AutoHideTab> m_sideTabWidget; QPointer<AutoHideTab> m_sideTabWidget;
DockWidget::eToolBarStyleSource m_toolBarStyleSource = DockWidget::ToolBarStyleFromDockManager;
/** /**
* Private data constructor * Private data constructor
@@ -106,6 +107,11 @@ public:
* Creates the content widget with the registered widget factory and returns true on success. * Creates the content widget with the registered widget factory and returns true on success.
*/ */
bool createWidgetFromFactory(); bool createWidgetFromFactory();
/**
* Use the dock manager toolbar style and icon size for the different states
*/
void setToolBarStyleFromDockManager();
}; // class DockWidgetPrivate }; // class DockWidgetPrivate
DockWidgetPrivate::DockWidgetPrivate(DockWidget *parent) DockWidgetPrivate::DockWidgetPrivate(DockWidget *parent)
@@ -246,6 +252,19 @@ bool DockWidgetPrivate::createWidgetFromFactory()
return true; return true;
} }
void DockWidgetPrivate::setToolBarStyleFromDockManager()
{
if (!m_dockManager)
return;
auto state = DockWidget::StateDocked;
q->setToolBarIconSize(m_dockManager->dockWidgetToolBarIconSize(state), state);
q->setToolBarStyle(m_dockManager->dockWidgetToolBarStyle(state), state);
state = DockWidget::StateFloating;
q->setToolBarIconSize(m_dockManager->dockWidgetToolBarIconSize(state), state);
q->setToolBarStyle(m_dockManager->dockWidgetToolBarStyle(state), state);
}
DockWidget::DockWidget(const QString &uniqueId, QWidget *parent) DockWidget::DockWidget(const QString &uniqueId, QWidget *parent)
: QFrame(parent) : QFrame(parent)
, d(new DockWidgetPrivate(this)) , d(new DockWidgetPrivate(this))
@@ -290,8 +309,11 @@ void DockWidget::setWidget(QWidget *widget, eInsertMode insertMode)
auto scrollAreaWidget = qobject_cast<QAbstractScrollArea *>(widget); auto scrollAreaWidget = qobject_cast<QAbstractScrollArea *>(widget);
if (scrollAreaWidget || ForceNoScrollArea == insertMode) { if (scrollAreaWidget || ForceNoScrollArea == insertMode) {
d->m_layout->addWidget(widget); d->m_layout->addWidget(widget);
if (scrollAreaWidget && scrollAreaWidget->viewport()) if (scrollAreaWidget) {
if (scrollAreaWidget->viewport())
scrollAreaWidget->viewport()->setProperty("dockWidgetContent", true); scrollAreaWidget->viewport()->setProperty("dockWidgetContent", true);
scrollAreaWidget->setProperty("focused", isFocused());
}
} else { } else {
d->setupScrollArea(); d->setupScrollArea();
d->m_scrollArea->setWidget(widget); d->m_scrollArea->setWidget(widget);
@@ -381,6 +403,12 @@ DockManager *DockWidget::dockManager() const
void DockWidget::setDockManager(DockManager *dockManager) void DockWidget::setDockManager(DockManager *dockManager)
{ {
d->m_dockManager = dockManager; d->m_dockManager = dockManager;
if (!dockManager)
return;
if (ToolBarStyleFromDockManager == d->m_toolBarStyleSource)
d->setToolBarStyleFromDockManager();
} }
DockContainerWidget *DockWidget::dockContainer() const DockContainerWidget *DockWidget::dockContainer() const
@@ -457,6 +485,11 @@ void DockWidget::setFocused(bool focused)
if (d->m_scrollArea) if (d->m_scrollArea)
d->m_scrollArea->setProperty("focused", focused); d->m_scrollArea->setProperty("focused", focused);
QList<QAbstractScrollArea *> scrollAreas = d->m_widget->findChildren<QAbstractScrollArea *>(QString(),
Qt::FindDirectChildrenOnly);
for (QAbstractScrollArea *scrollArea : scrollAreas)
scrollArea->setProperty("focused", focused);
const QString customObjectName = QString("__mainSrollView"); const QString customObjectName = QString("__mainSrollView");
QList<QQuickWidget *> quickWidgets = d->m_widget->findChildren<QQuickWidget *>(); QList<QQuickWidget *> quickWidgets = d->m_widget->findChildren<QQuickWidget *>();
@@ -491,6 +524,18 @@ QAction *DockWidget::toggleViewAction() const
return d->m_toggleViewAction; return d->m_toggleViewAction;
} }
void DockWidget::setToggleViewAction(QAction *action)
{
if (!action)
return;
d->m_toggleViewAction->setParent(nullptr);
delete d->m_toggleViewAction;
d->m_toggleViewAction = action;
d->m_toggleViewAction->setParent(this);
connect(d->m_toggleViewAction, &QAction::triggered, this, &DockWidget::toggleView);
}
void DockWidget::setToggleViewActionMode(eToggleViewActionMode mode) void DockWidget::setToggleViewActionMode(eToggleViewActionMode mode)
{ {
if (ActionModeToggle == mode) { if (ActionModeToggle == mode) {
@@ -693,6 +738,18 @@ void DockWidget::setToolBar(QToolBar *toolBar)
setToolbarFloatingStyle(isFloating()); setToolbarFloatingStyle(isFloating());
} }
void DockWidget::setToolBarStyleSource(eToolBarStyleSource source)
{
d->m_toolBarStyleSource = source;
if (ToolBarStyleFromDockManager == d->m_toolBarStyleSource)
d->setToolBarStyleFromDockManager();
}
DockWidget::eToolBarStyleSource DockWidget::toolBarStyleSource() const
{
return d->m_toolBarStyleSource;
}
void DockWidget::setToolBarStyle(Qt::ToolButtonStyle style, eState state) void DockWidget::setToolBarStyle(Qt::ToolButtonStyle style, eState state)
{ {
if (StateFloating == state) if (StateFloating == state)

View File

@@ -154,6 +154,8 @@ public:
enum eState { StateHidden, StateDocked, StateFloating }; enum eState { StateHidden, StateDocked, StateFloating };
enum eToolBarStyleSource { ToolBarStyleFromDockManager, ToolBarStyleFromDockWidget };
/** /**
* Sets the widget for the dock widget to widget. * Sets the widget for the dock widget to widget.
* The InsertMode defines how the widget is inserted into the dock widget. * The InsertMode defines how the widget is inserted into the dock widget.
@@ -385,6 +387,12 @@ public:
*/ */
QAction *toggleViewAction() const; QAction *toggleViewAction() const;
/**
* Use provided action to be the default toggle view action for this dock widget.
* This dock widget now owns the action.
*/
void setToggleViewAction(QAction *action);
/** /**
* Configures the behavior of the toggle view action. * Configures the behavior of the toggle view action.
* \see eToggleViewActionMode for a detailed description * \see eToggleViewActionMode for a detailed description
@@ -443,6 +451,17 @@ public:
*/ */
void setToolBar(QToolBar *toolBar); void setToolBar(QToolBar *toolBar);
/**
* Configures, if the dock widget uses the global tool bar styles from
* dock manager or if it uses its own tool bar style
*/
void setToolBarStyleSource(eToolBarStyleSource source);
/**
* Returns the configured tool bar style source
*/
eToolBarStyleSource toolBarStyleSource() const;
/** /**
* This function sets the tool button style for the given dock widget state. * This function sets the tool button style for the given dock widget state.
* It is possible to switch the tool button style depending on the state. * It is possible to switch the tool button style depending on the state.

View File

@@ -374,10 +374,12 @@ void DockWidgetTab::mouseReleaseEvent(QMouseEvent *event)
break; break;
default: default:
if (DockManager::testConfigFlag(DockManager::FocusHighlighting))
d->focusController()->setDockWidgetTabPressed(false);
break; break;
} }
if (DockManager::testConfigFlag(DockManager::FocusHighlighting))
d->focusController()->setDockWidgetTabPressed(false);
} else if (event->button() == Qt::MiddleButton) { } else if (event->button() == Qt::MiddleButton) {
if (DockManager::testConfigFlag(DockManager::MiddleMouseButtonClosesTab) if (DockManager::testConfigFlag(DockManager::MiddleMouseButtonClosesTab)
&& d->m_dockWidget->features().testFlag(DockWidget::DockWidgetClosable)) { && d->m_dockWidget->features().testFlag(DockWidget::DockWidgetClosable)) {

View File

@@ -41,16 +41,6 @@ const QString &Workspace::name() const
return m_name; return m_name;
} }
void Workspace::setLocked(bool value)
{
m_locked = value;
}
bool Workspace::isLocked() const
{
return m_locked;
}
const Utils::FilePath &Workspace::filePath() const const Utils::FilePath &Workspace::filePath() const
{ {
return m_filePath; return m_filePath;

View File

@@ -18,9 +18,6 @@ public:
void setName(const QString &name); void setName(const QString &name);
const QString &name() const; const QString &name() const;
void setLocked(bool value);
bool isLocked() const;
const Utils::FilePath &filePath() const; const Utils::FilePath &filePath() const;
QString fileName() const; QString fileName() const;
@@ -53,7 +50,6 @@ private:
QString m_name; QString m_name;
Utils::FilePath m_filePath; Utils::FilePath m_filePath;
bool m_preset = false; bool m_preset = false;
bool m_locked = false;
}; };
} // namespace ADS } // namespace ADS

View File

@@ -1485,8 +1485,10 @@ QList<const CppComponentValue *> CppQmlTypes::createObjectsForImport(const QStri
// if it already exists, skip // if it already exists, skip
const QString key = qualifiedName(package, fmo->className(), version); const QString key = qualifiedName(package, fmo->className(), version);
if (m_objectsByQualifiedName.contains(key)) if (m_objectsByQualifiedName.contains(key)) {
exportedObjects.insert(key, m_objectsByQualifiedName.value(key));
continue; continue;
}
ComponentVersion cppVersion; ComponentVersion cppVersion;
for (const FakeMetaObject::Export &bestExport : std::as_const(bestExports)) { for (const FakeMetaObject::Export &bestExport : std::as_const(bestExports)) {

View File

@@ -9,6 +9,7 @@
#include "cmaketoolmanager.h" #include "cmaketoolmanager.h"
#include <coreplugin/dialogs/ioptionspage.h> #include <coreplugin/dialogs/ioptionspage.h>
#include <coreplugin/icore.h>
#include <projectexplorer/projectexplorerconstants.h> #include <projectexplorer/projectexplorerconstants.h>
#include <utils/detailswidget.h> #include <utils/detailswidget.h>
@@ -457,6 +458,9 @@ void CMakeToolItemConfigWidget::onBinaryPathEditingFinished()
void CMakeToolItemConfigWidget::updateQchFilePath() void CMakeToolItemConfigWidget::updateQchFilePath()
{ {
// QDS does not want automatic detection of cmake help file
if (Core::ICore::isQtDesignStudio())
return;
if (m_qchFileChooser->filePath().isEmpty()) if (m_qchFileChooser->filePath().isEmpty())
m_qchFileChooser->setFilePath(CMakeTool::searchQchFile(m_binaryChooser->filePath())); m_qchFileChooser->setFilePath(CMakeTool::searchQchFile(m_binaryChooser->filePath()));
} }

View File

@@ -14,7 +14,8 @@
namespace EffectMaker { namespace EffectMaker {
CompositionNode::CompositionNode(const QString &effectName, const QString &qenPath, const QJsonObject &jsonObject) CompositionNode::CompositionNode(const QString &effectName, const QString &qenPath,
const QJsonObject &jsonObject)
{ {
QJsonObject json; QJsonObject json;
if (jsonObject.isEmpty()) { if (jsonObject.isEmpty()) {
@@ -58,6 +59,11 @@ QString CompositionNode::description() const
return m_description; return m_description;
} }
QString CompositionNode::id() const
{
return m_id;
}
QObject *CompositionNode::uniformsModel() QObject *CompositionNode::uniformsModel()
{ {
return &m_unifomrsModel; return &m_unifomrsModel;
@@ -81,6 +87,11 @@ void CompositionNode::setIsEnabled(bool newIsEnabled)
} }
} }
bool CompositionNode::isDependency() const
{
return m_refCount > 0;
}
CompositionNode::NodeType CompositionNode::type() const CompositionNode::NodeType CompositionNode::type() const
{ {
return m_type; return m_type;
@@ -102,6 +113,13 @@ void CompositionNode::parse(const QString &effectName, const QString &qenPath, c
m_fragmentCode = EffectUtils::codeFromJsonArray(json.value("fragmentCode").toArray()); m_fragmentCode = EffectUtils::codeFromJsonArray(json.value("fragmentCode").toArray());
m_vertexCode = EffectUtils::codeFromJsonArray(json.value("vertexCode").toArray()); m_vertexCode = EffectUtils::codeFromJsonArray(json.value("vertexCode").toArray());
m_id = json.value("id").toString();
if (m_id.isEmpty() && !qenPath.isEmpty()) {
QString fileName = qenPath.split('/').last();
fileName.chop(4); // remove ".qen"
m_id = fileName;
}
// parse properties // parse properties
QJsonArray jsonProps = json.value("properties").toArray(); QJsonArray jsonProps = json.value("properties").toArray();
for (const auto /*QJsonValueRef*/ &prop : jsonProps) { for (const auto /*QJsonValueRef*/ &prop : jsonProps) {
@@ -118,8 +136,7 @@ void CompositionNode::parse(const QString &effectName, const QString &qenPath, c
for (const QString &codeLine : std::as_const(shaderCodeLines)) { for (const QString &codeLine : std::as_const(shaderCodeLines)) {
QString trimmedLine = codeLine.trimmed(); QString trimmedLine = codeLine.trimmed();
if (trimmedLine.startsWith("@requires")) { if (trimmedLine.startsWith("@requires")) {
// Get the required node, remove "@requires" // Get the required node, remove "@requires "
QString l = trimmedLine.sliced(9).trimmed();
QString nodeName = trimmedLine.sliced(10); QString nodeName = trimmedLine.sliced(10);
if (!nodeName.isEmpty() && !m_requiredNodes.contains(nodeName)) if (!nodeName.isEmpty() && !m_requiredNodes.contains(nodeName))
m_requiredNodes << nodeName; m_requiredNodes << nodeName;
@@ -132,6 +149,36 @@ QList<Uniform *> CompositionNode::uniforms() const
return m_uniforms; return m_uniforms;
} }
int CompositionNode::incRefCount()
{
++m_refCount;
if (m_refCount == 1)
emit isDepencyChanged();
return m_refCount;
}
int CompositionNode::decRefCount()
{
--m_refCount;
if (m_refCount == 0)
emit isDepencyChanged();
return m_refCount;
}
void CompositionNode::setRefCount(int count)
{
bool notifyChange = (m_refCount > 0 && count == 0) || (m_refCount <= 0 && count > 0);
m_refCount = count;
if (notifyChange)
emit isDepencyChanged();
}
QString CompositionNode::name() const QString CompositionNode::name() const
{ {
return m_name; return m_name;

View File

@@ -16,6 +16,7 @@ class CompositionNode : public QObject
Q_PROPERTY(QString nodeName MEMBER m_name CONSTANT) Q_PROPERTY(QString nodeName MEMBER m_name CONSTANT)
Q_PROPERTY(bool nodeEnabled READ isEnabled WRITE setIsEnabled NOTIFY isEnabledChanged) Q_PROPERTY(bool nodeEnabled READ isEnabled WRITE setIsEnabled NOTIFY isEnabledChanged)
Q_PROPERTY(bool isDependency READ isDependency NOTIFY isDepencyChanged)
Q_PROPERTY(QObject *nodeUniformsModel READ uniformsModel NOTIFY uniformsModelChanged) Q_PROPERTY(QObject *nodeUniformsModel READ uniformsModel NOTIFY uniformsModelChanged)
public: public:
@@ -30,6 +31,7 @@ public:
QString fragmentCode() const; QString fragmentCode() const;
QString vertexCode() const; QString vertexCode() const;
QString description() const; QString description() const;
QString id() const;
QObject *uniformsModel(); QObject *uniformsModel();
@@ -40,13 +42,20 @@ public:
bool isEnabled() const; bool isEnabled() const;
void setIsEnabled(bool newIsEnabled); void setIsEnabled(bool newIsEnabled);
bool isDependency() const;
QString name() const; QString name() const;
QList<Uniform *> uniforms() const; QList<Uniform *> uniforms() const;
int incRefCount();
int decRefCount();
void setRefCount(int count);
signals: signals:
void uniformsModelChanged(); void uniformsModelChanged();
void isEnabledChanged(); void isEnabledChanged();
void isDepencyChanged();
private: private:
void parse(const QString &effectName, const QString &qenPath, const QJsonObject &json); void parse(const QString &effectName, const QString &qenPath, const QJsonObject &json);
@@ -57,7 +66,9 @@ private:
QString m_vertexCode; QString m_vertexCode;
QString m_description; QString m_description;
QStringList m_requiredNodes; QStringList m_requiredNodes;
QString m_id;
bool m_isEnabled = true; bool m_isEnabled = true;
int m_refCount = 0;
QList<Uniform*> m_uniforms; QList<Uniform*> m_uniforms;

View File

@@ -4,6 +4,8 @@
#include "effectmakermodel.h" #include "effectmakermodel.h"
#include "compositionnode.h" #include "compositionnode.h"
#include "effectutils.h"
#include "propertyhandler.h"
#include "syntaxhighlighterdata.h" #include "syntaxhighlighterdata.h"
#include "uniform.h" #include "uniform.h"
@@ -21,6 +23,7 @@
#include <modelnodeoperations.h> #include <modelnodeoperations.h>
#include <QByteArrayView> #include <QByteArrayView>
#include <QLibraryInfo>
#include <QVector2D> #include <QVector2D>
namespace EffectMaker { namespace EffectMaker {
@@ -57,6 +60,7 @@ QHash<int, QByteArray> EffectMakerModel::roleNames() const
roles[NameRole] = "nodeName"; roles[NameRole] = "nodeName";
roles[EnabledRole] = "nodeEnabled"; roles[EnabledRole] = "nodeEnabled";
roles[UniformsRole] = "nodeUniformsModel"; roles[UniformsRole] = "nodeUniformsModel";
roles[Dependency] = "isDependency";
return roles; return roles;
} }
@@ -103,14 +107,45 @@ void EffectMakerModel::setIsEmpty(bool val)
void EffectMakerModel::addNode(const QString &nodeQenPath) void EffectMakerModel::addNode(const QString &nodeQenPath)
{ {
beginInsertRows({}, m_nodes.size(), m_nodes.size()); beginResetModel();
auto *node = new CompositionNode("", nodeQenPath); auto *node = new CompositionNode({}, nodeQenPath);
connect(qobject_cast<EffectMakerUniformsModel *>(node->uniformsModel()),
&EffectMakerUniformsModel::dataChanged, this, [this] {
setHasUnsavedChanges(true);
});
const QList<QString> requiredNodes = node->requiredNodes();
if (requiredNodes.size() > 0) {
for (const QString &requiredId : requiredNodes) {
if (auto reqNode = findNodeById(requiredId)) {
reqNode->incRefCount();
continue;
}
const QString path = EffectUtils::nodesSourcesPath() + "/common/" + requiredId + ".qen";
auto requiredNode = new CompositionNode({}, path);
requiredNode->setRefCount(1);
m_nodes.prepend(requiredNode);
}
}
m_nodes.append(node); m_nodes.append(node);
endInsertRows(); endResetModel();
setIsEmpty(false); setIsEmpty(false);
bakeShaders(); bakeShaders();
setHasUnsavedChanges(true);
emit nodesChanged();
}
CompositionNode *EffectMakerModel::findNodeById(const QString &id) const
{
for (CompositionNode *node : std::as_const(m_nodes)) {
if (node->id() == id)
return node;
}
return {};
} }
void EffectMakerModel::moveNode(int fromIdx, int toIdx) void EffectMakerModel::moveNode(int fromIdx, int toIdx)
@@ -123,21 +158,67 @@ void EffectMakerModel::moveNode(int fromIdx, int toIdx)
m_nodes.move(fromIdx, toIdx); m_nodes.move(fromIdx, toIdx);
endMoveRows(); endMoveRows();
setHasUnsavedChanges(true);
bakeShaders(); bakeShaders();
} }
void EffectMakerModel::removeNode(int idx) void EffectMakerModel::removeNode(int idx)
{ {
beginRemoveRows({}, idx, idx); beginResetModel();
CompositionNode *node = m_nodes.at(idx); CompositionNode *node = m_nodes.takeAt(idx);
m_nodes.removeAt(idx);
const QStringList reqNodes = node->requiredNodes();
for (const QString &reqId : reqNodes) {
CompositionNode *depNode = findNodeById(reqId);
if (depNode && depNode->decRefCount() <= 0) {
m_nodes.removeOne(depNode);
delete depNode;
}
}
delete node; delete node;
endRemoveRows(); endResetModel();
if (m_nodes.isEmpty()) if (m_nodes.isEmpty())
setIsEmpty(true); setIsEmpty(true);
else else
bakeShaders(); bakeShaders();
setHasUnsavedChanges(true);
emit nodesChanged();
}
void EffectMakerModel::clear()
{
beginResetModel();
qDeleteAll(m_nodes);
m_nodes.clear();
endResetModel();
setHasUnsavedChanges(!m_currentComposition.isEmpty());
setCurrentComposition("");
setIsEmpty(true);
emit nodesChanged();
}
void EffectMakerModel::assignToSelected()
{
const QString effectsAssetsDir = QmlDesigner::ModelNodeOperations::getEffectsDefaultDirectory();
const QString path = effectsAssetsDir + QDir::separator() + m_currentComposition + ".qep";
emit assignToSelectedTriggered(path);
}
QString EffectMakerModel::getUniqueEffectName() const
{
const QString effectsDir = QmlDesigner::ModelNodeOperations::getEffectsDefaultDirectory();
const QString path = effectsDir + QDir::separator() + "Effect%1.qep";
int num = 0;
while (QFile::exists(path.arg(++num, 2, 10, QChar('0'))))
; // empty body
return QString("Effect%1").arg(num, 2, 10, QChar('0'));
} }
QString EffectMakerModel::fragmentShader() const QString EffectMakerModel::fragmentShader() const
@@ -171,25 +252,7 @@ const QString &EffectMakerModel::qmlComponentString() const
return m_qmlComponentString; return m_qmlComponentString;
} }
void EffectMakerModel::clear() const QList<Uniform *> EffectMakerModel::allUniforms() const
{
if (m_nodes.isEmpty())
return;
beginRemoveRows({}, 0, m_nodes.count());
for (CompositionNode *node : std::as_const(m_nodes))
delete node;
m_nodes.clear();
endRemoveRows();
setIsEmpty(true);
bakeShaders();
}
const QList<Uniform *> EffectMakerModel::allUniforms()
{ {
QList<Uniform *> uniforms = {}; QList<Uniform *> uniforms = {};
for (const auto &node : std::as_const(m_nodes)) for (const auto &node : std::as_const(m_nodes))
@@ -416,6 +479,8 @@ QJsonObject nodeToJson(const CompositionNode &node)
nodeObject.insert("description", node.description()); nodeObject.insert("description", node.description());
nodeObject.insert("enabled", node.isEnabled()); nodeObject.insert("enabled", node.isEnabled());
nodeObject.insert("version", 1); nodeObject.insert("version", 1);
nodeObject.insert("id", node.id());
// Add properties // Add properties
QJsonArray propertiesArray; QJsonArray propertiesArray;
const QList<Uniform *> uniforms = node.uniforms(); const QList<Uniform *> uniforms = node.uniforms();
@@ -523,14 +588,24 @@ QString EffectMakerModel::getQmlEffectString()
s += '\n'; s += '\n';
} }
//TODO: Blue stuff goes here if (m_shaderFeatures.enabled(ShaderFeatures::BlurSources)) {
s += " BlurHelper {\n";
s += " id: blurHelper\n";
s += " anchors.fill: parent\n";
int blurMax = 32;
if (g_propertyData.contains("BLUR_HELPER_MAX_LEVEL"))
blurMax = g_propertyData["BLUR_HELPER_MAX_LEVEL"].toInt();
s += QString(" property int blurMax: %1\n").arg(blurMax);
s += " property real blurMultiplier: rootItem.blurMultiplier\n";
s += " }\n";
}
s += getQmlComponentString(true); s += getQmlComponentString(true);
s += "}\n"; s += "}\n";
return s; return s;
} }
void EffectMakerModel::exportComposition(const QString &name) void EffectMakerModel::saveComposition(const QString &name)
{ {
const QString effectsAssetsDir = QmlDesigner::ModelNodeOperations::getEffectsDefaultDirectory(); const QString effectsAssetsDir = QmlDesigner::ModelNodeOperations::getEffectsDefaultDirectory();
const QString path = effectsAssetsDir + QDir::separator() + name + ".qep"; const QString path = effectsAssetsDir + QDir::separator() + name + ".qep";
@@ -561,12 +636,19 @@ void EffectMakerModel::exportComposition(const QString &name)
saveFile.write(jsonDoc.toJson()); saveFile.write(jsonDoc.toJson());
saveFile.close(); saveFile.close();
setCurrentComposition(name);
setHasUnsavedChanges(false);
saveResources(name);
} }
void EffectMakerModel::openComposition(const QString &path) void EffectMakerModel::openComposition(const QString &path)
{ {
clear(); clear();
const QString effectName = QFileInfo(path).baseName();
setCurrentComposition(effectName);
QFile compFile(path); QFile compFile(path);
if (!compFile.open(QIODevice::ReadOnly)) { if (!compFile.open(QIODevice::ReadOnly)) {
QString error = QString("Couldn't open composition file: '%1'").arg(path); QString error = QString("Couldn't open composition file: '%1'").arg(path);
@@ -576,6 +658,10 @@ void EffectMakerModel::openComposition(const QString &path)
} }
QByteArray data = compFile.readAll(); QByteArray data = compFile.readAll();
if (data.isEmpty())
return;
QJsonParseError parseError; QJsonParseError parseError;
QJsonDocument jsonDoc(QJsonDocument::fromJson(data, &parseError)); QJsonDocument jsonDoc(QJsonDocument::fromJson(data, &parseError));
if (parseError.error != QJsonParseError::NoError) { if (parseError.error != QJsonParseError::NoError) {
@@ -605,28 +691,40 @@ void EffectMakerModel::openComposition(const QString &path)
return; return;
} }
// Get effects dir
const QString effectName = QFileInfo(path).baseName();
const Utils::FilePath effectsResDir = QmlDesigner::ModelNodeOperations::getEffectsImportDirectory();
const QString effectsResPath = effectsResDir.pathAppended(effectName).toString();
if (json.contains("nodes") && json["nodes"].isArray()) { if (json.contains("nodes") && json["nodes"].isArray()) {
beginResetModel();
QHash<QString, int> refCounts;
const QJsonArray nodesArray = json["nodes"].toArray(); const QJsonArray nodesArray = json["nodes"].toArray();
for (const auto &nodeElement : nodesArray) { for (const auto &nodeElement : nodesArray) {
beginInsertRows({}, m_nodes.size(), m_nodes.size()); auto *node = new CompositionNode(effectName, {}, nodeElement.toObject());
auto *node = new CompositionNode(effectName, "", nodeElement.toObject()); connect(qobject_cast<EffectMakerUniformsModel *>(node->uniformsModel()),
&EffectMakerUniformsModel::dataChanged, this, [this] {
setHasUnsavedChanges(true);
});
m_nodes.append(node); m_nodes.append(node);
endInsertRows(); const QStringList reqIds = node->requiredNodes();
for (const QString &reqId : reqIds)
++refCounts[reqId];
} }
for (auto it = refCounts.cbegin(), end = refCounts.cend(); it != end; ++it) {
CompositionNode *depNode = findNodeById(it.key());
if (depNode)
depNode->setRefCount(it.value());
}
endResetModel();
setIsEmpty(m_nodes.isEmpty()); setIsEmpty(m_nodes.isEmpty());
bakeShaders(); bakeShaders();
} }
setCurrentComposition(effectName); setHasUnsavedChanges(false);
emit nodesChanged();
} }
void EffectMakerModel::exportResources(const QString &name) void EffectMakerModel::saveResources(const QString &name)
{ {
// Make sure that uniforms are up-to-date // Make sure that uniforms are up-to-date
updateCustomUniforms(); updateCustomUniforms();
@@ -692,7 +790,7 @@ void EffectMakerModel::exportResources(const QString &name)
QString qmlFilePath = effectsResPath + qmlFilename; QString qmlFilePath = effectsResPath + qmlFilename;
writeToFile(qmlString.toUtf8(), qmlFilePath, FileType::Text); writeToFile(qmlString.toUtf8(), qmlFilePath, FileType::Text);
// Export shaders and images // Save shaders and images
QStringList sources = {m_vertexShaderFilename, m_fragmentShaderFilename}; QStringList sources = {m_vertexShaderFilename, m_fragmentShaderFilename};
QStringList dests = {vsFilename, fsFilename}; QStringList dests = {vsFilename, fsFilename};
@@ -702,11 +800,28 @@ void EffectMakerModel::exportResources(const QString &name)
QString imagePath = uniform->value().toString(); QString imagePath = uniform->value().toString();
QFileInfo fi(imagePath); QFileInfo fi(imagePath);
QString imageFilename = fi.fileName(); QString imageFilename = fi.fileName();
sources.append(imagePath.remove(0, 7)); // Removes "file://" if (imagePath.startsWith("file:")) {
QUrl url(imagePath);
imagePath = url.toLocalFile();
}
sources.append(imagePath);
dests.append(imageFilename); dests.append(imageFilename);
} }
} }
if (m_shaderFeatures.enabled(ShaderFeatures::BlurSources)) {
QString blurHelperFilename("BlurHelper.qml");
QString blurFsFilename("bluritems.frag.qsb");
QString blurVsFilename("bluritems.vert.qsb");
QString blurHelperPath(EffectUtils::nodesSourcesPath() + "/common/");
sources.append(blurHelperPath + blurHelperFilename);
sources.append(blurHelperPath + blurFsFilename);
sources.append(blurHelperPath + blurVsFilename);
dests.append(blurHelperFilename);
dests.append(blurFsFilename);
dests.append(blurVsFilename);
}
for (int i = 0; i < sources.count(); ++i) { for (int i = 0; i < sources.count(); ++i) {
Utils::FilePath source = Utils::FilePath::fromString(sources[i]); Utils::FilePath source = Utils::FilePath::fromString(sources[i]);
Utils::FilePath target = Utils::FilePath::fromString(effectsResPath + dests[i]); Utils::FilePath target = Utils::FilePath::fromString(effectsResPath + dests[i]);
@@ -716,6 +831,8 @@ void EffectMakerModel::exportResources(const QString &name)
if (!source.copyFile(target)) if (!source.copyFile(target))
qWarning() << __FUNCTION__ << " Failed to copy file: " << source; qWarning() << __FUNCTION__ << " Failed to copy file: " << source;
} }
emit resourcesSaved(QString("Effects.%1.%1").arg(name).toUtf8(), effectPath);
} }
void EffectMakerModel::resetEffectError(int type) void EffectMakerModel::resetEffectError(int type)
@@ -737,16 +854,18 @@ QString EffectMakerModel::valueAsString(const Uniform &uniform)
return QString::number(uniform.value().toDouble()); return QString::number(uniform.value().toDouble());
} else if (uniform.type() == Uniform::Type::Vec2) { } else if (uniform.type() == Uniform::Type::Vec2) {
QVector2D v2 = uniform.value().value<QVector2D>(); QVector2D v2 = uniform.value().value<QVector2D>();
return QString("Qt.point(%1, %2)").arg(v2.x(), v2.y()); return QString("Qt.point(%1, %2)").arg(v2.x()).arg(v2.y());
} else if (uniform.type() == Uniform::Type::Vec3) { } else if (uniform.type() == Uniform::Type::Vec3) {
QVector3D v3 = uniform.value().value<QVector3D>(); QVector3D v3 = uniform.value().value<QVector3D>();
return QString("Qt.vector3d(%1, %2, %3)").arg(v3.x(), v3.y(), v3.z()); return QString("Qt.vector3d(%1, %2, %3)").arg(v3.x()).arg(v3.y()).arg(v3.z());
} else if (uniform.type() == Uniform::Type::Vec4) { } else if (uniform.type() == Uniform::Type::Vec4) {
QVector4D v4 = uniform.value().value<QVector4D>(); QVector4D v4 = uniform.value().value<QVector4D>();
return QString("Qt.vector4d(%1, %2, %3, %4)").arg(v4.x(), v4.y(), v4.z(), v4.w()); return QString("Qt.vector4d(%1, %2, %3, %4)").arg(v4.x()).arg(v4.y()).arg(v4.z()).arg(v4.w());
} else if (uniform.type() == Uniform::Type::Sampler) { } else if (uniform.type() == Uniform::Type::Sampler) {
return getImageElementName(uniform); return getImageElementName(uniform);
} else if (uniform.type() == Uniform::Type::Define || uniform.type() == Uniform::Type::Color) { } else if (uniform.type() == Uniform::Type::Color) {
return QString("\"%1\"").arg(uniform.value().toString());
} else if (uniform.type() == Uniform::Type::Define) {
return uniform.value().toString(); return uniform.value().toString();
} else { } else {
qWarning() << QString("Unhandled const variable type: %1").arg(int(uniform.type())).toLatin1(); qWarning() << QString("Unhandled const variable type: %1").arg(int(uniform.type())).toLatin1();
@@ -1085,18 +1204,25 @@ QString EffectMakerModel::generateFragmentShader(bool includeUniforms)
return s; return s;
} }
void EffectMakerModel::handleQsbProcessExit(Utils::Process *qsbProcess, const QString &shader) void EffectMakerModel::handleQsbProcessExit(Utils::Process *qsbProcess, const QString &shader, bool preview)
{ {
--m_remainingQsbTargets; --m_remainingQsbTargets;
const QString errStr = qsbProcess->errorString(); const QString errStr = qsbProcess->errorString();
const QByteArray errStd = qsbProcess->readAllRawStandardError(); const QByteArray errStd = qsbProcess->readAllRawStandardError();
if (!errStr.isEmpty()) QString previewStr;
qWarning() << QString("Failed to generate QSB file for: %1 %2").arg(shader, errStr); if (preview)
previewStr = QStringLiteral("preview");
if (!errStd.isEmpty()) if (!errStr.isEmpty()) {
qWarning() << QString("Failed to generate QSB file for: %1 %2") qWarning() << QString("Failed to generate %3 QSB file for: %1 %2")
.arg(shader, QString::fromUtf8(errStd)); .arg(shader, errStr, previewStr);
}
if (!errStd.isEmpty()) {
qWarning() << QString("Failed to generate %3 QSB file for: %1 %2")
.arg(shader, QString::fromUtf8(errStd), previewStr);
}
if (m_remainingQsbTargets <= 0) { if (m_remainingQsbTargets <= 0) {
Q_EMIT shadersBaked(); Q_EMIT shadersBaked();
@@ -1182,21 +1308,30 @@ void EffectMakerModel::createFiles()
QFile(m_vertexShaderFilename).remove(); QFile(m_vertexShaderFilename).remove();
if (QFileInfo(m_fragmentShaderFilename).exists()) if (QFileInfo(m_fragmentShaderFilename).exists())
QFile(m_fragmentShaderFilename).remove(); QFile(m_fragmentShaderFilename).remove();
if (QFileInfo(m_vertexShaderPreviewFilename).exists())
QFile(m_vertexShaderPreviewFilename).remove();
if (QFileInfo(m_fragmentShaderPreviewFilename).exists())
QFile(m_fragmentShaderPreviewFilename).remove();
auto vertexShaderFile = QTemporaryFile(QDir::tempPath() + "/dsem_XXXXXX.vert.qsb"); auto vertexShaderFile = QTemporaryFile(QDir::tempPath() + "/dsem_XXXXXX.vert.qsb");
auto fragmentShaderFile = QTemporaryFile(QDir::tempPath() + "/dsem_XXXXXX.frag.qsb"); auto fragmentShaderFile = QTemporaryFile(QDir::tempPath() + "/dsem_XXXXXX.frag.qsb");
auto vertexShaderPreviewFile = QTemporaryFile(QDir::tempPath() + "/dsem_prev_XXXXXX.vert.qsb");
auto fragmentShaderPreviewFile = QTemporaryFile(QDir::tempPath() + "/dsem_prev_XXXXXX.frag.qsb");
m_vertexSourceFile.setFileTemplate(QDir::tempPath() + "/dsem_XXXXXX.vert"); m_vertexSourceFile.setFileTemplate(QDir::tempPath() + "/dsem_XXXXXX.vert");
m_fragmentSourceFile.setFileTemplate(QDir::tempPath() + "/dsem_XXXXXX.frag"); m_fragmentSourceFile.setFileTemplate(QDir::tempPath() + "/dsem_XXXXXX.frag");
if (!m_vertexSourceFile.open() || !m_fragmentSourceFile.open() if (!m_vertexSourceFile.open() || !m_fragmentSourceFile.open()
|| !vertexShaderFile.open() || !fragmentShaderFile.open()) { || !vertexShaderFile.open() || !fragmentShaderFile.open()
|| !vertexShaderPreviewFile.open() || !fragmentShaderPreviewFile.open()) {
qWarning() << "Unable to open temporary files"; qWarning() << "Unable to open temporary files";
} else { } else {
m_vertexSourceFilename = m_vertexSourceFile.fileName(); m_vertexSourceFilename = m_vertexSourceFile.fileName();
m_fragmentSourceFilename = m_fragmentSourceFile.fileName(); m_fragmentSourceFilename = m_fragmentSourceFile.fileName();
m_vertexShaderFilename = vertexShaderFile.fileName(); m_vertexShaderFilename = vertexShaderFile.fileName();
m_fragmentShaderFilename = fragmentShaderFile.fileName(); m_fragmentShaderFilename = fragmentShaderFile.fileName();
m_vertexShaderPreviewFilename = vertexShaderPreviewFile.fileName();
m_fragmentShaderPreviewFilename = fragmentShaderPreviewFile.fileName();
} }
} }
@@ -1243,13 +1378,24 @@ void EffectMakerModel::bakeShaders()
Utils::FilePath qsbPath = qtVer->binPath().pathAppended("qsb").withExecutableSuffix(); Utils::FilePath qsbPath = qtVer->binPath().pathAppended("qsb").withExecutableSuffix();
if (!qsbPath.exists()) { if (!qsbPath.exists()) {
qWarning() << failMessage << "QSB tool not found"; qWarning() << failMessage << "QSB tool for target kit not found";
return;
}
Utils::FilePath binPath = Utils::FilePath::fromString(
QLibraryInfo::path(QLibraryInfo::BinariesPath));
Utils::FilePath qsbPrevPath = binPath.pathAppended("qsb").withExecutableSuffix();
if (!qsbPrevPath.exists()) {
qWarning() << failMessage << "QSB tool for preview shaders not found";
return; return;
} }
m_remainingQsbTargets = 2; // We only have 2 shaders m_remainingQsbTargets = 2; // We only have 2 shaders
const QStringList srcPaths = {m_vertexSourceFilename, m_fragmentSourceFilename}; const QStringList srcPaths = {m_vertexSourceFilename, m_fragmentSourceFilename};
const QStringList outPaths = {m_vertexShaderFilename, m_fragmentShaderFilename}; const QStringList outPaths = {m_vertexShaderFilename, m_fragmentShaderFilename};
const QStringList outPrevPaths = {m_vertexShaderPreviewFilename, m_fragmentShaderPreviewFilename};
auto runQsb = [this, srcPaths](const Utils::FilePath &qsbPath, const QStringList &outPaths, bool preview) {
for (int i = 0; i < 2; ++i) { for (int i = 0; i < 2; ++i) {
const auto workDir = Utils::FilePath::fromString(outPaths[i]); const auto workDir = Utils::FilePath::fromString(outPaths[i]);
// TODO: Optional legacy glsl support like standalone effect maker needs to add "100es,120" // TODO: Optional legacy glsl support like standalone effect maker needs to add "100es,120"
@@ -1258,12 +1404,17 @@ void EffectMakerModel::bakeShaders()
auto qsbProcess = new Utils::Process(this); auto qsbProcess = new Utils::Process(this);
connect(qsbProcess, &Utils::Process::done, this, [=] { connect(qsbProcess, &Utils::Process::done, this, [=] {
handleQsbProcessExit(qsbProcess, srcPaths[i]); handleQsbProcessExit(qsbProcess, srcPaths[i], preview);
}); });
qsbProcess->setWorkingDirectory(workDir.absolutePath()); qsbProcess->setWorkingDirectory(workDir.absolutePath());
qsbProcess->setCommand({qsbPath, args}); qsbProcess->setCommand({qsbPath, args});
qsbProcess->start(); qsbProcess->start();
} }
};
runQsb(qsbPath, outPaths, false);
runQsb(qsbPrevPath, outPrevPaths, true);
} }
bool EffectMakerModel::shadersUpToDate() const bool EffectMakerModel::shadersUpToDate() const
@@ -1375,8 +1526,10 @@ QString EffectMakerModel::getQmlComponentString(bool localFiles)
s += '\n' + customImagesString; s += '\n' + customImagesString;
s += '\n'; s += '\n';
s += l2 + "vertexShader: 'file:///" + m_vertexShaderFilename + "'\n"; const QString vertFile = localFiles ? m_vertexShaderFilename : m_vertexShaderPreviewFilename;
s += l2 + "fragmentShader: 'file:///" + m_fragmentShaderFilename + "'\n"; const QString fragFile = localFiles ? m_fragmentShaderFilename : m_fragmentShaderPreviewFilename;
s += l2 + "vertexShader: 'file:///" + vertFile + "'\n";
s += l2 + "fragmentShader: 'file:///" + fragFile + "'\n";
s += l2 + "anchors.fill: parent\n"; s += l2 + "anchors.fill: parent\n";
if (m_shaderFeatures.enabled(ShaderFeatures::GridMesh)) { if (m_shaderFeatures.enabled(ShaderFeatures::GridMesh)) {
QString gridSize = QString("%1, %2").arg(m_shaderFeatures.gridMeshWidth()) QString gridSize = QString("%1, %2").arg(m_shaderFeatures.gridMeshWidth())
@@ -1398,10 +1551,41 @@ void EffectMakerModel::setCurrentComposition(const QString &newCurrentCompositio
{ {
if (m_currentComposition == newCurrentComposition) if (m_currentComposition == newCurrentComposition)
return; return;
m_currentComposition = newCurrentComposition; m_currentComposition = newCurrentComposition;
emit currentCompositionChanged(); emit currentCompositionChanged();
} }
bool EffectMakerModel::hasUnsavedChanges() const
{
return m_hasUnsavedChanges;
}
void EffectMakerModel::setHasUnsavedChanges(bool val)
{
if (m_hasUnsavedChanges == val)
return;
m_hasUnsavedChanges = val;
emit hasUnsavedChangesChanged();
}
QStringList EffectMakerModel::uniformNames() const
{
QStringList usedList;
const QList<Uniform *> uniforms = allUniforms();
for (const auto uniform : uniforms)
usedList.append(uniform->name());
return usedList;
}
bool EffectMakerModel::isDependencyNode(int index) const
{
if (m_nodes.size() > index)
return m_nodes[index]->isDependency();
return false;
}
void EffectMakerModel::updateQmlComponent() void EffectMakerModel::updateQmlComponent()
{ {
// Clear possible QML runtime errors // Clear possible QML runtime errors

View File

@@ -7,10 +7,10 @@
#include <utils/filepath.h> #include <utils/filepath.h>
#include <QAbstractListModel>
#include <QFileSystemWatcher> #include <QFileSystemWatcher>
#include <QMap> #include <QMap>
#include <QRegularExpression> #include <QRegularExpression>
#include <QStandardItemModel>
#include <QTemporaryFile> #include <QTemporaryFile>
namespace ProjectExplorer { namespace ProjectExplorer {
@@ -44,6 +44,7 @@ class EffectMakerModel : public QAbstractListModel
Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged)
Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged) Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged)
Q_PROPERTY(bool hasUnsavedChanges MEMBER m_hasUnsavedChanges WRITE setHasUnsavedChanges NOTIFY hasUnsavedChangesChanged)
Q_PROPERTY(bool shadersUpToDate READ shadersUpToDate WRITE setShadersUpToDate NOTIFY shadersUpToDateChanged) Q_PROPERTY(bool shadersUpToDate READ shadersUpToDate WRITE setShadersUpToDate NOTIFY shadersUpToDateChanged)
Q_PROPERTY(QString qmlComponentString READ qmlComponentString) Q_PROPERTY(QString qmlComponentString READ qmlComponentString)
Q_PROPERTY(QString currentComposition READ currentComposition WRITE setCurrentComposition NOTIFY currentCompositionChanged) Q_PROPERTY(QString currentComposition READ currentComposition WRITE setCurrentComposition NOTIFY currentCompositionChanged)
@@ -61,8 +62,13 @@ public:
void addNode(const QString &nodeQenPath); void addNode(const QString &nodeQenPath);
CompositionNode *findNodeById(const QString &id) const;
Q_INVOKABLE void moveNode(int fromIdx, int toIdx); Q_INVOKABLE void moveNode(int fromIdx, int toIdx);
Q_INVOKABLE void removeNode(int idx); Q_INVOKABLE void removeNode(int idx);
Q_INVOKABLE void clear();
Q_INVOKABLE void assignToSelected();
Q_INVOKABLE QString getUniqueEffectName() const;
bool shadersUpToDate() const; bool shadersUpToDate() const;
void setShadersUpToDate(bool newShadersUpToDate); void setShadersUpToDate(bool newShadersUpToDate);
@@ -75,35 +81,43 @@ public:
const QString &qmlComponentString() const; const QString &qmlComponentString() const;
void clear();
Q_INVOKABLE void updateQmlComponent(); Q_INVOKABLE void updateQmlComponent();
Q_INVOKABLE void resetEffectError(int type); Q_INVOKABLE void resetEffectError(int type);
Q_INVOKABLE void setEffectError(const QString &errorMessage, int type = -1, int lineNumber = -1); Q_INVOKABLE void setEffectError(const QString &errorMessage, int type = -1, int lineNumber = -1);
Q_INVOKABLE void exportComposition(const QString &name); Q_INVOKABLE void saveComposition(const QString &name);
Q_INVOKABLE void exportResources(const QString &name);
void openComposition(const QString &path); void openComposition(const QString &path);
QString currentComposition() const; QString currentComposition() const;
void setCurrentComposition(const QString &newCurrentComposition); void setCurrentComposition(const QString &newCurrentComposition);
bool hasUnsavedChanges() const;
void setHasUnsavedChanges(bool val);
QStringList uniformNames() const;
Q_INVOKABLE bool isDependencyNode(int index) const;
signals: signals:
void isEmptyChanged(); void isEmptyChanged();
void selectedIndexChanged(int idx); void selectedIndexChanged(int idx);
void effectErrorChanged(); void effectErrorChanged();
void shadersUpToDateChanged(); void shadersUpToDateChanged();
void shadersBaked(); void shadersBaked();
void currentCompositionChanged(); void currentCompositionChanged();
void nodesChanged();
void resourcesSaved(const QByteArray &type, const Utils::FilePath &path);
void hasUnsavedChangesChanged();
void assignToSelectedTriggered(const QString &effectPath);
private: private:
enum Roles { enum Roles {
NameRole = Qt::UserRole + 1, NameRole = Qt::UserRole + 1,
EnabledRole, EnabledRole,
UniformsRole UniformsRole,
Dependency
}; };
enum ErrorTypes { enum ErrorTypes {
@@ -117,7 +131,7 @@ private:
bool isValidIndex(int idx) const; bool isValidIndex(int idx) const;
const QList<Uniform *> allUniforms(); const QList<Uniform *> allUniforms() const;
const QString getBufUniform(); const QString getBufUniform();
const QString getVSUniforms(); const QString getVSUniforms();
@@ -142,13 +156,14 @@ private:
QString getCustomShaderVaryings(bool outState); QString getCustomShaderVaryings(bool outState);
QString generateVertexShader(bool includeUniforms = true); QString generateVertexShader(bool includeUniforms = true);
QString generateFragmentShader(bool includeUniforms = true); QString generateFragmentShader(bool includeUniforms = true);
void handleQsbProcessExit(Utils::Process *qsbProcess, const QString &shader); void handleQsbProcessExit(Utils::Process *qsbProcess, const QString &shader, bool preview);
QString stripFileFromURL(const QString &urlString) const; QString stripFileFromURL(const QString &urlString) const;
QString getQmlEffectString(); QString getQmlEffectString();
void updateCustomUniforms(); void updateCustomUniforms();
void createFiles(); void createFiles();
void bakeShaders(); void bakeShaders();
void saveResources(const QString &name);
QString mipmapPropertyName(const QString &name) const; QString mipmapPropertyName(const QString &name) const;
QString getQmlImagesString(bool localFiles); QString getQmlImagesString(bool localFiles);
@@ -158,6 +173,7 @@ private:
int m_selectedIndex = -1; int m_selectedIndex = -1;
bool m_isEmpty = true; bool m_isEmpty = true;
bool m_hasUnsavedChanges = false;
// True when shaders haven't changed since last baking // True when shaders haven't changed since last baking
bool m_shadersUpToDate = true; bool m_shadersUpToDate = true;
int m_remainingQsbTargets = 0; int m_remainingQsbTargets = 0;
@@ -175,6 +191,8 @@ private:
QString m_vertexSourceFilename; QString m_vertexSourceFilename;
QString m_fragmentShaderFilename; QString m_fragmentShaderFilename;
QString m_vertexShaderFilename; QString m_vertexShaderFilename;
QString m_fragmentShaderPreviewFilename;
QString m_vertexShaderPreviewFilename;
// Used in exported QML, at root of the file // Used in exported QML, at root of the file
QString m_exportedRootPropertiesString; QString m_exportedRootPropertiesString;
// Used in exported QML, at ShaderEffect component of the file // Used in exported QML, at ShaderEffect component of the file

View File

@@ -2,7 +2,9 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "effectmakernodesmodel.h" #include "effectmakernodesmodel.h"
#include "effectutils.h"
#include <utils/filepath.h>
#include <utils/hostosinfo.h> #include <utils/hostosinfo.h>
#include <QCoreApplication> #include <QCoreApplication>
@@ -38,44 +40,21 @@ QVariant EffectMakerNodesModel::data(const QModelIndex &index, int role) const
return m_categories.at(index.row())->property(roleNames().value(role)); return m_categories.at(index.row())->property(roleNames().value(role));
} }
void EffectMakerNodesModel::findNodesPath()
{
if (m_nodesPath.exists() || m_probeNodesDir)
return;
QDir nodesDir;
if (!qEnvironmentVariable("EFFECT_MAKER_NODES_PATH").isEmpty())
nodesDir.setPath(qEnvironmentVariable("EFFECT_MAKER_NODES_PATH"));
else if (Utils::HostOsInfo::isMacHost())
nodesDir.setPath(QCoreApplication::applicationDirPath() + "/../Resources/effect_maker_nodes");
// search for nodesDir from exec dir and up
if (nodesDir.dirName() == ".") {
m_probeNodesDir = true; // probe only once
nodesDir.setPath(QCoreApplication::applicationDirPath());
while (!nodesDir.cd("effect_maker_nodes") && nodesDir.cdUp())
; // do nothing
if (nodesDir.dirName() != "effect_maker_nodes") // bundlePathDir not found
return;
}
m_nodesPath = Utils::FilePath::fromString(nodesDir.path());
}
void EffectMakerNodesModel::loadModel() void EffectMakerNodesModel::loadModel()
{ {
findNodesPath(); if (m_modelLoaded)
return;
if (!m_nodesPath.exists()) { auto nodesPath = Utils::FilePath::fromString(EffectUtils::nodesSourcesPath());
if (!nodesPath.exists()) {
qWarning() << __FUNCTION__ << "Effects not found."; qWarning() << __FUNCTION__ << "Effects not found.";
return; return;
} }
m_categories = {}; m_categories = {};
QDirIterator itCategories(m_nodesPath.toString(), QDir::Dirs | QDir::NoDotAndDotDot); QDirIterator itCategories(nodesPath.toString(), QDir::Dirs | QDir::NoDotAndDotDot);
while (itCategories.hasNext()) { while (itCategories.hasNext()) {
itCategories.next(); itCategories.next();
@@ -85,7 +64,7 @@ void EffectMakerNodesModel::loadModel()
QString catName = itCategories.fileName(); QString catName = itCategories.fileName();
QList<EffectNode *> effects = {}; QList<EffectNode *> effects = {};
Utils::FilePath categoryPath = m_nodesPath.resolvePath(itCategories.fileName()); Utils::FilePath categoryPath = nodesPath.resolvePath(itCategories.fileName());
QDirIterator itEffects(categoryPath.toString(), {"*.qen"}, QDir::Files); QDirIterator itEffects(categoryPath.toString(), {"*.qen"}, QDir::Files);
while (itEffects.hasNext()) { while (itEffects.hasNext()) {
itEffects.next(); itEffects.next();
@@ -102,6 +81,8 @@ void EffectMakerNodesModel::loadModel()
return a->name() < b->name(); return a->name() < b->name();
}); });
m_modelLoaded = true;
resetModel(); resetModel();
} }
@@ -111,5 +92,20 @@ void EffectMakerNodesModel::resetModel()
endResetModel(); endResetModel();
} }
} // namespace EffectMaker void EffectMakerNodesModel::updateCanBeAdded(const QStringList &uniforms)
{
for (const EffectNodesCategory *cat : std::as_const(m_categories)) {
const QList<EffectNode *> nodes = cat->nodes();
for (EffectNode *node : nodes) {
bool match = false;
for (const QString &uniform : uniforms) {
match = node->hasUniform(uniform);
if (match)
break;
}
node->setCanBeAdded(!match);
}
}
}
} // namespace EffectMaker

View File

@@ -5,8 +5,6 @@
#include "effectnodescategory.h" #include "effectnodescategory.h"
#include <utils/filepath.h>
#include <QStandardItemModel> #include <QStandardItemModel>
namespace EffectMaker { namespace EffectMaker {
@@ -32,12 +30,14 @@ public:
QList<EffectNodesCategory *> categories() const { return m_categories; } QList<EffectNodesCategory *> categories() const { return m_categories; }
void updateCanBeAdded(const QStringList &uniforms);
private: private:
void findNodesPath(); QString nodesSourcesPath() const;
QList<EffectNodesCategory *> m_categories; QList<EffectNodesCategory *> m_categories;
Utils::FilePath m_nodesPath;
bool m_probeNodesDir = false; bool m_probeNodesDir = false;
bool m_modelLoaded = false;
}; };
} // namespace EffectMaker } // namespace EffectMaker

View File

@@ -3,6 +3,7 @@
#include "effectmakerview.h" #include "effectmakerview.h"
#include <coreplugin/icore.h>
#include <extensionsystem/iplugin.h> #include <extensionsystem/iplugin.h>
#include <viewmanager.h> #include <viewmanager.h>
@@ -10,6 +11,14 @@
namespace EffectMaker { namespace EffectMaker {
static bool enableEffectMaker()
{
Utils::QtcSettings *settings = Core::ICore::settings();
const Utils::Key enableModelManagerKey = "QML/Designer/UseExperimentalFeatures44";
return settings->value(enableModelManagerKey, false).toBool();
}
class EffectMakerPlugin final : public ExtensionSystem::IPlugin class EffectMakerPlugin final : public ExtensionSystem::IPlugin
{ {
Q_OBJECT Q_OBJECT
@@ -17,11 +26,13 @@ class EffectMakerPlugin final : public ExtensionSystem::IPlugin
bool delayedInitialize() final bool delayedInitialize() final
{ {
auto designerPlugin = QmlDesigner::QmlDesignerPlugin::instance(); if (enableEffectMaker()) {
auto *designerPlugin = QmlDesigner::QmlDesignerPlugin::instance();
auto &viewManager = designerPlugin->viewManager(); auto &viewManager = designerPlugin->viewManager();
viewManager.registerView(std::make_unique<EffectMakerView>( viewManager.registerView(std::make_unique<EffectMakerView>(
QmlDesigner::QmlDesignerPlugin::externalDependenciesForPluginInitializationOnly())); QmlDesigner::QmlDesignerPlugin::externalDependenciesForPluginInitializationOnly()));
}
return true; return true;
} }
}; };

View File

@@ -26,6 +26,7 @@ QHash<int, QByteArray> EffectMakerUniformsModel::roleNames() const
roles[MinValueRole] = "uniformMinValue"; roles[MinValueRole] = "uniformMinValue";
roles[MaxValueRole] = "uniformMaxValue"; roles[MaxValueRole] = "uniformMaxValue";
roles[TypeRole] = "uniformType"; roles[TypeRole] = "uniformType";
roles[UseCustomValueRole] = "uniformUseCustomValue";
return roles; return roles;
} }

View File

@@ -37,6 +37,7 @@ private:
MaxValueRole, MaxValueRole,
MinValueRole, MinValueRole,
TypeRole, TypeRole,
UseCustomValueRole
}; };
QList<Uniform *> m_uniforms; QList<Uniform *> m_uniforms;

View File

@@ -7,16 +7,11 @@
#include "effectmakernodesmodel.h" #include "effectmakernodesmodel.h"
#include "effectmakerwidget.h" #include "effectmakerwidget.h"
#include "nodeinstanceview.h"
#include "qmldesignerconstants.h" #include "qmldesignerconstants.h"
#include <coreplugin/icore.h> #include <modelnodeoperations.h>
#include <QQmlContext> #include <coreplugin/icore.h>
#include <QQmlEngine>
#include <QQuickItem>
#include <QQuickView>
#include <QTimer>
namespace EffectMaker { namespace EffectMaker {
@@ -51,6 +46,15 @@ QmlDesigner::WidgetInfo EffectMakerView::widgetInfo()
if (m_widget.isNull()) { if (m_widget.isNull()) {
m_widget = new EffectMakerWidget{this}; m_widget = new EffectMakerWidget{this};
connect(m_widget->effectMakerModel(), &EffectMakerModel::assignToSelectedTriggered, this,
[&] (const QString &effectPath) {
executeInTransaction("EffectMakerView::widgetInfo", [&] {
const QList<QmlDesigner::ModelNode> selectedNodes = selectedModelNodes();
for (const QmlDesigner::ModelNode &node : selectedNodes)
QmlDesigner::ModelNodeOperations::handleItemLibraryEffectDrop(effectPath, node);
});
});
auto context = new EffectMakerContext(m_widget.data()); auto context = new EffectMakerContext(m_widget.data());
Core::ICore::addContextObject(context); Core::ICore::addContextObject(context);
} }
@@ -66,7 +70,7 @@ void EffectMakerView::customNotification([[maybe_unused]] const AbstractView *vi
{ {
if (identifier == "open_effectmaker_composition" && data.count() > 0) { if (identifier == "open_effectmaker_composition" && data.count() > 0) {
const QString compositionPath = data[0].toString(); const QString compositionPath = data[0].toString();
m_widget->effectMakerModel()->openComposition(compositionPath); m_widget->openComposition(compositionPath);
} }
} }
@@ -85,4 +89,3 @@ void EffectMakerView::modelAboutToBeDetached(QmlDesigner::Model *model)
} }
} // namespace EffectMaker } // namespace EffectMaker

View File

@@ -43,4 +43,3 @@ private:
}; };
} // namespace EffectMaker } // namespace EffectMaker

View File

@@ -7,26 +7,33 @@
#include "effectmakermodel.h" #include "effectmakermodel.h"
#include "effectmakernodesmodel.h" #include "effectmakernodesmodel.h"
#include "effectmakerview.h" #include "effectmakerview.h"
#include "effectutils.h"
#include "propertyhandler.h" #include "propertyhandler.h"
//#include "qmldesigner/designercore/imagecache/midsizeimagecacheprovider.h" //#include "qmldesigner/designercore/imagecache/midsizeimagecacheprovider.h"
#include "qmldesignerconstants.h" #include "qmldesignerconstants.h"
#include "qmldesignerplugin.h" #include "qmldesignerplugin.h"
#include "qqmlcontext.h"
#include "theme.h" #include "theme.h"
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <qmldesigner/documentmanager.h>
#include <qmldesigner/qmldesignerconstants.h> #include <qmldesigner/qmldesignerconstants.h>
#include <qmldesigner/qmldesignerplugin.h> #include <qmldesigner/qmldesignerplugin.h>
#include <studioquickwidget.h> #include <studioquickwidget.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/async.h>
#include <utils/environment.h> #include <utils/environment.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QQmlContext>
#include <QQmlEngine> #include <QQmlEngine>
#include <QQuickItem>
#include <QTimer>
namespace EffectMaker { namespace EffectMaker {
@@ -55,6 +62,7 @@ EffectMakerWidget::EffectMakerWidget(EffectMakerView *view)
m_quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); m_quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
QmlDesigner::Theme::setupTheme(m_quickWidget->engine()); QmlDesigner::Theme::setupTheme(m_quickWidget->engine());
m_quickWidget->engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); m_quickWidget->engine()->addImportPath(propertyEditorResourcesPath() + "/imports");
m_quickWidget->engine()->addImportPath(EffectUtils::nodesSourcesPath() + "/common");
m_quickWidget->setClearColor(QmlDesigner::Theme::getColor( m_quickWidget->setClearColor(QmlDesigner::Theme::getColor(
QmlDesigner::Theme::Color::QmlDesigner_BackgroundColorDarkAlternate)); QmlDesigner::Theme::Color::QmlDesigner_BackgroundColorDarkAlternate));
@@ -70,12 +78,38 @@ EffectMakerWidget::EffectMakerWidget(EffectMakerView *view)
m_quickWidget->rootContext()->setContextProperty("g_propertyData", &g_propertyData); m_quickWidget->rootContext()->setContextProperty("g_propertyData", &g_propertyData);
QString blurPath = "file:" + EffectUtils::nodesSourcesPath() + "/common/";
g_propertyData.insert(QString("blur_vs_path"), QString(blurPath + "bluritems.vert.qsb"));
g_propertyData.insert(QString("blur_fs_path"), QString(blurPath + "bluritems.frag.qsb"));
auto map = m_quickWidget->registerPropertyMap("EffectMakerBackend"); auto map = m_quickWidget->registerPropertyMap("EffectMakerBackend");
map->setProperties({{"effectMakerNodesModel", QVariant::fromValue(m_effectMakerNodesModel.data())}, map->setProperties({{"effectMakerNodesModel", QVariant::fromValue(m_effectMakerNodesModel.data())},
{"effectMakerModel", QVariant::fromValue(m_effectMakerModel.data())}, {"effectMakerModel", QVariant::fromValue(m_effectMakerModel.data())},
{"rootView", QVariant::fromValue(this)}}); {"rootView", QVariant::fromValue(this)}});
QmlDesigner::QmlDesignerPlugin::trackWidgetFocusTime( QmlDesigner::QmlDesignerPlugin::trackWidgetFocusTime(
this, QmlDesigner::Constants::EVENT_NEWEFFECTMAKER_TIME); this, QmlDesigner::Constants::EVENT_NEWEFFECTMAKER_TIME);
connect(m_effectMakerModel.data(), &EffectMakerModel::nodesChanged, this, [this]() {
m_effectMakerNodesModel->updateCanBeAdded(m_effectMakerModel->uniformNames());
});
connect(m_effectMakerModel.data(), &EffectMakerModel::resourcesSaved,
this, [this](const QmlDesigner::TypeName &type, const Utils::FilePath &path) {
if (!m_importScan.timer) {
m_importScan.timer = new QTimer(this);
connect(m_importScan.timer, &QTimer::timeout,
this, &EffectMakerWidget::handleImportScanTimer);
}
if (m_importScan.timer->isActive() && !m_importScan.future.isFinished())
m_importScan.future.cancel();
m_importScan.counter = 0;
m_importScan.type = type;
m_importScan.path = path;
m_importScan.timer->start(100);
});
} }
@@ -119,6 +153,20 @@ void EffectMakerWidget::focusSection(int section)
Q_UNUSED(section) Q_UNUSED(section)
} }
QRect EffectMakerWidget::screenRect() const
{
if (m_quickWidget && m_quickWidget->screen())
return m_quickWidget->screen()->availableGeometry();
return {};
}
QPoint EffectMakerWidget::globalPos(const QPoint &point) const
{
if (m_quickWidget)
return m_quickWidget->mapToGlobal(point);
return point;
}
QSize EffectMakerWidget::sizeHint() const QSize EffectMakerWidget::sizeHint() const
{ {
return {420, 420}; return {420, 420};
@@ -152,6 +200,21 @@ void EffectMakerWidget::initView()
reloadQmlSource(); reloadQmlSource();
} }
void EffectMakerWidget::openComposition(const QString &path)
{
m_compositionPath = path;
if (effectMakerModel()->hasUnsavedChanges())
QMetaObject::invokeMethod(quickWidget()->rootObject(), "promptToSaveBeforeOpen");
else
doOpenComposition();
}
void EffectMakerWidget::doOpenComposition()
{
effectMakerModel()->openComposition(m_compositionPath);
}
void EffectMakerWidget::reloadQmlSource() void EffectMakerWidget::reloadQmlSource()
{ {
const QString effectMakerQmlPath = qmlSourcesPath() + "/EffectMaker.qml"; const QString effectMakerQmlPath = qmlSourcesPath() + "/EffectMaker.qml";
@@ -159,5 +222,65 @@ void EffectMakerWidget::reloadQmlSource()
m_quickWidget->setSource(QUrl::fromLocalFile(effectMakerQmlPath)); m_quickWidget->setSource(QUrl::fromLocalFile(effectMakerQmlPath));
} }
void EffectMakerWidget::handleImportScanTimer()
{
++m_importScan.counter;
if (m_importScan.counter == 1) {
// Rescan the effect import to update code model
auto modelManager = QmlJS::ModelManagerInterface::instance();
if (modelManager) {
QmlJS::PathsAndLanguages pathToScan;
pathToScan.maybeInsert(m_importScan.path);
m_importScan.future = ::Utils::asyncRun(&QmlJS::ModelManagerInterface::importScan,
modelManager->workingCopy(),
pathToScan, modelManager, true, true, true);
}
} else if (m_importScan.counter < 100) {
// We have to wait a while to ensure qmljs detects new files and updates its
// internal model. Then we force amend on rewriter to trigger qmljs snapshot update.
if (m_importScan.future.isCanceled() || m_importScan.future.isFinished())
m_importScan.counter = 100; // skip the timeout step
} else if (m_importScan.counter == 100) {
// Scanning is taking too long, abort
m_importScan.future.cancel();
m_importScan.timer->stop();
m_importScan.counter = 0;
} else if (m_importScan.counter == 101) {
if (m_effectMakerView->model() && m_effectMakerView->model()->rewriterView()) {
QmlDesigner::QmlDesignerPlugin::instance()->documentManager().resetPossibleImports();
m_effectMakerView->model()->rewriterView()->forceAmend();
}
} else if (m_importScan.counter == 102) {
if (m_effectMakerView->model()) {
// If type is in use, we have to reset puppet to update 2D view
if (!m_effectMakerView->allModelNodesOfType(
m_effectMakerView->model()->metaInfo(m_importScan.type)).isEmpty()) {
m_effectMakerView->resetPuppet();
}
}
} else if (m_importScan.counter >= 103) {
// Refresh property view by resetting selection if any selected node is of updated type
if (m_effectMakerView->model() && m_effectMakerView->hasSelectedModelNodes()) {
const auto nodes = m_effectMakerView->selectedModelNodes();
QmlDesigner::MetaInfoType metaType
= m_effectMakerView->model()->metaInfo(m_importScan.type).type();
bool match = false;
for (const QmlDesigner::ModelNode &node : nodes) {
if (node.metaInfo().type() == metaType) {
match = true;
break;
}
}
if (match) {
m_effectMakerView->clearSelectedModelNodes();
m_effectMakerView->setSelectedModelNodes(nodes);
}
}
m_importScan.timer->stop();
m_importScan.counter = 0;
}
}
} // namespace EffectMaker } // namespace EffectMaker

View File

@@ -9,9 +9,14 @@
#include <coreplugin/icontext.h> #include <coreplugin/icontext.h>
#include <QFrame> #include <QFrame>
#include <QFuture>
class StudioQuickWidget; class StudioQuickWidget;
QT_BEGIN_NAMESPACE
class QTimer;
QT_END_NAMESPACE
namespace EffectMaker { namespace EffectMaker {
class EffectMakerView; class EffectMakerView;
@@ -34,6 +39,7 @@ public:
void delayedUpdateModel(); void delayedUpdateModel();
void updateModel(); void updateModel();
void initView(); void initView();
void openComposition(const QString &path);
StudioQuickWidget *quickWidget() const; StudioQuickWidget *quickWidget() const;
QPointer<EffectMakerModel> effectMakerModel() const; QPointer<EffectMakerModel> effectMakerModel() const;
@@ -41,6 +47,9 @@ public:
Q_INVOKABLE void addEffectNode(const QString &nodeQenPath); Q_INVOKABLE void addEffectNode(const QString &nodeQenPath);
Q_INVOKABLE void focusSection(int section); Q_INVOKABLE void focusSection(int section);
Q_INVOKABLE void doOpenComposition();
Q_INVOKABLE QRect screenRect() const;
Q_INVOKABLE QPoint globalPos(const QPoint &point) const;
QSize sizeHint() const override; QSize sizeHint() const override;
@@ -49,6 +58,7 @@ protected:
private: private:
void reloadQmlSource(); void reloadQmlSource();
void handleImportScanTimer();
QPointer<EffectMakerModel> m_effectMakerModel; QPointer<EffectMakerModel> m_effectMakerModel;
QPointer<EffectMakerNodesModel> m_effectMakerNodesModel; QPointer<EffectMakerNodesModel> m_effectMakerNodesModel;
@@ -56,6 +66,17 @@ private:
QPointer<StudioQuickWidget> m_quickWidget; QPointer<StudioQuickWidget> m_quickWidget;
QmlDesigner::QmlModelNodeProxy m_backendModelNode; QmlDesigner::QmlModelNodeProxy m_backendModelNode;
QmlDesigner::QmlAnchorBindingProxy m_backendAnchorBinding; QmlDesigner::QmlAnchorBindingProxy m_backendAnchorBinding;
struct ImportScanData {
QFuture<void> future;
int counter = 0;
QTimer *timer = nullptr;
QmlDesigner::TypeName type;
Utils::FilePath path;
};
ImportScanData m_importScan;
QString m_compositionPath;
}; };
} // namespace EffectMaker } // namespace EffectMaker

View File

@@ -2,6 +2,8 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "effectnode.h" #include "effectnode.h"
#include "compositionnode.h"
#include "uniform.h"
#include <QDir> #include <QDir>
#include <QFileInfo> #include <QFileInfo>
@@ -22,6 +24,12 @@ EffectNode::EffectNode(const QString &qenPath)
iconPath = QStringLiteral("%1/%2").arg(parentDir.path(), "placeholder.svg"); iconPath = QStringLiteral("%1/%2").arg(parentDir.path(), "placeholder.svg");
} }
m_iconPath = QUrl::fromLocalFile(iconPath); m_iconPath = QUrl::fromLocalFile(iconPath);
CompositionNode node({}, qenPath);
const QList<Uniform *> uniforms = node.uniforms();
for (const Uniform *uniform : uniforms)
m_uniformNames.insert(uniform->name());
} }
QString EffectNode::name() const QString EffectNode::name() const
@@ -39,5 +47,18 @@ QString EffectNode::qenPath() const
return m_qenPath; return m_qenPath;
} }
void EffectNode::setCanBeAdded(bool enabled)
{
if (enabled != m_canBeAdded) {
m_canBeAdded = enabled;
emit canBeAddedChanged();
}
}
bool EffectNode::hasUniform(const QString &name)
{
return m_uniformNames.contains(name);
}
} // namespace EffectMaker } // namespace EffectMaker

View File

@@ -4,6 +4,7 @@
#pragma once #pragma once
#include <QObject> #include <QObject>
#include <QSet>
#include <QUrl> #include <QUrl>
namespace EffectMaker { namespace EffectMaker {
@@ -16,6 +17,7 @@ class EffectNode : public QObject
Q_PROPERTY(QString nodeDescription MEMBER m_description CONSTANT) Q_PROPERTY(QString nodeDescription MEMBER m_description CONSTANT)
Q_PROPERTY(QUrl nodeIcon MEMBER m_iconPath CONSTANT) Q_PROPERTY(QUrl nodeIcon MEMBER m_iconPath CONSTANT)
Q_PROPERTY(QString nodeQenPath MEMBER m_qenPath CONSTANT) Q_PROPERTY(QString nodeQenPath MEMBER m_qenPath CONSTANT)
Q_PROPERTY(bool canBeAdded MEMBER m_canBeAdded NOTIFY canBeAddedChanged)
public: public:
EffectNode(const QString &qenPath); EffectNode(const QString &qenPath);
@@ -24,11 +26,20 @@ public:
QString description() const; QString description() const;
QString qenPath() const; QString qenPath() const;
void setCanBeAdded(bool enabled);
bool hasUniform(const QString &name);
signals:
void canBeAddedChanged();
private: private:
QString m_name; QString m_name;
QString m_description; QString m_description;
QString m_qenPath; QString m_qenPath;
QUrl m_iconPath; QUrl m_iconPath;
bool m_canBeAdded = true;
QSet<QString> m_uniformNames;
}; };
} // namespace EffectMaker } // namespace EffectMaker

View File

@@ -3,6 +3,8 @@
#include "effectutils.h" #include "effectutils.h"
#include <coreplugin/icore.h>
#include <QJsonArray> #include <QJsonArray>
namespace EffectMaker { namespace EffectMaker {
@@ -20,5 +22,14 @@ QString EffectUtils::codeFromJsonArray(const QJsonArray &codeArray)
return codeString; return codeString;
} }
QString EffectUtils::nodesSourcesPath()
{
#ifdef SHARE_QML_PATH
if (Utils::qtcEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE"))
return QLatin1String(SHARE_QML_PATH) + "/effectMakerNodes";
#endif
return Core::ICore::resourcePath("qmldesigner/effectMakerNodes").toString();
}
} // namespace EffectMaker } // namespace EffectMaker

View File

@@ -15,6 +15,8 @@ public:
EffectUtils() = delete; EffectUtils() = delete;
static QString codeFromJsonArray(const QJsonArray &codeArray); static QString codeFromJsonArray(const QJsonArray &codeArray);
static QString nodesSourcesPath();
}; };
} // namespace EffectMaker } // namespace EffectMaker

View File

@@ -25,6 +25,8 @@ class Uniform : public QObject
Q_PROPERTY(QVariant uniformBackendValue READ backendValue NOTIFY uniformBackendValueChanged) Q_PROPERTY(QVariant uniformBackendValue READ backendValue NOTIFY uniformBackendValueChanged)
Q_PROPERTY(QVariant uniformMinValue MEMBER m_minValue CONSTANT) Q_PROPERTY(QVariant uniformMinValue MEMBER m_minValue CONSTANT)
Q_PROPERTY(QVariant uniformMaxValue MEMBER m_maxValue CONSTANT) Q_PROPERTY(QVariant uniformMaxValue MEMBER m_maxValue CONSTANT)
Q_PROPERTY(QVariant uniformDefaultValue MEMBER m_defaultValue CONSTANT)
Q_PROPERTY(QVariant uniformUseCustomValue MEMBER m_useCustomValue CONSTANT)
public: public:
enum class Type enum class Type

View File

@@ -839,10 +839,12 @@ extend_qtc_plugin(QmlDesigner
collectiondetailssortfiltermodel.cpp collectiondetailssortfiltermodel.h collectiondetailssortfiltermodel.cpp collectiondetailssortfiltermodel.h
collectioneditorconstants.h collectioneditorconstants.h
collectioneditorutils.cpp collectioneditorutils.h collectioneditorutils.cpp collectioneditorutils.h
collectionimporttools.cpp collectionimporttools.h
collectionlistmodel.cpp collectionlistmodel.h collectionlistmodel.cpp collectionlistmodel.h
collectionsourcemodel.cpp collectionsourcemodel.h collectionsourcemodel.cpp collectionsourcemodel.h
collectionview.cpp collectionview.h collectionview.cpp collectionview.h
collectionwidget.cpp collectionwidget.h collectionwidget.cpp collectionwidget.h
datastoremodelnode.cpp datastoremodelnode.h
) )
extend_qtc_plugin(QmlDesigner extend_qtc_plugin(QmlDesigner

Some files were not shown because too many files have changed in this diff Show More