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}\"")
endif()
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
if(DEFINED ENV{DESTDIR})
set(DESTDIR_WITH_SEP \"\$ENV{DESTDIR}/\")
@@ -48,7 +51,7 @@ function(setup_dependencies_component)
\"\${_ide_app_target}\"
\"${_qmake_binary}\"
COMMAND_ECHO STDOUT
\${QTC_COMMAND_ERROR_IS_FATAL}
${QTC_COMMAND_ERROR_IS_FATAL}
)
"
COMPONENT Dependencies

View File

@@ -23,17 +23,24 @@
necessary files to a location in a device where you want to run the
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:
\list 1
\if defined(qtcreator)
\li In \uicontrol Projects > \uicontrol {Build & Run}, enable the kit
predefined for the device type (1).
\li Select the kit for the device in the kit selector (2).
\if defined(qtcreator)
\image qtcreator-live-preview-kit.png
\else
\image studio-kit-selector.png "Kit selector"
\endif
\else
\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
\li Select \uicontrol Build > \uicontrol {QML Preview} or
press \key {Alt+P}.
\endlist

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
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
original UI design files for your MCU application. After the initial design
work, export your design from the design tools, and import your 2D and 3D UI
design assets into \QDS, which can convert them into code for developers.
For more information on managing the original assets created with
specialized UI design tools, see \l {Asset Creation with Other Tools}.
such as Adobe Photoshop, Sketch, or Figma, to create the original UI design
files for your MCU application. After the initial design work, export your
design from the design tools, and import your 2D UI design assets into \QDS,
which can convert them into code for developers. For more information on
managing the original assets created with specialized UI design tools, see
\l {Asset Creation with Other Tools}.
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

View File

@@ -104,6 +104,16 @@
selected, the camera is pointed at the world origin. This does not affect
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"
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
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
To move, rotate, or scale components in the scene, you need to select them
@@ -358,6 +375,54 @@
\inlineimage icons/particles-seek.png
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
The \uicontrol{3D} view toolbar contains the following buttons:
@@ -441,6 +506,11 @@
\li Background Color Actions
\li
\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
\li \inlineimage icons/particles-seek.png
\li Seek Particle System Time

View File

@@ -213,10 +213,11 @@
\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,
PointLight, and SpotLight, including the shadows cast by the lights. At run time, instead of
performing the appropriate 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
Baked lightmaps allow pre-generating the direct lighting from lights,
such as DirectionalLight, PointLight, and SpotLight, including the shadows
cast by the lights. At run time, instead of performing the appropriate
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.
\section2 Baking Lightmaps
@@ -224,47 +225,56 @@
To bake lightmaps for models in your 3D scene:
\list 1
\li Right-click anywhere in the \uicontrol 3D view and select \uicontrol {Bake Lights}.
\li In the \uicontrol {Lights Baking Setup} dialog:
\li Select \inlineimage icons/bakelights.png in the \uicontrol 3D view
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
\li For every light you want to use to bake lightmaps, set \uicontrol {Bake Mode} to BakeModeAll.
\li For every 3D model you want to bake lightmaps, select \uicontrol {In Use} and
\uicontrol {Enabled}, and set the desired \uicontrol {Resolution}.
\li To bake both direct (diffuse and shadow) and indirect lighting,
select \uicontrol {Bake All}.
\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
\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 For every 3D model you want to bake into a lightmap, select
\uicontrol {In Use} and \uicontrol {Enabled}, and set the desired
\uicontrol {Resolution}.
\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.
\endlist
\image bake-lights-dialog.png
\section2 Manually Baking Lightmaps for a 3D Model
Baked lightmap components are not visible in the \uicontrol Navigator view by default. To make
them visible, select \inlineimage icons/visibilityon.png
\note Baked lightmap components are not visible in the \uicontrol Navigator view by
default. To make them visible, select \inlineimage icons/visibilityon.png
in the \uicontrol Navigator view.
To bake lightmaps for a 3D model:
\list 1
\li From \uicontrol Components, drag a \uicontrol {Baked Lightmap} component to
the 3D model in the \uicontrol Navigator view.
\li From \uicontrol Components, drag a \uicontrol {Baked Lightmap} component
to the 3D model in the \uicontrol Navigator view.
\image baked-lightmaps-navigator.png
\li In the \uicontrol Navigator view, select \e bakedLightmap and in the \uicontrol Properties
view:
\li In the \uicontrol Navigator view, select \e bakedLightmap, and in the
\uicontrol Properties view:
\list
\li Select \uicontrol Enabled.
\li In \uicontrol Key, set the filename base for the generated light maps. This must be
a unique name.
\li In \uicontrol {Load Prefix}, set the relative path to the folder where the generated
light map files are saved.
\li In \uicontrol Key, set the filename base for the generated light maps.
This must be a unique name.
\li In \uicontrol {Load Prefix}, set the relative path to the folder where
the generated light map files are saved.
\endlist
\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}.
\li Optional. Adjust \uicontrol Resolution to set the light map resoution. This effects how
accurate and time-consuming the lightmap baking is.
accurate and time-consuming the lightmap baking is.
\li In the \uicontrol Navigator view, select the light component that you want to bake
lightmaps for, and in the \uicontrol Properties view, set \uicontrol {Bake Mode} to BakeModeAll.
lightmaps for, and in the \uicontrol Properties view, set \uicontrol {Bake Mode} to BakeModeAll.
\li Right-click anywhere in the \uicontrol 3D view and select \uicontrol {Bake Lights}.
\li Select \uicontrol {Setup baking manually}, and then select \uicontrol Bake.
\endlist

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)
def deploy_qtdiag(qtc_binary_path, qt_install):
print("Copying qtdiag")
qtdiag_src = os.path.join(qt_install.bin, with_exe_ext('qtdiag'))
def deploy_binary(binary_name, qtc_binary_path, qt_install):
print(f"Copying {binary_name}")
binary_src = os.path.join(qt_install.bin, with_exe_ext(binary_name))
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, '..', 'lib', 'Qt', 'bin'))
if not os.path.exists(destdir):
os.makedirs(destdir)
shutil.copy(qtdiag_src, destdir)
shutil.copy(binary_src, destdir)
if common.is_mac_platform():
# fix RPATHs
qtdiag_dest = os.path.join(destdir, 'qtdiag')
subprocess.check_call(['xcrun', 'install_name_tool', '-add_rpath', '@loader_path/../Frameworks', qtdiag_dest])
subprocess.check_call(['xcrun', 'install_name_tool', '-delete_rpath', '@loader_path/../lib', qtdiag_dest])
binary_dest = os.path.join(destdir, binary_name)
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', binary_dest])
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()
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_imports(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
IconButton {
icon: StudioTheme.Constants.updateContent_medium
tooltip: qsTr("Update existing file with changes")
icon: StudioTheme.Constants.save_medium
tooltip: qsTr("Save changes")
enabled: root.model.collectionName !== ""
onClicked:
{
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")
}
onClicked: root.model.saveCurrentCollection()
}
IconButton {
icon: StudioTheme.Constants.export_medium
tooltip: qsTr("Export the model to a new file")
tooltip: qsTr("Export model")
enabled: root.model.collectionName !== ""
onClicked: exportMenu.popup()
}
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()
}
}
onClicked: fileDialog.open()
}
}
}
@@ -145,17 +117,18 @@ Item {
PlatformWidgets.FileDialog {
id: fileDialog
fileMode: PlatformWidgets.FileDialog.SaveFile
onAccepted:
{
var fileAddress = file.toString()
if (fileAddress.indexOf("json") !== -1)
root.model.exportCollection(fileAddress, root.model.collectionName, "JSON")
else if (fileAddress.indexOf("csv") !== -1)
root.model.exportCollection(fileAddress, root.model.collectionName, "CSV")
nameFilters: ["JSON Files (*.json)",
"Comma-Separated Values (*.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.Controls
import QtQuick.Layouts
import CollectionDetails 1.0 as CollectionDetails
import HelperWidgets 2.0 as HelperWidgets
import StudioTheme 1.0 as StudioTheme
import StudioControls 1.0 as StudioControls
@@ -109,15 +110,9 @@ Rectangle {
clip: true
delegate: HeaderDelegate {
id: horizontalHeaderItem
selectedItem: tableView.model.selectedColumn
color: StudioTheme.Values.themeControlBackgroundInteraction
function getGlobalBottomLeft() {
return mapToGlobal(0, horizontalHeaderItem.height)
}
MouseArea {
anchors.fill: parent
anchors.margins: 5
@@ -125,47 +120,51 @@ Rectangle {
onClicked: (mouse) => {
tableView.model.selectColumn(index)
if (mouse.button === Qt.RightButton)
headerMenu.popIndex(index, horizontalHeaderItem.getGlobalBottomLeft())
if (mouse.button === Qt.RightButton) {
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 {
id: headerMenu
property int clickedHeader: -1
property point initialPosition
function popIndex(clickedIndex, clickedRect)
{
headerMenu.clickedHeader = clickedIndex
headerMenu.initialPosition = clickedRect
headerMenu.popup()
}
property int clickedHeaderIndex: -1
property point dialogPos
onClosed: {
headerMenu.clickedHeader = -1
headerMenu.clickedHeaderIndex = -1
}
StudioControls.MenuItem {
text: qsTr("Edit")
onTriggered: editProperyDialog.editProperty(headerMenu.clickedHeader, headerMenu.initialPosition)
onTriggered: editProperyDialog.openDialog(headerMenu.clickedHeaderIndex,
headerMenu.dialogPos)
}
StudioControls.MenuItem {
text: qsTr("Delete")
onTriggered: deleteColumnDialog.popUp(headerMenu.clickedHeader)
onTriggered: deleteColumnDialog.popUp(headerMenu.clickedHeaderIndex)
}
StudioControls.MenuItem {
text: qsTr("Sort Ascending")
onTriggered: sortedModel.sort(headerMenu.clickedHeader, Qt.AscendingOrder)
onTriggered: sortedModel.sort(headerMenu.clickedHeaderIndex, Qt.AscendingOrder)
}
StudioControls.MenuItem {
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
implicitWidth: 100
implicitHeight: itemText.height
border.color: dataTypeWarning !== CollectionDetails.Warning.None ?
StudioTheme.Values.themeWarning : StudioTheme.Values.themeControlBackgroundInteraction
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 {
id: itemText
text: display ? display : ""
text: display
color: StudioTheme.Values.themePlaceholderTextColorInteraction
width: parent.width
leftPadding: 5
topPadding: 3
@@ -241,7 +250,6 @@ Rectangle {
PropertyChanges {
target: itemCell
color: StudioTheme.Values.themeControlBackground
border.color: StudioTheme.Values.themeControlBackgroundInteraction
}
PropertyChanges {
@@ -306,14 +314,11 @@ Rectangle {
}
Text {
anchors.fill: parent
anchors.centerIn: parent
text: qsTr("Select a model to continue")
visible: !topRow.visible
textFormat: Text.RichText
color: StudioTheme.Values.themeTextColor
font.pixelSize: StudioTheme.Values.mediumFontSize
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
}
TextMetrics {

View File

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

View File

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

View File

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

View File

@@ -13,7 +13,7 @@ import StudioTheme as StudioTheme
StudioControls.Dialog {
id: root
title: qsTr("Import A CSV File")
title: qsTr("Import a model")
anchors.centerIn: parent
closePolicy: Popup.CloseOnEscape
modal: true
@@ -23,8 +23,8 @@ StudioControls.Dialog {
property bool fileExists: false
onOpened: {
collectionName.text = "Collection_"
fileName.text = qsTr("New CSV File")
collectionName.text = "Model"
fileName.text = qsTr("Model path")
fileName.selectAll()
fileName.forceActiveFocus()
}
@@ -40,6 +40,14 @@ StudioControls.Dialog {
PlatformWidgets.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
}
@@ -61,7 +69,7 @@ StudioControls.Dialog {
spacing: 2
Text {
text: qsTr("File name: ")
text: qsTr("File name")
color: StudioTheme.Values.themeTextColor
}
@@ -80,11 +88,11 @@ StudioControls.Dialog {
translationIndicator.visible: false
validator: fileNameValidator
Keys.onEnterPressed: btnCreate.onClicked()
Keys.onReturnPressed: btnCreate.onClicked()
Keys.onEnterPressed: btnImport.onClicked()
Keys.onReturnPressed: btnImport.onClicked()
Keys.onEscapePressed: root.reject()
onTextChanged: root.fileExists = root.backendValue.isCsvFile(fileName.text)
onTextChanged: root.fileExists = root.backendValue.isValidUrlToImport(fileName.text)
}
HelperWidgets.Button {
@@ -100,7 +108,7 @@ StudioControls.Dialog {
Spacer {}
Text {
text: qsTr("The model name: ")
text: qsTr("The model name")
color: StudioTheme.Values.themeTextColor
}
@@ -115,8 +123,8 @@ StudioControls.Dialog {
regularExpression: /^\w+$/
}
Keys.onEnterPressed: btnCreate.onClicked()
Keys.onReturnPressed: btnCreate.onClicked()
Keys.onEnterPressed: btnImport.onClicked()
Keys.onReturnPressed: btnImport.onClicked()
Keys.onEscapePressed: root.reject()
}
@@ -179,15 +187,17 @@ StudioControls.Dialog {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
HelperWidgets.Button {
id: btnCreate
id: btnImport
text: qsTr("Import")
enabled: root.fileExists && collectionName.text !== ""
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()
else
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
implicitWidth: 300
implicitHeight: wholeColumn.height
implicitHeight: collectionListView.height
property bool hasSelectedTarget
property color textColor
property var collectionModel
ListView {
id: collectionListView
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
implicitHeight: contentHeight
leftMargin: 6
Item {
id: boundingRect
model: internalModels
clip: true
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 {
id: collectionListView
Layout.fillWidth: true
Layout.preferredHeight: root.expanded ? contentHeight : 0
Layout.leftMargin: 6
model: internalModels
clip: true
Behavior on Layout.preferredHeight {
NumberAnimation {duration: 500}
}
delegate: CollectionItem {
width: collectionListView.width
sourceType: collectionListView.model.sourceType
onDeleteItem: collectionListView.model.removeRow(index)
}
delegate: CollectionItem {
width: collectionListView.width
sourceType: collectionListView.model.sourceType
hasSelectedTarget: root.hasSelectedTarget
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 {
id: root
enum SourceType { NewJson, NewCsv, ExistingCollection, NewCollectionToJson }
enum SourceType { NewJson, NewCsv, ExistingCollection }
required property var backendValue
required property var sourceModel
readonly property alias collectionType: typeMode.collectionType
readonly property bool isValid: collectionName.isValid
&& jsonCollections.isValid
&& newCollectionPath.isValid
title: qsTr("Add a new Model")
anchors.centerIn: parent
@@ -31,8 +28,6 @@ StudioControls.Dialog {
onOpened: {
collectionName.text = qsTr("Model")
updateType()
updateJsonSourceIndex()
updateCollectionExists()
}
@@ -41,57 +36,12 @@ StudioControls.Dialog {
}
onAccepted: {
if (root.isValid) {
root.backendValue.addCollection(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
if (root.isValid)
root.backendValue.addCollectionToDataStore(collectionName.text);
}
function updateCollectionExists() {
collectionName.alreadyExists = sourceModel.collectionExists(jsonCollections.currentValue,
collectionName.alreadyExists = sourceModel.collectionExists(backendValue.dataStoreNode(),
collectionName.text)
}
@@ -119,118 +69,6 @@ StudioControls.Dialog {
contentItem: ColumnLayout {
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 {
text: qsTr("The model name")
visible: collectionName.enabled

View File

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

View File

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

View File

@@ -16,14 +16,45 @@ Item {
property int moveToIdx: 0
property bool previewAnimationRunning: false
SaveDialog {
id: saveDialog
compositionName: EffectMakerBackend.effectMakerModel.currentComposition
// Invoked after save changes is done
property var onSaveChangesCallback: () => {}
// 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
onAccepted: {
let name = saveDialog.compositionName
EffectMakerBackend.effectMakerModel.exportComposition(name)
EffectMakerBackend.effectMakerModel.exportResources(name)
}
SaveChangesDialog {
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
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 {
@@ -55,6 +108,21 @@ Item {
mainRoot: root
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 {
@@ -64,7 +132,7 @@ Item {
style: StudioTheme.Values.viewBarButtonStyle
buttonIcon: StudioTheme.Constants.code
tooltip: qsTr("Open Shader in Code Editor")
tooltip: qsTr("Open Shader in Code Editor.")
visible: false // TODO: to be implemented
onClicked: {} // TODO
@@ -96,6 +164,7 @@ Item {
delegate: EffectCompositionNode {
width: root.width
modelIndex: index
Behavior on y {
PropertyAnimation {
@@ -144,7 +213,8 @@ Item {
currItem.y = root.secsY[i]
}
} 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
root.moveToIdx = Math.min(root.moveToIdx, i)
} else {

View File

@@ -84,17 +84,6 @@ Column {
anchors.horizontalCenter: parent.horizontalCenter
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 {
enabled: sourceImage.scale < 2
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 {
enabled: sourceImage.scale !== 1
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 {
id: componentParent
width: source.width

View File

@@ -15,15 +15,59 @@ Rectangle {
height: StudioTheme.Values.toolbarHeight
color: StudioTheme.Values.themeToolbarBackground
signal addClicked
signal saveClicked
signal saveAsClicked
signal assignToSelectedClicked
HelperWidgets.Button {
Row {
spacing: 5
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.saveClicked()
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()
}
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 {

View File

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

View File

@@ -12,8 +12,6 @@ StudioControls.ComboBox {
id: root
actionIndicatorVisible: false
x: 5
width: parent.width - 50
model: [qsTr("+ Add Effect")]
@@ -23,18 +21,48 @@ StudioControls.ComboBox {
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 {
target: root.popup
function onAboutToShow() {
var a = mainRoot.mapToGlobal(0, 0)
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
root.calculateWindowGeometry()
window.show()
window.requestActivate()
// Geometry can get corrupted by first show after screen change, so recalc it
root.calculateWindowGeometry()
}
function onAboutToHide() {
@@ -45,12 +73,10 @@ StudioControls.ComboBox {
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
onActiveFocusItemChanged: {
if (!window.activeFocusItem && !root.indicator.hover && root.popup.opened)
if (!window.activeFocusItem && !root.hovered && root.popup.opened)
root.popup.close()
}
@@ -59,6 +85,7 @@ StudioControls.ComboBox {
color: StudioTheme.Values.themePanelBackground
border.color: StudioTheme.Values.themeInteraction
border.width: 1
focus: true
HelperWidgets.ScrollView {
anchors.fill: parent
@@ -67,16 +94,6 @@ StudioControls.ComboBox {
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
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"]
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 {
target: root.popup
function onAboutToShow() {
var a = mainRoot.mapToGlobal(0, 0)
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
root.calculateWindowGeometry()
window.show()
window.requestActivate()
// Geometry can get corrupted by first show after screen change, so recalc it
root.calculateWindowGeometry()
}
function onAboutToHide() {
@@ -60,17 +90,29 @@ StudioControls.ComboBox {
anchors.fill: parent
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 {
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
onActiveFocusItemChanged: {
if (!window.activeFocusItem && !root.indicator.hover && root.popup.opened)
if (!window.activeFocusItem && !root.hovered && root.popup.opened)
root.popup.close()
}
@@ -79,6 +121,7 @@ StudioControls.ComboBox {
color: StudioTheme.Values.themePanelBackground
border.color: StudioTheme.Values.themeInteraction
border.width: 1
focus: true
HelperWidgets.ScrollView {
anchors.fill: parent
@@ -88,16 +131,6 @@ StudioControls.ComboBox {
Column {
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
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 StudioControls as StudioControls
import StudioTheme as StudioTheme
import AssetsLibraryBackend
import EffectMakerBackend
StudioControls.Dialog {
id: root
@@ -18,12 +18,13 @@ StudioControls.Dialog {
implicitWidth: 250
implicitHeight: 160
property string compositionName: ""
property bool clearOnClose: false // clear the effect maker after saving
onOpened: {
nameText.text = compositionName //TODO: Generate unique name
emptyText.text = ""
nameText.text = EffectMakerBackend.effectMakerModel.getUniqueEffectName()
nameText.selectAll()
nameText.forceActiveFocus()
emptyText.text = ""
}
contentItem: Item {
@@ -83,14 +84,28 @@ StudioControls.Dialog {
text: qsTr("Save")
enabled: nameText.text !== ""
onClicked: {
root.compositionName = nameText.text
root.accept() //TODO: Check if name is unique
EffectMakerBackend.effectMakerModel.saveComposition(nameText.text)
if (root.clearOnClose) {
EffectMakerBackend.effectMakerModel.clear()
root.clearOnClose = false
}
root.accept() // TODO: confirm before overriding effect with same name
}
}
HelperWidgets.Button {
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
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 {
id: predefinedSection
caption: qsTr("Predefined Categories")
@@ -412,6 +412,7 @@ Rectangle {
}
}
}
*/
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,13 +1,13 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import HelperWidgets 2.0
import StudioControls 1.0 as StudioControls
import StudioTheme 1.0 as StudioTheme
import QtQuickDesignerTheme 1.0
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import HelperWidgets
import StudioControls as StudioControls
import StudioTheme as StudioTheme
import QtQuickDesignerTheme
Row {
id: root
@@ -22,6 +22,10 @@ Row {
// by QtQuick3D to add built-in primitives to the model.
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
property string absoluteFilePath: ""
@@ -40,6 +44,96 @@ Row {
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 {
id: comboBox
@@ -86,71 +180,28 @@ Row {
}
}
ToolTip {
id: toolTip
visible: comboBox.hover && toolTip.text !== ""
ThumbnailToolTip {
id: rootToolTip
visible: comboBox.hover && rootToolTip.text !== ""
text: root.backendValue?.valueToString ?? ""
delay: StudioTheme.Values.toolTipDelay
background: Rectangle {
color: StudioTheme.Values.themeToolTipBackground
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))
return "image://qmldesigner_thumbnails/"
+ root.absoluteFilePath.substring(1, root.absoluteFilePath.length)
+ ".builtin"
if (fileModel.isLocal(root.absoluteFilePath))
return "image://qmldesigner_thumbnails/" + root.absoluteFilePath
return root.absoluteFilePath
}
}
}
ColumnLayout {
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
}
}
checkerVisible: !root.isMesh(root.absoluteFilePath)
thumbnailSource: {
if (root.isBuiltInPrimitive(root.absoluteFilePath))
return "image://qmldesigner_thumbnails/"
+ root.absoluteFilePath.substring(1, root.absoluteFilePath.length)
+ ".builtin"
if (fileModel.isLocal(root.absoluteFilePath))
return "image://qmldesigner_thumbnails/" + root.absoluteFilePath
return root.absoluteFilePath
}
titleText: root.fileName(rootToolTip.text)
descriptionText: root.isBuiltInPrimitive(rootToolTip.text)
? qsTr("Built-in primitive")
: rootToolTip.text
}
delegate: ItemDelegate {
@@ -213,71 +264,25 @@ Row {
}
}
ToolTip {
ThumbnailToolTip {
id: delegateToolTip
visible: delegateRoot.hovered
text: delegateRoot.relativeFilePath
delay: StudioTheme.Values.toolTipDelay
background: Rectangle {
color: StudioTheme.Values.themeToolTipBackground
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))
return "image://qmldesigner_thumbnails/"
+ delegateRoot.name.substring(1, delegateRoot.name.length)
+ ".builtin"
return "image://qmldesigner_thumbnails/" + delegateRoot.absoluteFilePath
}
}
}
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")
: delegateToolTip.text
font: delegateToolTip.font
color: StudioTheme.Values.themeToolTipText
wrapMode: Text.WordWrap
}
}
checkerVisible: !root.isMesh(delegateRoot.absoluteFilePath)
thumbnailSource: {
if (root.isBuiltInPrimitive(delegateRoot.name))
return "image://qmldesigner_thumbnails/"
+ delegateRoot.name.substring(1, delegateRoot.name.length)
+ ".builtin"
return "image://qmldesigner_thumbnails/" + delegateRoot.absoluteFilePath
}
titleText: delegateRoot.name
descriptionText: root.isBuiltInPrimitive(delegateToolTip.text)
? qsTr("Built-in primitive")
: delegateToolTip.text
}
}
@@ -422,8 +427,10 @@ Row {
if (root.defaultItems !== undefined) {
for (var i = 0; i < root.defaultItems.length; ++i) {
comboBox.listModel.append({
absoluteFilePath: "",
relativeFilePath: root.defaultItems[i],
absoluteFilePath: root.defaultPaths ? root.defaultPaths[i]
: "",
relativeFilePath: root.defaultPaths ? root.defaultPaths[i]
: root.defaultItems[i],
name: root.defaultItems[i],
group: 0
})
@@ -454,6 +461,7 @@ Row {
}
onDefaultItemsChanged: root.createModel()
onDefaultPathsChanged: root.createModel()
Component.onCompleted: {
root.createModel()

View File

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

View File

@@ -25,19 +25,21 @@ QtObject {
property alias flags: window.flags
property alias visible: window.visible
property int anchorGap: 10
property int edge: Qt.LeftEdge
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 bool keepOpen: false
signal closing(close: var)
function showGlobal()
{
var pos = WindowManager.globalCursorPosition();
function showGlobal() {
var pos = WindowManager.globalCursorPosition()
root.__itemGlobal = Qt.rect(pos.x, pos.y, 300, 20)
root.chevronVisible = false
//root.chevronVisible = false
root.layout()
window.show()
window.raise()
@@ -46,7 +48,7 @@ QtObject {
function show(target: Item) {
var originGlobal = target.mapToGlobal(0, 0)
root.__itemGlobal = Qt.rect(originGlobal.x, originGlobal.y, target.width, target.height)
root.chevronVisible = true
//root.chevronVisible = true
root.layout()
window.show()
window.raise()
@@ -57,11 +59,8 @@ QtObject {
}
function layout() {
// Setup
var screen = Qt.rect(0, //Screen.virtualX, // TODO
0, //Screen.virtualY, // TODO
Screen.desktopAvailableWidth,
Screen.desktopAvailableHeight)
let position = Qt.point(root.__itemGlobal.x, root.__itemGlobal.y)
var screen = WindowManager.getScreenGeometry(position)
// Collect region information
let edges = window.getRegions(screen, root.__itemGlobal)
@@ -78,8 +77,8 @@ QtObject {
let anchor = edges[edge].anchor
let popoverRect = window.popoverGeometry(edge, anchor, edges[edge].region)
if (chevron.visible)
chevron.layout(edge, popoverRect, anchor)
//if (chevron.visible)
// chevron.layout(edge, popoverRect, anchor)
window.x = popoverRect.x
window.y = popoverRect.y
@@ -88,7 +87,7 @@ QtObject {
property Window window: Window {
id: window
property int margin: 20
property int margin: 0 //20
width: root.width + (2 * window.margin)
height: root.height + (2 * window.margin)
@@ -139,7 +138,7 @@ QtObject {
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 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),
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
let topAnchor = Qt.point(targetCenter.x, target.y)
let topRegion = Qt.rect(source.x, source.y, source.width, Math.max(0, topAnchor.y))
let topAnchor = Qt.point(targetCenter.x, target.y - root.anchorGap)
let topRegion = Qt.rect(source.x,
source.y,
source.width,
(topAnchor.y < source.top) ? 0 : Math.abs(topAnchor.y - source.top))
edges[Qt.TopEdge] = {
anchor: topAnchor,
@@ -177,10 +185,10 @@ QtObject {
}
// 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,
source.y,
Math.max(0, source.width - rightAnchor.x),
(rightAnchor.x > sourceRight) ? 0 : Math.abs(sourceRight - rightAnchor.x),
source.height)
edges[Qt.RightEdge] = {
@@ -191,11 +199,11 @@ QtObject {
}
// 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,
bottomAnchor.y,
source.width,
Math.max(0, source.height - bottomAnchor.y))
(bottomAnchor.y > sourceBottom) ? 0 : Math.abs(sourceBottom - bottomAnchor.y))
edges[Qt.BottomEdge] = {
anchor: bottomAnchor,
@@ -205,8 +213,11 @@ QtObject {
}
// LEFT
let leftAnchor = Qt.point(target.x, targetCenter.y)
let leftRegion = Qt.rect(source.x, source.y, Math.max(0, leftAnchor.x), source.height)
let leftAnchor = Qt.point(target.x - root.anchorGap, targetCenter.y)
let leftRegion = Qt.rect(source.x,
source.y,
(leftAnchor.x < source.left) ? 0 : Math.abs(leftAnchor.x - source.left),
source.height)
edges[Qt.LeftEdge] = {
anchor: leftAnchor,
@@ -221,7 +232,9 @@ QtObject {
function popoverGeometry(edge: int, anchor: point, region: rect) {
if (edge === Qt.TopEdge) {
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,
Math.min(window.width, region.width),
height)
@@ -230,14 +243,18 @@ QtObject {
if (edge === Qt.RightEdge) {
let width = Math.min(window.width, region.width)
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,
Math.min(window.height, region.height))
}
if (edge === Qt.BottomEdge) {
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,
Math.min(window.width, region.width),
height)
@@ -246,7 +263,9 @@ QtObject {
if (edge === Qt.LeftEdge) {
let width = Math.min(window.width, region.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,
Math.min(window.height, region.height))
}
@@ -270,6 +289,9 @@ QtObject {
if (!focusWindow)
return
if (root.keepOpen)
return
if (focusWindow !== window && focusWindow.transientParent !== window)
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 {
id: chevron
@@ -381,7 +406,7 @@ QtObject {
PathLine { id: end; x: 0; y: 0 }
}
}
*/
Column {
id: column
anchors.fill: parent
@@ -392,6 +417,17 @@ QtObject {
width: parent.width
height: StudioTheme.Values.titleBarHeight
DragHandler {
id: dragHandler
target: null
grabPermissions: PointerHandler.CanTakeOverFromAnything
onActiveChanged: {
if (dragHandler.active)
window.startSystemMove() // QTBUG-102488
}
}
Row {
id: row
anchors.fill: parent

View File

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

View File

@@ -9,11 +9,11 @@ import StudioTheme 1.0 as StudioTheme
T.Label {
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)
color: control.style.text.idle
font.pixelSize: control.style.baseFontSize
color: control.controlStyle.text.idle
font.pixelSize: control.controlStyle.baseFontSize
elide: Text.ElideRight
Layout.preferredWidth: width

View File

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

View File

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

View File

@@ -63,7 +63,8 @@ Item {
model: backend.kits
onActivated: backend.setCurrentKit(kits.currentIndex)
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
onKitIndexChanged: kits.currentIndex = backend.currentKit
}

View File

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

View File

@@ -382,6 +382,22 @@
{
"source": "../shared-plugin/name/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",
"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"
}
Files {
filter: "*.json"
}
Files {
filter: "*.mesh"
directory: "asset_imports"
@@ -110,7 +114,7 @@ Project {
/* Required for deployment */
targetDirectory: "/opt/%{ProjectName}"
qdsVersion: "4.4"
qdsVersion: "4.3"
quickVersion: "%{QtQuickVersion}"

View File

@@ -382,6 +382,22 @@
{
"source": "../shared-plugin/name/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",
"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",
"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",
"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

@@ -11,9 +11,14 @@ qt6_add_qml_module(%{ImportModuleName}
URI "%{ImportModuleName}"
VERSION 1.0
RESOURCE_PREFIX "/qt/qml"
QML_FILES
QML_FILES
Constants.qml
DataStore.qml
DirectoryFontLoader.qml
EventListModel.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}
singleton DataStore 1.0 DataStore.qml
singleton Constants 1.0 Constants.qml
EventListSimulator 1.0 EventListSimulator.qml
EventListModel 1.0 EventListModel.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 DataStore 1.0 DataStore.qml
EventListModel 1.0 EventListModel.qml
EventListSimulator 1.0 EventListSimulator.qml
DirectoryFontLoader 1.0 DirectoryFontLoader.qml

View File

@@ -261,6 +261,22 @@
"source": "%{QdsWizardPath}/shared-plugin/name/designer/plugin.metainfo",
"target": "%{ProjectDirectory}/imports/%{ImportModuleName}/designer/plugin.metainfo",
"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,
TitleBarButtonUndock,
TitleBarButtonClose,
TitleBarButtonAutoHide
TitleBarButtonAutoHide,
TitleBarButtonMinimize
};
/**
@@ -83,16 +84,17 @@ enum eDragState {
* The different icons used in the UI
*/
enum eIcon {
TabCloseIcon, //!< TabCloseIcon
AutoHideIcon, //!< AutoHideIcon
DockAreaMenuIcon, //!< DockAreaMenuIcon
DockAreaUndockIcon, //!< DockAreaUndockIcon
DockAreaCloseIcon, //!< DockAreaCloseIcon
TabCloseIcon, //!< TabCloseIcon
AutoHideIcon, //!< AutoHideIcon
DockAreaMenuIcon, //!< DockAreaMenuIcon
DockAreaUndockIcon, //!< DockAreaUndockIcon
DockAreaCloseIcon, //!< DockAreaCloseIcon
DockAreaMinimizeIcon,
FloatingWidgetCloseIcon, //!< FloatingWidgetCloseIcon
FloatingWidgetNormalIcon, //!< FloatingWidgetNormalIcon
FloatingWidgetMaximizeIcon, //!< FloatingWidgetMaximizeIcon
IconCount, //!< just a delimiter for range checks
IconCount, //!< just a delimiter for range checks
};
/**
@@ -161,8 +163,8 @@ bool isSideBarArea(DockWidgetArea area);
/**
* 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.
* It is not safe to use this function in in DockWidget because only the current dock widget has a
* widget or nullptr if the widget is not child of any widget of type T.
* 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.
*/
template<class T>
@@ -171,12 +173,12 @@ T findParent(const QWidget *widget)
QWidget *parentWidget = widget->parentWidget();
while (parentWidget) {
T parentImpl = qobject_cast<T>(parentWidget);
if (parentImpl) {
if (parentImpl)
return parentImpl;
}
parentWidget = parentWidget->parentWidget();
}
return 0;
return nullptr;
}
/**

View File

@@ -46,10 +46,11 @@ public:
QPointer<TitleBarButton> m_autoHideButton;
QPointer<TitleBarButton> m_undockButton;
QPointer<TitleBarButton> m_closeButton;
QPointer<TitleBarButton> m_minimizeButton;
QBoxLayout *m_layout = nullptr;
DockAreaWidget *m_dockArea = nullptr;
DockAreaTabBar *m_tabBar = nullptr;
ElidingLabel *m_autoHideTitleLabel;
ElidingLabel *m_autoHideTitleLabel = nullptr;
bool m_menuOutdated = true;
QMenu *m_tabsMenu;
QList<TitleBarButtonType *> m_dockWidgetActionsButtons;
@@ -198,6 +199,23 @@ void DockAreaTitleBarPrivate::createButtons()
q,
&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
m_closeButton = new TitleBarButton(testConfigFlag(DockManager::DockAreaHasCloseButton));
m_closeButton->setObjectName("dockAreaCloseButton");
@@ -224,7 +242,10 @@ void DockAreaTitleBarPrivate::createAutoHideTitleLabel()
{
m_autoHideTitleLabel = new ElidingLabel("");
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()
{
@@ -366,10 +387,8 @@ DockAreaTitleBar::DockAreaTitleBar(DockAreaWidget *parent)
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
d->createTabBar();
d->createAutoHideTitleLabel();
d->m_autoHideTitleLabel->setVisible(false); // Default hidden
d->m_layout->addWidget(new SpacerWidget(this));
d->createButtons();
d->createAutoHideTitleLabel();
setFocusPolicy(Qt::NoFocus);
}
@@ -448,6 +467,18 @@ void DockAreaTitleBar::onCloseButtonClicked()
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()
{
if (d->m_dockArea->features().testFlag(DockWidget::DockWidgetFloatable))
@@ -533,6 +564,8 @@ TitleBarButton *DockAreaTitleBar::button(eTitleBarButton which) const
return d->m_closeButton;
case TitleBarButtonAutoHide:
return d->m_autoHideButton;
case TitleBarButtonMinimize:
return d->m_minimizeButton;
}
return nullptr;
}
@@ -676,12 +709,28 @@ void DockAreaTitleBar::contextMenuEvent(QContextMenuEvent *event)
}
menu.addSeparator();
}
QAction *closeAction = menu.addAction(isAutoHide ? Tr::tr("Close") : Tr::tr("Close Group"));
closeAction->connect(closeAction,
&QAction::triggered,
this,
&DockAreaTitleBar::onCloseButtonClicked);
closeAction->setEnabled(d->m_dockArea->features().testFlag(DockWidget::DockWidgetClosable));
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,
&QAction::triggered,
this,
&DockAreaTitleBar::onCloseButtonClicked);
closeAction->setEnabled(d->m_dockArea->features().testFlag(DockWidget::DockWidgetClosable));
}
if (!isAutoHide && !isTopLevelArea) {
QAction *closeOthersAction = menu.addAction(Tr::tr("Close Other Groups"));
@@ -718,8 +767,11 @@ QString DockAreaTitleBar::titleBarButtonToolTip(eTitleBarButton button) const
break;
case TitleBarButtonClose:
if (d->m_dockArea->isAutoHide())
return Tr::tr("Close");
if (d->m_dockArea->isAutoHide()) {
bool minimize = DockManager::testAutoHideConfigFlag(
DockManager::AutoHideCloseButtonCollapsesDock);
return minimize ? Tr::tr("Minimize") : Tr::tr("Close");
}
if (DockManager::testConfigFlag(DockManager::DockAreaCloseButtonClosesTab))
return Tr::tr("Close Active Tab");
@@ -748,4 +800,11 @@ void DockAreaTitleBar::setAreaFloating()
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

View File

@@ -84,6 +84,8 @@ private:
void onTabsMenuAboutToShow();
void onCloseButtonClicked();
void onAutoHideCloseActionTriggered();
void minimizeAutoHideContainer();
void onUndockButtonClicked();
void onTabsMenuActionTriggered(QAction *action);
void onCurrentTabChanged(int index);
@@ -190,6 +192,11 @@ public:
*/
void setAreaFloating();
/**
* Call this function, to create all the required auto hide controls
*/
void showAutoHideControls(bool show);
signals:
/**
* 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)
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(TitleBarButtonAutoHide)->setVisible(!container->isFloating());
// Undock and tabs should never show when auto hidden
m_titleBar->button(TitleBarButtonUndock)
->setVisible(!container->isFloating() && !q->isAutoHide());
m_titleBar->button(TitleBarButtonTabsMenu)->setVisible(!q->isAutoHide());
m_titleBar->button(TitleBarButtonUndock)->setVisible(!container->isFloating());
m_titleBar->button(TitleBarButtonTabsMenu)->setVisible(true);
} else {
m_titleBar->button(TitleBarButtonClose)->setVisible(true);
m_titleBar->button(TitleBarButtonAutoHide)->setVisible(true);
@@ -650,10 +657,7 @@ void DockAreaWidget::updateTitleBarVisibility()
}
if (isAutoHideFeatureEnabled()) {
auto tabBar = d->m_titleBar->tabBar();
tabBar->setVisible(!autoHide); // Never show tab bar when auto hidden
// Always show when auto hidden, never otherwise
d->m_titleBar->autoHideTitleLabel()->setVisible(autoHide);
d->m_titleBar->showAutoHideControls(autoHide);
updateTitleBarButtonVisibility(container->topLevelDockArea() == this);
}
}

View File

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

View File

@@ -87,6 +87,11 @@ public:
DockWidget *m_centralWidget = nullptr;
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;
QList<Workspace> m_workspaces;
Workspace m_workspace;
@@ -94,6 +99,7 @@ public:
QtcSettings *m_settings = nullptr;
bool m_modeChangeState = false;
bool m_wasShown = false;
bool m_workspaceOrderDirty = false;
/**
@@ -364,8 +370,10 @@ DockManager::DockManager(QWidget *parent)
DockManager::~DockManager()
{
emit aboutToUnloadWorkspace(d->m_workspace.fileName());
save();
if (d->m_wasShown) {
emit aboutToUnloadWorkspace(d->m_workspace.fileName());
save();
}
saveStartupWorkspace();
saveLockWorkspace();
@@ -766,6 +774,38 @@ QString DockManager::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
{
return d->m_centralWidget;
@@ -1324,6 +1364,11 @@ bool DockManager::isModeChangeState() const
return d->m_modeChangeState;
}
void DockManager::aboutToShow()
{
d->m_wasShown = true;
}
expected_str<QString> DockManager::importWorkspace(const QString &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
AutoHideCloseButtonCollapsesDock
= 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
= AutoHideFeatureEnabled | DockAreaHasAutoHideButton
| AutoHideCloseButtonCollapsesDock ///< the default configuration for left and right side bars
DefaultAutoHideConfig = AutoHideFeatureEnabled | DockAreaHasAutoHideButton
| AutoHideCloseButtonCollapsesDock | AutoHideHasCloseButton
};
Q_DECLARE_FLAGS(AutoHideFlags, eAutoHideFlag)
@@ -452,6 +455,31 @@ public:
*/
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.
*/
@@ -733,6 +761,13 @@ public:
static QString readDisplayName(const Utils::FilePath &filePath);
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:
void aboutToUnloadWorkspace(QString fileName);
void aboutToLoadWorkspace(QString fileName);

View File

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

View File

@@ -64,6 +64,7 @@ public:
= DockWidget::MinimumSizeHintFromDockWidget;
WidgetFactory *m_factory = nullptr;
QPointer<AutoHideTab> m_sideTabWidget;
DockWidget::eToolBarStyleSource m_toolBarStyleSource = DockWidget::ToolBarStyleFromDockManager;
/**
* Private data constructor
@@ -106,6 +107,11 @@ public:
* Creates the content widget with the registered widget factory and returns true on success.
*/
bool createWidgetFromFactory();
/**
* Use the dock manager toolbar style and icon size for the different states
*/
void setToolBarStyleFromDockManager();
}; // class DockWidgetPrivate
DockWidgetPrivate::DockWidgetPrivate(DockWidget *parent)
@@ -246,6 +252,19 @@ bool DockWidgetPrivate::createWidgetFromFactory()
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)
: QFrame(parent)
, d(new DockWidgetPrivate(this))
@@ -290,8 +309,11 @@ void DockWidget::setWidget(QWidget *widget, eInsertMode insertMode)
auto scrollAreaWidget = qobject_cast<QAbstractScrollArea *>(widget);
if (scrollAreaWidget || ForceNoScrollArea == insertMode) {
d->m_layout->addWidget(widget);
if (scrollAreaWidget && scrollAreaWidget->viewport())
scrollAreaWidget->viewport()->setProperty("dockWidgetContent", true);
if (scrollAreaWidget) {
if (scrollAreaWidget->viewport())
scrollAreaWidget->viewport()->setProperty("dockWidgetContent", true);
scrollAreaWidget->setProperty("focused", isFocused());
}
} else {
d->setupScrollArea();
d->m_scrollArea->setWidget(widget);
@@ -381,6 +403,12 @@ DockManager *DockWidget::dockManager() const
void DockWidget::setDockManager(DockManager *dockManager)
{
d->m_dockManager = dockManager;
if (!dockManager)
return;
if (ToolBarStyleFromDockManager == d->m_toolBarStyleSource)
d->setToolBarStyleFromDockManager();
}
DockContainerWidget *DockWidget::dockContainer() const
@@ -457,6 +485,11 @@ void DockWidget::setFocused(bool focused)
if (d->m_scrollArea)
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");
QList<QQuickWidget *> quickWidgets = d->m_widget->findChildren<QQuickWidget *>();
@@ -491,6 +524,18 @@ QAction *DockWidget::toggleViewAction() const
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)
{
if (ActionModeToggle == mode) {
@@ -693,6 +738,18 @@ void DockWidget::setToolBar(QToolBar *toolBar)
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)
{
if (StateFloating == state)

View File

@@ -154,6 +154,8 @@ public:
enum eState { StateHidden, StateDocked, StateFloating };
enum eToolBarStyleSource { ToolBarStyleFromDockManager, ToolBarStyleFromDockWidget };
/**
* Sets the widget for the dock widget to widget.
* The InsertMode defines how the widget is inserted into the dock widget.
@@ -385,6 +387,12 @@ public:
*/
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.
* \see eToggleViewActionMode for a detailed description
@@ -443,6 +451,17 @@ public:
*/
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.
* 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;
default:
if (DockManager::testConfigFlag(DockManager::FocusHighlighting))
d->focusController()->setDockWidgetTabPressed(false);
break;
}
if (DockManager::testConfigFlag(DockManager::FocusHighlighting))
d->focusController()->setDockWidgetTabPressed(false);
} else if (event->button() == Qt::MiddleButton) {
if (DockManager::testConfigFlag(DockManager::MiddleMouseButtonClosesTab)
&& d->m_dockWidget->features().testFlag(DockWidget::DockWidgetClosable)) {

View File

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

View File

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

View File

@@ -1485,8 +1485,10 @@ QList<const CppComponentValue *> CppQmlTypes::createObjectsForImport(const QStri
// if it already exists, skip
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;
}
ComponentVersion cppVersion;
for (const FakeMetaObject::Export &bestExport : std::as_const(bestExports)) {

View File

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

View File

@@ -14,7 +14,8 @@
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;
if (jsonObject.isEmpty()) {
@@ -58,6 +59,11 @@ QString CompositionNode::description() const
return m_description;
}
QString CompositionNode::id() const
{
return m_id;
}
QObject *CompositionNode::uniformsModel()
{
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
{
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_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
QJsonArray jsonProps = json.value("properties").toArray();
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)) {
QString trimmedLine = codeLine.trimmed();
if (trimmedLine.startsWith("@requires")) {
// Get the required node, remove "@requires"
QString l = trimmedLine.sliced(9).trimmed();
// Get the required node, remove "@requires "
QString nodeName = trimmedLine.sliced(10);
if (!nodeName.isEmpty() && !m_requiredNodes.contains(nodeName))
m_requiredNodes << nodeName;
@@ -132,6 +149,36 @@ QList<Uniform *> CompositionNode::uniforms() const
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
{
return m_name;

View File

@@ -15,7 +15,8 @@ class CompositionNode : public QObject
Q_OBJECT
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)
public:
@@ -30,6 +31,7 @@ public:
QString fragmentCode() const;
QString vertexCode() const;
QString description() const;
QString id() const;
QObject *uniformsModel();
@@ -40,13 +42,20 @@ public:
bool isEnabled() const;
void setIsEnabled(bool newIsEnabled);
bool isDependency() const;
QString name() const;
QList<Uniform *> uniforms() const;
int incRefCount();
int decRefCount();
void setRefCount(int count);
signals:
void uniformsModelChanged();
void isEnabledChanged();
void isDepencyChanged();
private:
void parse(const QString &effectName, const QString &qenPath, const QJsonObject &json);
@@ -57,7 +66,9 @@ private:
QString m_vertexCode;
QString m_description;
QStringList m_requiredNodes;
QString m_id;
bool m_isEnabled = true;
int m_refCount = 0;
QList<Uniform*> m_uniforms;

View File

@@ -4,6 +4,8 @@
#include "effectmakermodel.h"
#include "compositionnode.h"
#include "effectutils.h"
#include "propertyhandler.h"
#include "syntaxhighlighterdata.h"
#include "uniform.h"
@@ -21,6 +23,7 @@
#include <modelnodeoperations.h>
#include <QByteArrayView>
#include <QLibraryInfo>
#include <QVector2D>
namespace EffectMaker {
@@ -57,6 +60,7 @@ QHash<int, QByteArray> EffectMakerModel::roleNames() const
roles[NameRole] = "nodeName";
roles[EnabledRole] = "nodeEnabled";
roles[UniformsRole] = "nodeUniformsModel";
roles[Dependency] = "isDependency";
return roles;
}
@@ -103,14 +107,45 @@ void EffectMakerModel::setIsEmpty(bool val)
void EffectMakerModel::addNode(const QString &nodeQenPath)
{
beginInsertRows({}, m_nodes.size(), m_nodes.size());
auto *node = new CompositionNode("", nodeQenPath);
beginResetModel();
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);
endInsertRows();
endResetModel();
setIsEmpty(false);
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)
@@ -123,21 +158,67 @@ void EffectMakerModel::moveNode(int fromIdx, int toIdx)
m_nodes.move(fromIdx, toIdx);
endMoveRows();
setHasUnsavedChanges(true);
bakeShaders();
}
void EffectMakerModel::removeNode(int idx)
{
beginRemoveRows({}, idx, idx);
CompositionNode *node = m_nodes.at(idx);
m_nodes.removeAt(idx);
beginResetModel();
CompositionNode *node = m_nodes.takeAt(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;
endRemoveRows();
endResetModel();
if (m_nodes.isEmpty())
setIsEmpty(true);
else
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
@@ -171,25 +252,7 @@ const QString &EffectMakerModel::qmlComponentString() const
return m_qmlComponentString;
}
void EffectMakerModel::clear()
{
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()
const QList<Uniform *> EffectMakerModel::allUniforms() const
{
QList<Uniform *> uniforms = {};
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("enabled", node.isEnabled());
nodeObject.insert("version", 1);
nodeObject.insert("id", node.id());
// Add properties
QJsonArray propertiesArray;
const QList<Uniform *> uniforms = node.uniforms();
@@ -523,14 +588,24 @@ QString EffectMakerModel::getQmlEffectString()
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 += "}\n";
return s;
}
void EffectMakerModel::exportComposition(const QString &name)
void EffectMakerModel::saveComposition(const QString &name)
{
const QString effectsAssetsDir = QmlDesigner::ModelNodeOperations::getEffectsDefaultDirectory();
const QString path = effectsAssetsDir + QDir::separator() + name + ".qep";
@@ -561,12 +636,19 @@ void EffectMakerModel::exportComposition(const QString &name)
saveFile.write(jsonDoc.toJson());
saveFile.close();
setCurrentComposition(name);
setHasUnsavedChanges(false);
saveResources(name);
}
void EffectMakerModel::openComposition(const QString &path)
{
clear();
const QString effectName = QFileInfo(path).baseName();
setCurrentComposition(effectName);
QFile compFile(path);
if (!compFile.open(QIODevice::ReadOnly)) {
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();
if (data.isEmpty())
return;
QJsonParseError parseError;
QJsonDocument jsonDoc(QJsonDocument::fromJson(data, &parseError));
if (parseError.error != QJsonParseError::NoError) {
@@ -605,28 +691,40 @@ void EffectMakerModel::openComposition(const QString &path)
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()) {
beginResetModel();
QHash<QString, int> refCounts;
const QJsonArray nodesArray = json["nodes"].toArray();
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);
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());
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
updateCustomUniforms();
@@ -682,7 +780,7 @@ void EffectMakerModel::exportResources(const QString &name)
if (line.startsWith("vertexShader")) {
QString vsLine = " vertexShader: '" + vsFilename + "'";
qmlStringList[i] = vsLine;
} else if (line.startsWith("fragmentShader")) {
} else if (line.startsWith("fragmentShader")) {
QString fsLine = " fragmentShader: '" + fsFilename + "'";
qmlStringList[i] = fsLine;
}
@@ -692,7 +790,7 @@ void EffectMakerModel::exportResources(const QString &name)
QString qmlFilePath = effectsResPath + qmlFilename;
writeToFile(qmlString.toUtf8(), qmlFilePath, FileType::Text);
// Export shaders and images
// Save shaders and images
QStringList sources = {m_vertexShaderFilename, m_fragmentShaderFilename};
QStringList dests = {vsFilename, fsFilename};
@@ -702,11 +800,28 @@ void EffectMakerModel::exportResources(const QString &name)
QString imagePath = uniform->value().toString();
QFileInfo fi(imagePath);
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);
}
}
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) {
Utils::FilePath source = Utils::FilePath::fromString(sources[i]);
Utils::FilePath target = Utils::FilePath::fromString(effectsResPath + dests[i]);
@@ -716,6 +831,8 @@ void EffectMakerModel::exportResources(const QString &name)
if (!source.copyFile(target))
qWarning() << __FUNCTION__ << " Failed to copy file: " << source;
}
emit resourcesSaved(QString("Effects.%1.%1").arg(name).toUtf8(), effectPath);
}
void EffectMakerModel::resetEffectError(int type)
@@ -737,16 +854,18 @@ QString EffectMakerModel::valueAsString(const Uniform &uniform)
return QString::number(uniform.value().toDouble());
} else if (uniform.type() == Uniform::Type::Vec2) {
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) {
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) {
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) {
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();
} else {
qWarning() << QString("Unhandled const variable type: %1").arg(int(uniform.type())).toLatin1();
@@ -1085,18 +1204,25 @@ QString EffectMakerModel::generateFragmentShader(bool includeUniforms)
return s;
}
void EffectMakerModel::handleQsbProcessExit(Utils::Process *qsbProcess, const QString &shader)
void EffectMakerModel::handleQsbProcessExit(Utils::Process *qsbProcess, const QString &shader, bool preview)
{
--m_remainingQsbTargets;
const QString errStr = qsbProcess->errorString();
const QByteArray errStd = qsbProcess->readAllRawStandardError();
if (!errStr.isEmpty())
qWarning() << QString("Failed to generate QSB file for: %1 %2").arg(shader, errStr);
QString previewStr;
if (preview)
previewStr = QStringLiteral("preview");
if (!errStd.isEmpty())
qWarning() << QString("Failed to generate QSB file for: %1 %2")
.arg(shader, QString::fromUtf8(errStd));
if (!errStr.isEmpty()) {
qWarning() << QString("Failed to generate %3 QSB file for: %1 %2")
.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) {
Q_EMIT shadersBaked();
@@ -1182,21 +1308,30 @@ void EffectMakerModel::createFiles()
QFile(m_vertexShaderFilename).remove();
if (QFileInfo(m_fragmentShaderFilename).exists())
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 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_fragmentSourceFile.setFileTemplate(QDir::tempPath() + "/dsem_XXXXXX.frag");
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";
} else {
m_vertexSourceFilename = m_vertexSourceFile.fileName();
m_fragmentSourceFilename = m_fragmentSourceFile.fileName();
m_vertexShaderFilename = vertexShaderFile.fileName();
m_fragmentShaderFilename = fragmentShaderFile.fileName();
m_vertexShaderPreviewFilename = vertexShaderPreviewFile.fileName();
m_fragmentShaderPreviewFilename = fragmentShaderPreviewFile.fileName();
}
}
@@ -1243,27 +1378,43 @@ void EffectMakerModel::bakeShaders()
Utils::FilePath qsbPath = qtVer->binPath().pathAppended("qsb").withExecutableSuffix();
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;
}
m_remainingQsbTargets = 2; // We only have 2 shaders
const QStringList srcPaths = {m_vertexSourceFilename, m_fragmentSourceFilename};
const QStringList outPaths = {m_vertexShaderFilename, m_fragmentShaderFilename};
for (int i = 0; i < 2; ++i) {
const auto workDir = Utils::FilePath::fromString(outPaths[i]);
// TODO: Optional legacy glsl support like standalone effect maker needs to add "100es,120"
QStringList args = {"-s", "--glsl", "300es,140,330,410", "--hlsl", "50", "--msl", "12"};
args << "-o" << outPaths[i] << srcPaths[i];
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) {
const auto workDir = Utils::FilePath::fromString(outPaths[i]);
// TODO: Optional legacy glsl support like standalone effect maker needs to add "100es,120"
QStringList args = {"-s", "--glsl", "300es,140,330,410", "--hlsl", "50", "--msl", "12"};
args << "-o" << outPaths[i] << srcPaths[i];
auto qsbProcess = new Utils::Process(this);
connect(qsbProcess, &Utils::Process::done, this, [=] {
handleQsbProcessExit(qsbProcess, srcPaths[i], preview);
});
qsbProcess->setWorkingDirectory(workDir.absolutePath());
qsbProcess->setCommand({qsbPath, args});
qsbProcess->start();
}
};
runQsb(qsbPath, outPaths, false);
runQsb(qsbPrevPath, outPrevPaths, true);
auto qsbProcess = new Utils::Process(this);
connect(qsbProcess, &Utils::Process::done, this, [=] {
handleQsbProcessExit(qsbProcess, srcPaths[i]);
});
qsbProcess->setWorkingDirectory(workDir.absolutePath());
qsbProcess->setCommand({qsbPath, args});
qsbProcess->start();
}
}
bool EffectMakerModel::shadersUpToDate() const
@@ -1375,8 +1526,10 @@ QString EffectMakerModel::getQmlComponentString(bool localFiles)
s += '\n' + customImagesString;
s += '\n';
s += l2 + "vertexShader: 'file:///" + m_vertexShaderFilename + "'\n";
s += l2 + "fragmentShader: 'file:///" + m_fragmentShaderFilename + "'\n";
const QString vertFile = localFiles ? m_vertexShaderFilename : m_vertexShaderPreviewFilename;
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";
if (m_shaderFeatures.enabled(ShaderFeatures::GridMesh)) {
QString gridSize = QString("%1, %2").arg(m_shaderFeatures.gridMeshWidth())
@@ -1398,10 +1551,41 @@ void EffectMakerModel::setCurrentComposition(const QString &newCurrentCompositio
{
if (m_currentComposition == newCurrentComposition)
return;
m_currentComposition = newCurrentComposition;
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()
{
// Clear possible QML runtime errors

View File

@@ -7,10 +7,10 @@
#include <utils/filepath.h>
#include <QAbstractListModel>
#include <QFileSystemWatcher>
#include <QMap>
#include <QRegularExpression>
#include <QStandardItemModel>
#include <QTemporaryFile>
namespace ProjectExplorer {
@@ -44,6 +44,7 @@ class EffectMakerModel : public QAbstractListModel
Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged)
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(QString qmlComponentString READ qmlComponentString)
Q_PROPERTY(QString currentComposition READ currentComposition WRITE setCurrentComposition NOTIFY currentCompositionChanged)
@@ -61,8 +62,13 @@ public:
void addNode(const QString &nodeQenPath);
CompositionNode *findNodeById(const QString &id) const;
Q_INVOKABLE void moveNode(int fromIdx, int toIdx);
Q_INVOKABLE void removeNode(int idx);
Q_INVOKABLE void clear();
Q_INVOKABLE void assignToSelected();
Q_INVOKABLE QString getUniqueEffectName() const;
bool shadersUpToDate() const;
void setShadersUpToDate(bool newShadersUpToDate);
@@ -75,35 +81,43 @@ public:
const QString &qmlComponentString() const;
void clear();
Q_INVOKABLE void updateQmlComponent();
Q_INVOKABLE void resetEffectError(int type);
Q_INVOKABLE void setEffectError(const QString &errorMessage, int type = -1, int lineNumber = -1);
Q_INVOKABLE void exportComposition(const QString &name);
Q_INVOKABLE void exportResources(const QString &name);
Q_INVOKABLE void saveComposition(const QString &name);
void openComposition(const QString &path);
QString currentComposition() const;
void setCurrentComposition(const QString &newCurrentComposition);
bool hasUnsavedChanges() const;
void setHasUnsavedChanges(bool val);
QStringList uniformNames() const;
Q_INVOKABLE bool isDependencyNode(int index) const;
signals:
void isEmptyChanged();
void selectedIndexChanged(int idx);
void effectErrorChanged();
void shadersUpToDateChanged();
void shadersBaked();
void currentCompositionChanged();
void nodesChanged();
void resourcesSaved(const QByteArray &type, const Utils::FilePath &path);
void hasUnsavedChangesChanged();
void assignToSelectedTriggered(const QString &effectPath);
private:
enum Roles {
NameRole = Qt::UserRole + 1,
EnabledRole,
UniformsRole
UniformsRole,
Dependency
};
enum ErrorTypes {
@@ -117,7 +131,7 @@ private:
bool isValidIndex(int idx) const;
const QList<Uniform *> allUniforms();
const QList<Uniform *> allUniforms() const;
const QString getBufUniform();
const QString getVSUniforms();
@@ -142,13 +156,14 @@ private:
QString getCustomShaderVaryings(bool outState);
QString generateVertexShader(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 getQmlEffectString();
void updateCustomUniforms();
void createFiles();
void bakeShaders();
void saveResources(const QString &name);
QString mipmapPropertyName(const QString &name) const;
QString getQmlImagesString(bool localFiles);
@@ -158,6 +173,7 @@ private:
int m_selectedIndex = -1;
bool m_isEmpty = true;
bool m_hasUnsavedChanges = false;
// True when shaders haven't changed since last baking
bool m_shadersUpToDate = true;
int m_remainingQsbTargets = 0;
@@ -175,6 +191,8 @@ private:
QString m_vertexSourceFilename;
QString m_fragmentShaderFilename;
QString m_vertexShaderFilename;
QString m_fragmentShaderPreviewFilename;
QString m_vertexShaderPreviewFilename;
// Used in exported QML, at root of the file
QString m_exportedRootPropertiesString;
// 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
#include "effectmakernodesmodel.h"
#include "effectutils.h"
#include <utils/filepath.h>
#include <utils/hostosinfo.h>
#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));
}
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()
{
findNodesPath();
if (m_modelLoaded)
return;
if (!m_nodesPath.exists()) {
auto nodesPath = Utils::FilePath::fromString(EffectUtils::nodesSourcesPath());
if (!nodesPath.exists()) {
qWarning() << __FUNCTION__ << "Effects not found.";
return;
}
m_categories = {};
QDirIterator itCategories(m_nodesPath.toString(), QDir::Dirs | QDir::NoDotAndDotDot);
QDirIterator itCategories(nodesPath.toString(), QDir::Dirs | QDir::NoDotAndDotDot);
while (itCategories.hasNext()) {
itCategories.next();
@@ -85,7 +64,7 @@ void EffectMakerNodesModel::loadModel()
QString catName = itCategories.fileName();
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);
while (itEffects.hasNext()) {
itEffects.next();
@@ -102,6 +81,8 @@ void EffectMakerNodesModel::loadModel()
return a->name() < b->name();
});
m_modelLoaded = true;
resetModel();
}
@@ -111,5 +92,20 @@ void EffectMakerNodesModel::resetModel()
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 <utils/filepath.h>
#include <QStandardItemModel>
namespace EffectMaker {
@@ -32,12 +30,14 @@ public:
QList<EffectNodesCategory *> categories() const { return m_categories; }
void updateCanBeAdded(const QStringList &uniforms);
private:
void findNodesPath();
QString nodesSourcesPath() const;
QList<EffectNodesCategory *> m_categories;
Utils::FilePath m_nodesPath;
bool m_probeNodesDir = false;
bool m_modelLoaded = false;
};
} // namespace EffectMaker

View File

@@ -3,6 +3,7 @@
#include "effectmakerview.h"
#include <coreplugin/icore.h>
#include <extensionsystem/iplugin.h>
#include <viewmanager.h>
@@ -10,6 +11,14 @@
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
{
Q_OBJECT
@@ -17,11 +26,13 @@ class EffectMakerPlugin final : public ExtensionSystem::IPlugin
bool delayedInitialize() final
{
auto designerPlugin = QmlDesigner::QmlDesignerPlugin::instance();
auto &viewManager = designerPlugin->viewManager();
viewManager.registerView(std::make_unique<EffectMakerView>(
QmlDesigner::QmlDesignerPlugin::externalDependenciesForPluginInitializationOnly()));
if (enableEffectMaker()) {
auto *designerPlugin = QmlDesigner::QmlDesignerPlugin::instance();
auto &viewManager = designerPlugin->viewManager();
viewManager.registerView(std::make_unique<EffectMakerView>(
QmlDesigner::QmlDesignerPlugin::externalDependenciesForPluginInitializationOnly()));
}
return true;
}
};

View File

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

View File

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

View File

@@ -7,16 +7,11 @@
#include "effectmakernodesmodel.h"
#include "effectmakerwidget.h"
#include "nodeinstanceview.h"
#include "qmldesignerconstants.h"
#include <coreplugin/icore.h>
#include <modelnodeoperations.h>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQuickItem>
#include <QQuickView>
#include <QTimer>
#include <coreplugin/icore.h>
namespace EffectMaker {
@@ -51,6 +46,15 @@ QmlDesigner::WidgetInfo EffectMakerView::widgetInfo()
if (m_widget.isNull()) {
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());
Core::ICore::addContextObject(context);
}
@@ -66,7 +70,7 @@ void EffectMakerView::customNotification([[maybe_unused]] const AbstractView *vi
{
if (identifier == "open_effectmaker_composition" && data.count() > 0) {
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

View File

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

View File

@@ -7,26 +7,33 @@
#include "effectmakermodel.h"
#include "effectmakernodesmodel.h"
#include "effectmakerview.h"
#include "effectutils.h"
#include "propertyhandler.h"
//#include "qmldesigner/designercore/imagecache/midsizeimagecacheprovider.h"
#include "qmldesignerconstants.h"
#include "qmldesignerplugin.h"
#include "qqmlcontext.h"
#include "theme.h"
#include <coreplugin/icore.h>
#include <qmldesigner/documentmanager.h>
#include <qmldesigner/qmldesignerconstants.h>
#include <qmldesigner/qmldesignerplugin.h>
#include <studioquickwidget.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
#include <utils/algorithm.h>
#include <utils/async.h>
#include <utils/environment.h>
#include <utils/qtcassert.h>
#include <QHBoxLayout>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQuickItem>
#include <QTimer>
namespace EffectMaker {
@@ -55,6 +62,7 @@ EffectMakerWidget::EffectMakerWidget(EffectMakerView *view)
m_quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
QmlDesigner::Theme::setupTheme(m_quickWidget->engine());
m_quickWidget->engine()->addImportPath(propertyEditorResourcesPath() + "/imports");
m_quickWidget->engine()->addImportPath(EffectUtils::nodesSourcesPath() + "/common");
m_quickWidget->setClearColor(QmlDesigner::Theme::getColor(
QmlDesigner::Theme::Color::QmlDesigner_BackgroundColorDarkAlternate));
@@ -70,12 +78,38 @@ EffectMakerWidget::EffectMakerWidget(EffectMakerView *view)
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");
map->setProperties({{"effectMakerNodesModel", QVariant::fromValue(m_effectMakerNodesModel.data())},
{"effectMakerModel", QVariant::fromValue(m_effectMakerModel.data())},
{"rootView", QVariant::fromValue(this)}});
QmlDesigner::QmlDesignerPlugin::trackWidgetFocusTime(
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)
}
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
{
return {420, 420};
@@ -152,6 +200,21 @@ void EffectMakerWidget::initView()
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()
{
const QString effectMakerQmlPath = qmlSourcesPath() + "/EffectMaker.qml";
@@ -159,5 +222,65 @@ void EffectMakerWidget::reloadQmlSource()
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

View File

@@ -9,9 +9,14 @@
#include <coreplugin/icontext.h>
#include <QFrame>
#include <QFuture>
class StudioQuickWidget;
QT_BEGIN_NAMESPACE
class QTimer;
QT_END_NAMESPACE
namespace EffectMaker {
class EffectMakerView;
@@ -34,6 +39,7 @@ public:
void delayedUpdateModel();
void updateModel();
void initView();
void openComposition(const QString &path);
StudioQuickWidget *quickWidget() const;
QPointer<EffectMakerModel> effectMakerModel() const;
@@ -41,6 +47,9 @@ public:
Q_INVOKABLE void addEffectNode(const QString &nodeQenPath);
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;
@@ -49,6 +58,7 @@ protected:
private:
void reloadQmlSource();
void handleImportScanTimer();
QPointer<EffectMakerModel> m_effectMakerModel;
QPointer<EffectMakerNodesModel> m_effectMakerNodesModel;
@@ -56,6 +66,17 @@ private:
QPointer<StudioQuickWidget> m_quickWidget;
QmlDesigner::QmlModelNodeProxy m_backendModelNode;
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

View File

@@ -2,6 +2,8 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "effectnode.h"
#include "compositionnode.h"
#include "uniform.h"
#include <QDir>
#include <QFileInfo>
@@ -22,6 +24,12 @@ EffectNode::EffectNode(const QString &qenPath)
iconPath = QStringLiteral("%1/%2").arg(parentDir.path(), "placeholder.svg");
}
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
@@ -39,5 +47,18 @@ QString EffectNode::qenPath() const
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

View File

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

View File

@@ -3,6 +3,8 @@
#include "effectutils.h"
#include <coreplugin/icore.h>
#include <QJsonArray>
namespace EffectMaker {
@@ -20,5 +22,14 @@ QString EffectUtils::codeFromJsonArray(const QJsonArray &codeArray)
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

View File

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

View File

@@ -25,6 +25,8 @@ class Uniform : public QObject
Q_PROPERTY(QVariant uniformBackendValue READ backendValue NOTIFY uniformBackendValueChanged)
Q_PROPERTY(QVariant uniformMinValue MEMBER m_minValue 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:
enum class Type

View File

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

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