Merge "Merge remote-tracking branch 'origin/qds/dev'"
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
BIN
doc/qtdesignstudio/images/design-studio-kit-selection.webp
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
doc/qtdesignstudio/images/design-studio-select-kit.webp
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 348 B After Width: | Height: | Size: 1.9 KiB |
BIN
doc/qtdesignstudio/images/icons/bakelights.png
Normal file
|
After Width: | Height: | Size: 502 B |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.5 KiB |
BIN
doc/qtdesignstudio/images/icons/split-view.png
Normal file
|
After Width: | Height: | Size: 326 B |
BIN
doc/qtdesignstudio/images/studio-3d-split-view.webp
Normal file
|
After Width: | Height: | Size: 33 KiB |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -49,7 +49,7 @@ StudioControls.PopupDialog {
|
||||
root.close()
|
||||
}
|
||||
function onPopupShouldOpen() {
|
||||
root.showGlobal()
|
||||
Qt.callLater(root.showGlobal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -15,6 +15,8 @@ Item {
|
||||
|
||||
height: layout.implicitHeight
|
||||
|
||||
visible: !uniformUseCustomValue
|
||||
|
||||
Component.onCompleted: {
|
||||
if (uniformType === "int")
|
||||
valueLoader.source = "ValueInt.qml"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
HelperWidgets.Section {
|
||||
id: predefinedSection
|
||||
caption: qsTr("Predefined Categories")
|
||||
@@ -412,6 +412,7 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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}"
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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 {}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "Christen Anderson",
|
||||
"number": "+3455641"
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -26,6 +26,7 @@ QHash<int, QByteArray> EffectMakerUniformsModel::roleNames() const
|
||||
roles[MinValueRole] = "uniformMinValue";
|
||||
roles[MaxValueRole] = "uniformMaxValue";
|
||||
roles[TypeRole] = "uniformType";
|
||||
roles[UseCustomValueRole] = "uniformUseCustomValue";
|
||||
return roles;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ private:
|
||||
MaxValueRole,
|
||||
MinValueRole,
|
||||
TypeRole,
|
||||
UseCustomValueRole
|
||||
};
|
||||
|
||||
QList<Uniform *> m_uniforms;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -43,4 +43,3 @@ private:
|
||||
};
|
||||
|
||||
} // namespace EffectMaker
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ public:
|
||||
EffectUtils() = delete;
|
||||
|
||||
static QString codeFromJsonArray(const QJsonArray &codeArray);
|
||||
|
||||
static QString nodesSourcesPath();
|
||||
};
|
||||
|
||||
} // namespace EffectMaker
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||