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

Change-Id: Ic852bc9977d0292fb6cd93a319f4bfdebb22a1b0
This commit is contained in:
Tim Jenssen
2023-11-16 17:17:46 +01:00
498 changed files with 25501 additions and 7928 deletions

View File

@@ -934,6 +934,34 @@ SQLite (https://www.sqlite.org) is in the Public Domain.
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
### QrCodeGenerator
The QML Designer plugin uses QR Code Generator for Design Viewer integration.
https://github.com/alex-spataru/Qt-QrCodeGenerator
MIT License
Copyright (c) 2023 Alex Spataru
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
### cmake ### cmake
The CMake project manager uses the CMake lexer code for parsing CMake files The CMake project manager uses the CMake lexer code for parsing CMake files

View File

@@ -1,6 +1,6 @@
set(IDE_VERSION "4.3.0") # The IDE version. set(IDE_VERSION "4.4.0") # The IDE version.
set(IDE_VERSION_COMPAT "4.3.0") # The IDE Compatibility version. set(IDE_VERSION_COMPAT "4.4.0") # The IDE Compatibility version.
set(IDE_VERSION_DISPLAY "4.3.0") # The IDE display version. set(IDE_VERSION_DISPLAY "4.4.0") # The IDE display version.
set(IDE_COPYRIGHT_YEAR "2023") # The IDE current copyright year. set(IDE_COPYRIGHT_YEAR "2023") # The IDE current copyright year.
set(IDE_SETTINGSVARIANT "QtProject") # The IDE settings variation. set(IDE_SETTINGSVARIANT "QtProject") # The IDE settings variation.

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -2,7 +2,6 @@
images/commercial.png \ images/commercial.png \
images/SsFWyUeAA_4.jpg \ images/SsFWyUeAA_4.jpg \
images/9ihYeC0YJ0M.jpg \ images/9ihYeC0YJ0M.jpg \
images/RfEYO-5Mw6s.jpg \
images/yOUdg1o2KJM.jpg \ images/yOUdg1o2KJM.jpg \
images/DVWd_xMMgvg.jpg \ images/DVWd_xMMgvg.jpg \
images/Ed8WS03C-Vk.jpg \ images/Ed8WS03C-Vk.jpg \
@@ -11,6 +10,5 @@
images/w1yhDl93YI0.jpg \ images/w1yhDl93YI0.jpg \
images/pEETxSxYazg.jpg \ images/pEETxSxYazg.jpg \
images/V3Po15bNErw.jpg \ images/V3Po15bNErw.jpg \
images/bMXeeQw6BYs.jpg \ images/9MqUCP6JLCQ.jpg \
images/u3kZJjlk3CY.jpg \ images/KDxnMQzgmIY.jpg
images/9MqUCP6JLCQ.jpg

View File

@@ -897,6 +897,18 @@
\include license-mit.qdocinc \include license-mit.qdocinc
\li \b QrCodeGenerator
The QML Designer plugin uses QR Code Generator for Design Viewer integration.
\list
\li \l https://github.com/alex-spataru/Qt-QrCodeGenerator
\endlist
Distributed under the MIT license.
\include license-mit.qdocinc
\li \b cmake \li \b cmake
The CMake project manager uses the CMake lexer code for parsing CMake files. The CMake project manager uses the CMake lexer code for parsing CMake files.

View File

@@ -41,6 +41,8 @@
Besides the 3D model, the 3D scene also has the default camera and the default directional Besides the 3D model, the 3D scene also has the default camera and the default directional
light. light.
\include run-tutorial-project.qdocinc
\section1 Adding Materials to the 3D Models \section1 Adding Materials to the 3D Models
First, use materials from \uicontrol {Content Library} on the ball bearing. First, use materials from \uicontrol {Content Library} on the ball bearing.

View File

@@ -30,6 +30,8 @@
All assets you need for this tutorial are included in the Car Demo project. All assets you need for this tutorial are included in the Car Demo project.
\include run-tutorial-project.qdocinc
\section1 Creating States \section1 Creating States
First, you create the different states. In this tutorial, you create four First, you create the different states. In this tutorial, you create four
@@ -121,16 +123,19 @@
\list 1 \list 1
\li Go to the \uicontrol Connections view. \li Go to the \uicontrol Connections view.
\li In \uicontrol{Navigator}, select \e button_side and in \li In \uicontrol{Navigator}, select \e button_side and in
\uicontrol {Connections}, select \inlineimage icons/plus.png \uicontrol {Connections}, select the \inlineimage icons/plus.png
. button to open the connection setup options.
This creates a new connection with \e button_side as the target. \li Set \uicontrol Signal to \c clicked, \uicontrol Action to
\li Set \uicontrol{Signal Handler} to \uicontrol onClicked. \c {Change State}, \uicontrol {State Group} to \c rectangle and
\li Set \uicontrol Actions to \e {Change state to side}. \uicontrol State to \c side in the respective
drop-down menus.
\li Select the \inlineimage icons/close.png
button to close the connection setup options.
\li Repeat steps 2 to 4 for the next three buttons and set them to go to \li Repeat steps 2 to 4 for the next three buttons and set them to go to
their corresponding states. their corresponding states.
\endlist \endlist
\image state-transition-connections.png \image state-transition-connections.webp
Now you can preview and try the transitions to see how the UI moves between Now you can preview and try the transitions to see how the UI moves between
the states when you select the buttons. the states when you select the buttons.

View File

@@ -52,6 +52,8 @@
This tutorial requires that you know the basics of \QDS, see This tutorial requires that you know the basics of \QDS, see
\l{Getting Started}. \l{Getting Started}.
\include run-tutorial-project.qdocinc
\section1 Creating a Timeline Animation \section1 Creating a Timeline Animation
First, you create an animation where the ball bearing continuously rotates First, you create an animation where the ball bearing continuously rotates

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@@ -252,7 +252,7 @@
\image loginui1-entry-field-styled.jpg "Modified button in the 2D view" \image loginui1-entry-field-styled.jpg "Modified button in the 2D view"
\note Do not edit the the value of \uicontrol Text in the \uicontrol Character \note Do not edit the value of \uicontrol Text in the \uicontrol Character
property, because this will break the connection, and later you won't be able property, because this will break the connection, and later you won't be able
to change the text in \uicontrol {Button Content} > \uicontrol Text. to change the text in \uicontrol {Button Content} > \uicontrol Text.

View File

@@ -135,17 +135,22 @@
\uicontrol {Connections} to open the \uicontrol Connections view. \uicontrol {Connections} to open the \uicontrol Connections view.
\li Select \e createAccount in \uicontrol Navigator. \li Select \e createAccount in \uicontrol Navigator.
\li In the \uicontrol Connections tab, select the \inlineimage icons/plus.png \li In the \uicontrol Connections tab, select the \inlineimage icons/plus.png
button to add the action that the \c onClicked signal handler of button to open the connection setup options.
\e createAccount should apply. \li Set \uicontrol Signal to \c clicked, \uicontrol Action to
\li Double-click the value \uicontrol Action column and select \c {Change State}, \uicontrol {State Group} to \c rectangle and
\uicontrol {Change state to createAccount} in the drop-down menu. \uicontrol State to \c createAccount in the respective
\note Or, you can right-click the \e createAccount button in \l Navigator. drop-down menus.
Then select \uicontrol {Connections} > \uicontrol {Add signal handler} > \li Select the \inlineimage icons/close.png
\uicontrol {clicked} > \uicontrol {Change State to createAccount}. button to close the connection setup options.
\image loginui3-connections.png "Connections tab"
\image loginui3-connections.webp "Connections tab"
\li Select \uicontrol File > \uicontrol Save or press \key {Ctrl+S} \li Select \uicontrol File > \uicontrol Save or press \key {Ctrl+S}
to save your changes. to save your changes.
\note Or, you can right-click the \e createAccount button in \l Navigator.
Then select \uicontrol {Connections} > \uicontrol {Add signal handler} >
\uicontrol {clicked} > \uicontrol {Change State to createAccount}.
\endlist \endlist
In the live preview, you can now click the \uicontrol {Create Account} In the live preview, you can now click the \uicontrol {Create Account}

View File

@@ -20,14 +20,13 @@
\l{https://git.qt.io/public-demos/qtdesign-studio/-/tree/master/tutorial%20projects/multi-language%20tutorial/Loginui2}{here} \l{https://git.qt.io/public-demos/qtdesign-studio/-/tree/master/tutorial%20projects/multi-language%20tutorial/Loginui2}{here}
before you start. before you start.
Download the project and open the \e loginui2.qmlproject file in \QDS
to get started.
This project consists of a login page with a couple of text elements. This project consists of a login page with a couple of text elements.
Additionally, you will use a JSON translation file in this tutorial. Additionally, you will use a JSON translation file in this tutorial.
Download it from \l{https://git.qt.io/public-demos/qtdesign-studio/-/tree/master/tutorial%20projects/multi-language}{here}. Download it from \l{https://git.qt.io/public-demos/qtdesign-studio/-/tree/master/tutorial%20projects/multi-language}{here}.
\include run-tutorial-project.qdocinc
\section1 JSON Translation File \section1 JSON Translation File
The JSON translation file you are using in this project has the following The JSON translation file you are using in this project has the following

View File

@@ -40,10 +40,23 @@
We use the \uicontrol {\QMCU Application} project template to create We use the \uicontrol {\QMCU Application} project template to create
an application for MCUs, which support only a subset of the preset an application for MCUs, which support only a subset of the preset
\l{glossary-component}{components}. We select \uicontrol File > \l{glossary-component}{components}.
\uicontrol {New Project} > \uicontrol {\QMCU Application} >
\uicontrol Choose, and follow the instructions of the wizard to create our To create an MCU project:
project.
\list 1
\li Select \uicontrol {File} > \uicontrol {New Project}.
\li In the \uicontrol {Presets} tab, select the \uicontrol {\QMCU} preset.
\li In the \uicontrol {Details} tab:
\list
\li Select the path for the project files. You can move the project
folders later.
\li Set the screen size to match the device screen, which also enables
previewing on the desktop. You can change the screen size later in
\l {Properties}.
\endlist
\li Select \uicontrol {Create} to create the project.
\endlist
This way, only the components and properties supported on MCUs are visible This way, only the components and properties supported on MCUs are visible
in \l Components and \l Properties, and we won't accidentally in \l Components and \l Properties, and we won't accidentally
@@ -193,10 +206,12 @@
Then, we select the mouse area for the start button, \e startMA, Then, we select the mouse area for the start button, \e startMA,
in \uicontrol Navigator. On the \uicontrol Connections tab in the in \uicontrol Navigator. On the \uicontrol Connections tab in the
\l {Connections} view, we select the \inlineimage icons/plus.png \l {Connections} view, we select the \inlineimage icons/plus.png
(\uicontrol Add) button to connect the \c onClicked() signal handler (\uicontrol Add) button. We set \uicontrol Signal to \c clicked,
of the button to the \c startClicked() signal. \uicontrol Action to \c {Call Function} and \uicontrol Item to
\c startClicked. Next, we select the \inlineimage icons/close.png
button to close the connection setup options.
\image washingmachineui-connections.png "Connections view" \image washingmachineui-connections.webp "Connections view"
Then, in \e ApplicationView.qml, we specify that the \c startClicked() Then, in \e ApplicationView.qml, we specify that the \c startClicked()
signal changes the application state to \e presets: signal changes the application state to \e presets:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

View File

@@ -28,19 +28,19 @@
in ways that are not supported in \QDS by default, you can define in ways that are not supported in \QDS by default, you can define
custom properties on the \uicontrol {Properties} tab in the custom properties on the \uicontrol {Properties} tab in the
\l {Connections} view. \l {Connections} view.
\image qmldesigner-dynamicprops.png "Connections View Properties tab" \image add-updated-local-custom-property.webp "Connections View Properties tab"
For more information, see \l{Specifying Custom Properties}. For more information, see \l{Specifying Custom Properties}.
\li To enable users to interact with the component instances, connect \li To enable users to interact with the component instances, connect
the instances to signals on the \uicontrol Connections tab in the the instances to signals on the \uicontrol Connections tab in the
\uicontrol {Connections} view. For example, you can specify what \uicontrol {Connections} view. For example, you can specify what
happens when a component instance is clicked. For more information, happens when a component instance is clicked. For more information,
see \l{Connecting Components to Signals}. see \l{Connecting Components to Signals}.
\image qmldesigner-connections.png "Connections View Connections tab" \image qmldesigner-connections.webp "Connections View Connections tab"
\li To dynamically change the behavior of a component instance when \li To dynamically change the behavior of a component instance when
another component instance changes, create bindings between them on another component instance changes, create bindings between them on
the \uicontrol Bindings tab in the \uicontrol {Connections} view. the \uicontrol Bindings tab in the \uicontrol {Connections} view.
For more information, see \l{Adding Bindings Between Properties}. For more information, see \l{Adding Bindings Between Properties}.
\image qmldesigner-bindings.png "Connections view Bindings tab" \image qmldesigner-bindings.webp "Connections view Bindings tab"
\li Add states to apply sets of changes to the property values of one \li Add states to apply sets of changes to the property values of one
or several component instances in the \uicontrol States view. or several component instances in the \uicontrol States view.
For more information, see \l{Working with States}. For more information, see \l{Working with States}.

View File

@@ -72,7 +72,7 @@
the \l{UI Files}{UI files} (.ui.qml), while developers should work the \l{UI Files}{UI files} (.ui.qml), while developers should work
on the corresponding implementation files (.qml) to define their on the corresponding implementation files (.qml) to define their
programmatic behaviors or JavaScript. This enables iteration from programmatic behaviors or JavaScript. This enables iteration from
both the design and development side of the process without the the both the design and development side of the process without the
risk of overwriting each other's work. risk of overwriting each other's work.
\endlist \endlist
*/ */

View File

@@ -421,7 +421,7 @@
The child components of grid layout components are arranged according to the The child components of grid layout components are arranged according to the
\uicontrol Flow property. When the direction of a flow is set to \uicontrol Flow property. When the direction of a flow is set to
\uicontrol LeftToRight, child components are positioned next to to each \uicontrol LeftToRight, child components are positioned next to to each
other until the the number of columns specified in the other until the number of columns specified in the
\uicontrol {Columns & Rows} field is reached. Then, \uicontrol {Columns & Rows} field is reached. Then,
the auto-positioning wraps back to the beginning of the next row. the auto-positioning wraps back to the beginning of the next row.

View File

@@ -73,19 +73,6 @@
\note Using 3D components will affect the performance of your UI. Do not \note Using 3D components will affect the performance of your UI. Do not
use 3D components if the same results can be achieved using 2D components. use 3D components if the same results can be achieved using 2D components.
\section2 Videos About 3D Components
The following video shows you how to add the components included in the
\uicontrol {Qt Quick 3D} module, such as 3D models, cameras, and lights,
to your scene:
\youtube u3kZJjlk3CY
The following video shows you how to use the custom shader utilities, 3D
effects, and materials:
\youtube bMXeeQw6BYs
The following video shows you how to combine 2D and 3D components: The following video shows you how to combine 2D and 3D components:
\youtube w1yhDl93YI0 \youtube w1yhDl93YI0

View File

@@ -206,7 +206,7 @@
Text can be either in plain text or rich text format, depending on the Text can be either in plain text or rich text format, depending on the
value you set in the \uicontrol Format field. If you select value you set in the \uicontrol Format field. If you select
\uicontrol AutoText and the the first line of text contains an HTML tag, \uicontrol AutoText and the first line of text contains an HTML tag,
the text is treated as rich text. Rich text supports a subset of HTML 4 the text is treated as rich text. Rich text supports a subset of HTML 4
described on the \l {Supported HTML Subset}. Note that plain text offers described on the \l {Supported HTML Subset}. Note that plain text offers
better performance than rich text. better performance than rich text.

View File

@@ -31,76 +31,58 @@
\e CMakeLists.txt file as the project file. This enables you to share \e CMakeLists.txt file as the project file. This enables you to share
your project as a fully working C++ application with developers. your project as a fully working C++ application with developers.
If you add or remove QML files in \QDS, you have to regenerate the
\e CMakeLists.txt project configuration file by selecting \uicontrol File
> \uicontrol {Export Project} > \uicontrol {Generate CMake Build Files}.
If you use Git, you can clone an example project If you use Git, you can clone an example project
\l{https://git.qt.io/public-demos/qtdesign-studio/-/tree/master/playground/AuroraCluster0} \l{https://git.qt.io/public-demos/qtdesign-studio/-/tree/master/playground/AuroraCluster0}
{here}. {here}.
The following image shows the example project structure and contents in the \section1 Exporting a \QDS Project
\l Projects and \l {File System} views in \QDS and Qt Creator:
\image studio-project-structure.png "\QDS project in \QDS and Qt Creator views" \QDS uses a different project format than Qt Creator. \QDS does not build the project,
it uses a pre-compiled \l{QML runtime} to run the project. To export a \QDS project for the
\section1 Converting Project Structure for CMake Qt Creator, follow the process:
\QDS can generate \e CMakeLists.txt and other related files to use with
Qt Creator and to compile into an executable application but only if the
project has a certain folder structure. If you have a \QDS QML project that
doesn't have the CMake configuration, follow these steps to convert its
file structure to the correct format.
\list 1 \list 1
\li Create a folder named \e content in the project's folder. This folder contains the \li Open the project you want to export in \QDS.
application's main module. \li Select \uicontrol {File} > \uicontrol {Export Project} > \uicontrol {Generate CMake Build Files}.
\li Move all QML files of the project's main module to the \e content folder. If your project \image studio-project-export.webp "Export the \QDS project for Qt Creator"
has multiple modules, place the other modules in the \e imports or
\e asset_imports folder.
\li If your project's main module has resource folders such as \e fonts or \e {images}, move
them to the \e content folder.
\li Create a folder named \e src in the project's folder. This folder contains C++ code for
compiling the project.
\li If your project doesn't have an \e imports folder for other QML modules, create it
now even if you do not have other modules. The CMake file generator expects it.
\li In the project's \e .qmlproject file:
\list
\li Add \e "." in importPaths. For example:
\code
importPaths: [ "imports", "asset_imports", "." ]
\endcode
\li Change mainFile to \e "content/App.qml":
\code
mainFile: "content/App.qml"
\endcode
\endlist
\li In the \e content folder, create a file named \e App.qml and add the following content:
\qml \li Select \uicontrol {Details} to access the \l {Advanced Options}.
import QtQuick \image studio-project-export-advanced.webp "Access Advanced Options in the project exporter"
import QtQuick.Window
import YourImportModuleHere
Window {
width: Constants.width
height: Constants.height
visible: true
title: "YourWindowTitleHere"
<YourMainQmlClassHere> {
}
}
\endqml
\li In \e{App.qml}, modify imported modules, window dimensions, window title, and main QML \note The project exporter has default settings selected. This works better if the project
class appropriately. is combined with an existing Qt project.
\note This template assumes that your project has a module named \e YourImportModuleHere in \li Select all the options here. This allows to export the
the \a imports folder containing a singleton class named \a Constants. complete project. So, it can be compiled as a stand-alone application.
This isn't mandatory. \image studio-project-export-advanced-options.webp "Select all the options in the project exporter"
\li Generate CMake files and C++ source files that are used to compile the application into
an executable file by selecting \uicontrol File > \uicontrol {Export Project} >
\uicontrol {Generate CMake Build Files}.
\note If you copy this export on top of the existing Qt Creator project
it overwrites the existing project. Hence, the default selected options in
the exporter only exports the QML-specific items. You get a list of
warnings at the bottom part of the exporter that denotes exactly which parts
of the project gets overwritten.
\endlist \endlist
\section1 Using the Exported Project in Qt Creator
After exporting the project from the \QDS, you have to open it from Qt Creator.
If you have used any version before \QDS 4.0 to create the project, manually include this code
in the \l {CMakeLists.txt} file so the exported project works in Qt Creator.
\code
set(BUILD_QDS_COMPONENTS ON CACHE BOOL "Build design studio components")
set(CMAKE_INCLUDE_CURRENT_DIR ON)
if (${BUILD_QDS_COMPONENTS})
include(${CMAKE_CURRENT_SOURCE_DIR}/qmlcomponents)
endif ()
include(${CMAKE_CURRENT_SOURCE_DIR}/qmlmodules)
\endcode
\note If you have created the project with the \QDS version 4.0 or above, you already have this code in
\l {CMakeLists.txt} by default.
*/ */

View File

@@ -16,8 +16,14 @@
\li \QDS Version \li \QDS Version
\li \QMCU SDK Version \li \QMCU SDK Version
\row \row
\li 4.0 or later \li 4.3 or later
\li 2.4 or later \li 2.6 or later
\row
\li 4.2 or later
\li 2.5
\row
\li 4.0 up to 4.1
\li 2.4
\row \row
\li 3.8 up to 3.9 \li 3.8 up to 3.9
\li 2.3 \li 2.3

View File

@@ -25,7 +25,8 @@
\li \b - \li \b -
\li A scene in the \uicontrol 2D view is rendered by the regular Qt Quick \li A scene in the \uicontrol 2D view is rendered by the regular Qt Quick
and QML, and not as \QUL and \QMCU, so some imperfections or inaccuracies and QML, and not as \QUL and \QMCU, so some imperfections or inaccuracies
can occur. can occur. Note that the default font used in \QDS preview and \QUL are
different, and the developer must confirm both fonts are the same.
\row \row
\li \l 3D \li \l 3D
\li \b - \li \b -
@@ -71,15 +72,19 @@
\li \b - \li \b -
\li \b - \li \b -
\li The \uicontrol Connections view displays all signal handlers in the \li The \uicontrol Connections view displays all signal handlers in the
current file but it doesn't filter available signals, so you can still current file, but it doesn't filter available signals, so you can still
see and select signals that are available in Qt Quick, but not in \QUL. see and select signals available in Qt Quick, but not in \QUL.
The same also applies if \uicontrol Action is set to \uicontrol{Call Function}
and \uicontrol Item is set to \uicontrol Qt. See the component documentation
to filter available signal/function.
\row \row
\li \l {States} \li \l {States}
\li \b X \li \b X
\li \b - \li \b -
\li \b - \li \b -
\li The feature is fully supported as such, but there are some \li The feature is fully supported as such, but there are some
limitations listed in \l {\QMCU Known Issues or Limitations}. limitations such as StateGroup and the ones listed in
\l {\QMCU Known Issues or Limitations}.
\row \row
\li \l {Transitions} \li \l {Transitions}
\li \b X \li \b X
@@ -89,9 +94,11 @@
\row \row
\li \l {Translations} \li \l {Translations}
\li \b - \li \b -
\li \b -
\li \b X \li \b X
\li \b - \li \b -
\li The \uicontrol Translations view previews with regular Qt Quick instead
of \QUL, and it can be inaccurate in calculating the text overflow in some translations.
Also, the developer needs to configure the \QUL project to use \QDS translations (.ts) files.
\row \row
\li \l {Timeline} \li \l {Timeline}
\li \b X \li \b X
@@ -100,11 +107,11 @@
\li \b - \li \b -
\row \row
\li \l {Curves} \li \l {Curves}
\li \b -
\li \b X \li \b X
\li \b - \li \b -
\li Linear interpolation works, but \QMCU does not support the \li \b -
\c easing.bezierCurve property of a keyframe. \li Linear interpolation works, and \QMCU supports the \c easing.bezierCurve property
of a keyframe in \QMCU 2.6 or higher.
\row \row
\li \l Code \li \l Code
\li \b X \li \b X

View File

@@ -41,7 +41,7 @@
\image qtquick-annotation-editor.png "Annotation Editor" \image qtquick-annotation-editor.png "Annotation Editor"
\li The \uicontrol {Selected Item} field displays the ID of the \li The \uicontrol {Selected Item} field displays the ID of the
component. component.
\li In the the \uicontrol Name field, enter a free-form text that \li In the \uicontrol Name field, enter a free-form text that
describes the component. describes the component.
\li In the \uicontrol Title field, enter the text to display in \li In the \uicontrol Title field, enter the text to display in
the tab for this comment. the tab for this comment.

View File

@@ -132,7 +132,7 @@
\section2 Set the AVD as the Device in the Android Kit \section2 Set the AVD as the Device in the Android Kit
Next, you need to set the AVD as the Android device kit. You do this under the the Next, you need to set the AVD as the Android device kit. You do this under the
\uicontrol Kits tab. If the \uicontrol Kits list is empty, restart \QDS. \uicontrol Kits tab. If the \uicontrol Kits list is empty, restart \QDS.
\image qtds-options-kits.png \image qtds-options-kits.png

View File

@@ -57,7 +57,7 @@
component height is adjusted automatically. Similarly, the opacity of a component height is adjusted automatically. Similarly, the opacity of a
component can be bound to the opacity of its parent component. component can be bound to the opacity of its parent component.
\image qtquick-connection-editor-assignment.png "Binding Editor" \image qtquick-connection-editor-assignment.webp "Binding Editor"
Property bindings are created implicitly whenever a property is assigned a Property bindings are created implicitly whenever a property is assigned a
JavaScript expression. JavaScript expression.
@@ -115,7 +115,7 @@
is to create \l{glossary-binding}{bindings} between the values of their is to create \l{glossary-binding}{bindings} between the values of their
\l{glossary-property}{properties}. \l{glossary-property}{properties}.
\image qmldesigner-connections.png "The Connections view" \image qmldesigner-connections.webp "The Connections view"
Read more about connections: Read more about connections:
@@ -211,9 +211,9 @@
the application. For example, the \l {Mouse Area} component has a \c clicked the application. For example, the \l {Mouse Area} component has a \c clicked
signal that is emitted whenever the mouse is clicked within the area. Since signal that is emitted whenever the mouse is clicked within the area. Since
the signal name is \c clicked, the signal handler for receiving this signal the signal name is \c clicked, the signal handler for receiving this signal
is named \c onClicked. is named \c onClicked. Then it performs the defined \uicontrol {Action}.
\image washingmachineui-connections.png "Connections view, Connections tab" \image qtquick-component-signal.webp "Component signal"
Further, a signal is automatically emitted when the value of a Further, a signal is automatically emitted when the value of a
\l{glossary-property}{property} changes. \l{glossary-property}{property} changes.

View File

@@ -792,7 +792,7 @@
in particle size, specify values for \uicontrol {Particle scale variation} in particle size, specify values for \uicontrol {Particle scale variation}
and \uicontrol {Particle end scale variation}. and \uicontrol {Particle end scale variation}.
\uicontrol {Depth bias} specifies the the depth bias of the emitter. Depth \uicontrol {Depth bias} specifies the depth bias of the emitter. Depth
bias is added to the object's distance from camera when sorting objects. bias is added to the object's distance from camera when sorting objects.
This can be used to force the rendering order of objects that are located This can be used to force the rendering order of objects that are located
close to each other if it might otherwise change between frames. Negative close to each other if it might otherwise change between frames. Negative
@@ -1046,7 +1046,7 @@
\uicontrol {Maximum Size} defines the maximum size that the affector can \uicontrol {Maximum Size} defines the maximum size that the affector can
scale particles to. scale particles to.
\uicontrol Duration defines the the duration of the scaling cycle in \uicontrol Duration defines the duration of the scaling cycle in
milliseconds. milliseconds.
\uicontrol {Easing Curve} defines the \uicontrol {Easing Curve} defines the

View File

@@ -285,7 +285,7 @@
\uicontrol {Input 01} field to the value of the \uicontrol {Below min} \uicontrol {Input 01} field to the value of the \uicontrol {Below min}
field of the minimum-maximum mapper for the bad value range. For the field of the minimum-maximum mapper for the bad value range. For the
\e overValueAnd operator, we bind it to the value of the \e overValueAnd operator, we bind it to the value of the
\uicontrol {Above max} field of the the same mapper. \uicontrol {Above max} field of the same mapper.
\image studio-logic-helper-combining-example-ao2.png "Under value minimum-maximum mapper Input 01" \image studio-logic-helper-combining-example-ao2.png "Under value minimum-maximum mapper Input 01"

View File

@@ -0,0 +1,4 @@
\section1 Running the Tutorial Project
To open the tutorial project in \QDS, open the \e{.qmlproject} file located
in the root folder of the downloaded project.

View File

@@ -112,6 +112,13 @@
\li Open the \uicontrol {Manual Code Edit} window from the \li Open the \uicontrol {Manual Code Edit} window from the
\uicontrol {Connections} view and write JavaScript expressions with components \uicontrol {Connections} view and write JavaScript expressions with components
and logical expressions manually. and logical expressions manually.
\note If you create a conditional expression by selecting options from the
drop-down menus in the \uicontrol {Connection} view, you can only create a single
level \e {if-else} expression. For nested level \e {if-else} expressions,
use the \uicontrol {Manual Code Edit}.
\image qmldesigner-connections-ConditionalAction-Manual.webp
\endlist \endlist
\section2 Action Properties \section2 Action Properties
@@ -152,11 +159,7 @@
\li N/A \li N/A
\endtable \endtable
\note If you create a conditional expression by selecting options from drop-down menus in Watch this video for practical examples of the \uicontrol {Connection} view workflow:
the \uicontrol {Connection} view, you can only create a single \youtube KDxnMQzgmIY
level {if-else} expression. For nested level \e {if-elseif-else} expressions,
you have to use the \uicontrol {Manual Code Edit}.
\image qmldesigner-connections-ConditionalAction-Manual.webp
*/ */

View File

@@ -25,10 +25,6 @@
You can move the views anywhere on the screen and save them as You can move the views anywhere on the screen and save them as
\e workspaces, as instructed in \l {Managing Workspaces}. \e workspaces, as instructed in \l {Managing Workspaces}.
To learn more about using the design views, see the following video:
\youtube RfEYO-5Mw6s
\section1 Summary of Design Views \section1 Summary of Design Views
In addition to the summary of design views, the table below includes an MCU In addition to the summary of design views, the table below includes an MCU

View File

@@ -45,7 +45,7 @@
\li \l{Previewing Component Size} \li \l{Previewing Component Size}
\row \row
\li \inlineimage icons/canvas-color.png \li \inlineimage icons/canvas-color.png
\li Sets the color of the the \uicontrol {2D} view working area. \li Sets the color of the \uicontrol {2D} view working area.
\li \l{Setting Canvas Color} \li \l{Setting Canvas Color}
\row \row
\li \inlineimage icons/zoomIn.png \li \inlineimage icons/zoomIn.png

View File

@@ -32,12 +32,7 @@ TreeViewDelegate {
readonly property int __dirItemHeight: 21 readonly property int __dirItemHeight: 21
implicitHeight: root.__isDirectory ? root.__dirItemHeight : root.__fileItemHeight implicitHeight: root.__isDirectory ? root.__dirItemHeight : root.__fileItemHeight
implicitWidth: { implicitWidth: root.assetsView.width
if (root.assetsView.verticalScrollBar.scrollBarVisible)
return root.assetsView.width - root.indentation - root.assetsView.verticalScrollBar.width
else
return root.assetsView.width - root.indentation
}
leftMargin: root.__isDirectory ? 0 : thumbnailImage.width leftMargin: root.__isDirectory ? 0 : thumbnailImage.width
@@ -88,7 +83,7 @@ TreeViewDelegate {
background: Rectangle { background: Rectangle {
id: bg id: bg
x: root.indentation * root.depth x: root.indentation * (root.depth - 1)
width: root.implicitWidth - bg.x width: root.implicitWidth - bg.x
color: { color: {

View File

@@ -0,0 +1,224 @@
// 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 CollectionDetails 1.0 as CollectionDetails
import HelperWidgets 2.0 as HelperWidgets
import StudioControls 1.0 as StudioControls
import StudioHelpers as StudioHelpers
import StudioTheme 1.0 as StudioTheme
import QtQuick.Templates as T
Item {
id: root
required property var columnType
property var __modifier : textEditor
property bool __changesAccepted: true
TableView.onCommit: {
if (root.__changesAccepted)
edit = __modifier.editor.editValue
}
Component.onCompleted: {
__changesAccepted = true
if (edit && edit !== "")
root.__modifier.editor.editValue = edit
}
onActiveFocusChanged: {
if (root.activeFocus)
root.__modifier.editor.forceActiveFocus()
}
Connections {
id: modifierFocusConnection
target: root.__modifier.editor
function onActiveFocusChanged() {
if (!modifierFocusConnection.target.activeFocus)
root.TableView.commit()
}
}
EditorPopup {
id: textEditor
editor: textField
StudioControls.TextField {
id: textField
property alias editValue: textField.text
actionIndicator.visible: false
translationIndicatorVisible: false
onRejected: root.__changesAccepted = false
}
}
EditorPopup {
id: numberEditor
editor: numberField
StudioControls.RealSpinBox {
id: numberField
property alias editValue: numberField.realValue
actionIndicator.visible: false
realFrom: -9e9
realTo: 9e9
realStepSize: 1.0
decimals: 6
}
}
EditorPopup {
id: boolEditor
editor: boolField
StudioControls.CheckBox {
id: boolField
property alias editValue: boolField.checked
actionIndicatorVisible: false
}
}
EditorPopup {
id: colorEditor
editor: colorPicker
implicitHeight: colorPicker.height + topPadding + bottomPadding
implicitWidth: colorPicker.width + leftPadding + rightPadding
padding: 8
StudioHelpers.ColorBackend {
id: colorBackend
}
StudioControls.ColorEditorPopup {
id: colorPicker
property alias editValue: colorBackend.color
color: colorBackend.color
width: 200
Keys.onEnterPressed: colorPicker.focus = false
onActivateColor: function(color) {
colorBackend.activateColor(color)
}
}
background: Rectangle {
color: StudioTheme.Values.themeControlBackgroundInteraction
border.color: StudioTheme.Values.themeInteraction
border.width: StudioTheme.Values.border
}
}
component EditorPopup: T.Popup {
id: editorPopup
required property Item editor
implicitHeight: contentHeight
implicitWidth: contentWidth
enabled: visible
visible: false
Connections {
target: editorPopup.editor
function onActiveFocusChanged() {
if (!editorPopup.editor.activeFocus)
editorPopup.close()
}
}
Connections {
target: editorPopup.editor.Keys
function onEscapePressed() {
root.__changesAccepted = false
editorPopup.close()
}
}
}
states: [
State {
name: "default"
when: columnType !== CollectionDetails.DataType.Boolean
&& columnType !== CollectionDetails.DataType.Color
&& columnType !== CollectionDetails.DataType.Number
PropertyChanges {
target: root
__modifier: textEditor
}
PropertyChanges {
target: textEditor
visible: true
focus: true
}
},
State {
name: "number"
when: columnType === CollectionDetails.DataType.Number
PropertyChanges {
target: root
__modifier: numberEditor
}
PropertyChanges {
target: numberEditor
visible: true
focus: true
}
},
State {
name: "bool"
when: columnType === CollectionDetails.DataType.Boolean
PropertyChanges {
target: root
__modifier: boolEditor
}
PropertyChanges {
target: boolEditor
visible: true
focus: true
}
},
State {
name: "color"
when: columnType === CollectionDetails.DataType.Color
PropertyChanges {
target: root
__modifier: colorEditor
}
PropertyChanges {
target: colorEditor
visible: true
focus: true
}
}
]
}

View File

@@ -0,0 +1,286 @@
// 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 Qt.labs.platform as PlatformWidgets
import HelperWidgets 2.0 as HelperWidgets
import StudioControls 1.0 as StudioControls
import StudioTheme 1.0 as StudioTheme
import CollectionEditorBackend
Item {
id: root
property real iconHeight: 2 * StudioTheme.Values.bigFont
required property var model
required property var backend
property int selectedRow: -1
implicitHeight: container.height
function addNewColumn() {
addColumnDialog.popUp(root.model.columnCount())
}
function addNewRow() {
root.model.insertRow(root.model.rowCount())
}
RowLayout {
id: container
width: parent.width
spacing: StudioTheme.Values.sectionRowSpacing
RowLayout {
id: leftSideToolbar
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
spacing: StudioTheme.Values.sectionRowSpacing
IconButton {
icon: StudioTheme.Constants.addcolumnleft_medium
tooltip: qsTr("Add property left %1").arg(leftSideToolbar.topPadding)
enabled: root.model.selectedColumn > -1
onClicked: addColumnDialog.popUp(root.model.selectedColumn - 1)
}
IconButton {
icon: StudioTheme.Constants.addcolumnright_medium
tooltip: qsTr("Add property right")
enabled: root.model.selectedColumn > -1
onClicked: addColumnDialog.popUp(root.model.selectedColumn + 1)
}
IconButton {
icon: StudioTheme.Constants.deletecolumn_medium
tooltip: qsTr("Delete selected property")
enabled: root.model.selectedColumn > -1
onClicked: root.model.removeColumn(root.model.selectedColumn)
}
Item { // spacer
implicitWidth: StudioTheme.Values.toolbarSpacing
implicitHeight: 1
}
IconButton {
icon: StudioTheme.Constants.addrowbelow_medium
tooltip: qsTr("Insert row below")
enabled: root.model.selectedRow > -1
onClicked: root.model.insertRow(root.model.selectedRow + 1)
}
IconButton {
icon: StudioTheme.Constants.addrowabove_medium
tooltip: qsTr("Insert row above")
enabled: root.model.selectedRow > -1
onClicked: root.model.insertRow(root.model.selectedRow)
}
IconButton {
icon: StudioTheme.Constants.deleterow_medium
tooltip: qsTr("Delete selected row")
enabled: root.model.selectedRow > -1
onClicked: root.model.removeRow(root.model.selectedRow)
}
}
Item { // spacer
Layout.minimumHeight: 1
Layout.fillWidth: true
}
RowLayout {
id: rightSideToolbar
spacing: StudioTheme.Values.sectionRowSpacing
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
IconButton {
icon: StudioTheme.Constants.updateContent_medium
tooltip: qsTr("Update existing file with 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")
}
}
IconButton {
icon: StudioTheme.Constants.export_medium
tooltip: qsTr("Export the model to a new file")
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()
}
}
}
}
}
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")
fileDialog.reject()
}
}
component IconButton: HelperWidgets.IconButton {
Layout.preferredHeight: root.iconHeight
Layout.preferredWidth: root.iconHeight
radius: StudioTheme.Values.smallRadius
iconSize: StudioTheme.Values.bigFont
}
component Spacer: Item {
implicitWidth: 1
implicitHeight: StudioTheme.Values.columnGap
}
RegularExpressionValidator {
id: nameValidator
regularExpression: /^\w+$/
}
StudioControls.Dialog {
id: addColumnDialog
property int clickedIndex: -1
property bool nameIsValid
title: qsTr("Add Column")
function popUp(index)
{
addColumnDialog.clickedIndex = index
columnName.text = ""
addedPropertyType.currentIndex = addedPropertyType.find("String")
addColumnDialog.open()
}
function addColumnName() {
if (addColumnDialog.nameIsValid) {
root.model.addColumn(addColumnDialog.clickedIndex, columnName.text, addedPropertyType.currentText)
addColumnDialog.accept()
} else {
addColumnDialog.reject()
}
}
contentItem: ColumnLayout {
spacing: 2
Text {
text: qsTr("Column name:")
color: StudioTheme.Values.themeTextColor
}
StudioControls.TextField {
id: columnName
Layout.fillWidth: true
actionIndicator.visible: false
translationIndicator.visible: false
validator: nameValidator
Keys.onEnterPressed: addColumnDialog.addColumnName()
Keys.onReturnPressed: addColumnDialog.addColumnName()
Keys.onEscapePressed: addColumnDialog.reject()
onTextChanged: {
addColumnDialog.nameIsValid = (columnName.text !== ""
&& !root.model.isPropertyAvailable(columnName.text))
}
}
Spacer { implicitHeight: StudioTheme.Values.controlLabelGap }
Label {
Layout.fillWidth: true
text: qsTr("The model already contains \"%1\"!").arg(columnName.text)
visible: columnName.text !== "" && !addColumnDialog.nameIsValid
color: StudioTheme.Values.themeTextColor
wrapMode: Label.WordWrap
padding: 5
background: Rectangle {
color: "transparent"
border.width: StudioTheme.Values.border
border.color: StudioTheme.Values.themeWarning
}
}
Spacer {}
Text {
text: qsTr("Type:")
color: StudioTheme.Values.themeTextColor
}
StudioControls.ComboBox {
id: addedPropertyType
Layout.fillWidth: true
model: root.model.typesList()
actionIndicatorVisible: false
}
Spacer {}
RowLayout {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
spacing: StudioTheme.Values.sectionRowSpacing
HelperWidgets.Button {
enabled: addColumnDialog.nameIsValid
text: qsTr("Add")
onClicked: addColumnDialog.addColumnName()
}
HelperWidgets.Button {
text: qsTr("Cancel")
onClicked: addColumnDialog.reject()
}
}
}
}
}

View File

@@ -0,0 +1,434 @@
// 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 HelperWidgets 2.0 as HelperWidgets
import StudioTheme 1.0 as StudioTheme
import StudioControls 1.0 as StudioControls
Rectangle {
id: root
required property var model
required property var backend
required property var sortedModel
implicitWidth: 300
implicitHeight: 400
color: StudioTheme.Values.themeControlBackground
ColumnLayout {
id: topRow
visible: collectionNameText.text !== ""
spacing: 0
anchors {
fill: parent
topMargin: 10
leftMargin: 15
rightMargin: 15
bottomMargin: 10
}
Text {
id: collectionNameText
leftPadding: 8
rightPadding: 8
topPadding: 3
bottomPadding: 3
color: StudioTheme.Values.themeTextColor
text: root.model.collectionName
font.pixelSize: StudioTheme.Values.baseFontSize
elide: Text.ElideRight
}
Item { // spacer
implicitWidth: 1
implicitHeight: 10
}
CollectionDetailsToolbar {
id: toolbar
model: root.model
backend: root.backend
Layout.fillWidth: true
Layout.minimumWidth: implicitWidth
}
Item { // spacer
implicitWidth: 1
implicitHeight: 5
}
GridLayout {
columns: 3
rowSpacing: 1
columnSpacing: 1
Layout.fillWidth: true
Layout.fillHeight: true
Layout.maximumWidth: parent.width
Rectangle {
clip: true
visible: !tableView.model.isEmpty
color: StudioTheme.Values.themeControlBackgroundInteraction
border.color: StudioTheme.Values.themeControlBackgroundInteraction
border.width: 2
Layout.preferredWidth: rowIdView.width
Layout.preferredHeight: headerView.height
Layout.minimumWidth: rowIdView.width
Layout.minimumHeight: headerView.height
Text {
anchors.fill: parent
font: headerTextMetrics.font
text: "#"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: StudioTheme.Values.themeTextColor
}
}
HorizontalHeaderView {
id: headerView
property real topPadding: 5
property real bottomPadding: 5
Layout.preferredHeight: headerTextMetrics.height + topPadding + bottomPadding
Layout.columnSpan: 2
syncView: tableView
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
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: (mouse) => {
tableView.model.selectColumn(index)
if (mouse.button === Qt.RightButton)
headerMenu.popIndex(index, horizontalHeaderItem.getGlobalBottomLeft())
}
}
}
StudioControls.Menu {
id: headerMenu
property int clickedHeader: -1
property point initialPosition
function popIndex(clickedIndex, clickedRect)
{
headerMenu.clickedHeader = clickedIndex
headerMenu.initialPosition = clickedRect
headerMenu.popup()
}
onClosed: {
headerMenu.clickedHeader = -1
}
StudioControls.MenuItem {
text: qsTr("Edit")
onTriggered: editProperyDialog.editProperty(headerMenu.clickedHeader, headerMenu.initialPosition)
}
StudioControls.MenuItem {
text: qsTr("Delete")
onTriggered: deleteColumnDialog.popUp(headerMenu.clickedHeader)
}
StudioControls.MenuItem {
text: qsTr("Sort Ascending")
onTriggered: sortedModel.sort(headerMenu.clickedHeader, Qt.AscendingOrder)
}
StudioControls.MenuItem {
text: qsTr("Sort Descending")
onTriggered: sortedModel.sort(headerMenu.clickedHeader, Qt.DescendingOrder)
}
}
}
VerticalHeaderView {
id: rowIdView
syncView: tableView
clip: true
Layout.preferredHeight: tableView.height
Layout.rowSpan: 2
Layout.alignment: Qt.AlignTop + Qt.AlignLeft
delegate: HeaderDelegate {
selectedItem: tableView.model.selectedRow
color: StudioTheme.Values.themeControlBackgroundHover
MouseArea {
anchors.fill: parent
anchors.margins: 5
acceptedButtons: Qt.LeftButton
onClicked: tableView.model.selectRow(index)
}
}
}
TableView {
id: tableView
model: root.sortedModel
clip: true
Layout.preferredWidth: tableView.contentWidth
Layout.preferredHeight: tableView.contentHeight
Layout.minimumWidth: 100
Layout.minimumHeight: 20
Layout.maximumWidth: root.width
delegate: Rectangle {
id: itemCell
implicitWidth: 100
implicitHeight: itemText.height
border.width: 1
Text {
id: itemText
text: display ? display : ""
width: parent.width
leftPadding: 5
topPadding: 3
bottomPadding: 3
font.pixelSize: StudioTheme.Values.baseFontSize
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
TableView.editDelegate: CollectionDetailsEditDelegate {
anchors {
top: itemText.top
left: itemText.left
}
}
states: [
State {
name: "default"
when: !itemSelected
PropertyChanges {
target: itemCell
color: StudioTheme.Values.themeControlBackground
border.color: StudioTheme.Values.themeControlBackgroundInteraction
}
PropertyChanges {
target: itemText
color: StudioTheme.Values.themePlaceholderTextColorInteraction
}
},
State {
name: "selected"
when: itemSelected
PropertyChanges {
target: itemCell
color: StudioTheme.Values.themeControlBackgroundInteraction
border.color: StudioTheme.Values.themeControlBackground
}
PropertyChanges {
target: itemText
color: StudioTheme.Values.themeInteraction
}
}
]
}
}
HelperWidgets.IconButton {
id: addColumnContainer
iconSize:16
Layout.preferredWidth: 24
Layout.preferredHeight: tableView.height
Layout.minimumHeight: 24
Layout.alignment: Qt.AlignLeft + Qt.AlignVCenter
icon: StudioTheme.Constants.create_medium
tooltip: "Add Column"
onClicked: toolbar.addNewColumn()
}
HelperWidgets.IconButton {
id: addRowContainer
iconSize:16
Layout.preferredWidth: tableView.width
Layout.preferredHeight: 24
Layout.minimumWidth: 24
Layout.alignment: Qt.AlignTop + Qt.AlignHCenter
icon: StudioTheme.Constants.create_medium
tooltip: "Add Row"
onClicked: toolbar.addNewRow()
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
}
}
Text {
anchors.fill: 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 {
id: headerTextMetrics
font.pixelSize: StudioTheme.Values.baseFontSize
text: "Xq"
}
component HeaderDelegate: Rectangle {
id: headerItem
required property int selectedItem
property alias horizontalAlignment: headerText.horizontalAlignment
property alias verticalAlignment: headerText.verticalAlignment
implicitWidth: headerText.implicitWidth
implicitHeight: headerText.implicitHeight
border.width: 1
clip: true
Text {
id: headerText
topPadding: headerView.topPadding
bottomPadding: headerView.bottomPadding
leftPadding: 5
rightPadding: 5
text: display
font: headerTextMetrics.font
color: StudioTheme.Values.themeTextColor
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
anchors.fill: parent
elide: Text.ElideRight
}
states: [
State {
name: "default"
when: index !== selectedItem
PropertyChanges {
target: headerItem
border.color: StudioTheme.Values.themeControlBackgroundInteraction
}
PropertyChanges {
target: headerText
font.bold: false
}
},
State {
name: "selected"
when: index === selectedItem
PropertyChanges {
target: headerItem
border.color: StudioTheme.Values.themeControlBackground
}
PropertyChanges {
target: headerText
font.bold: true
}
}
]
}
EditPropertyDialog {
id: editProperyDialog
model: root.model
}
StudioControls.Dialog {
id: deleteColumnDialog
property int clickedIndex: -1
title: qsTr("Delete Column")
width: 400
onAccepted: {
root.model.removeColumn(clickedIndex)
}
function popUp(index)
{
deleteColumnDialog.clickedIndex = index
deleteColumnDialog.open()
}
contentItem: ColumnLayout {
spacing: StudioTheme.Values.sectionColumnSpacing
Text {
text: qsTr("Are you sure that you want to delete column \"%1\"?").arg(
root.model.headerData(
deleteColumnDialog.clickedIndex, Qt.Horizontal))
color: StudioTheme.Values.themeTextColor
}
RowLayout {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
spacing: StudioTheme.Values.sectionRowSpacing
HelperWidgets.Button {
text: qsTr("Delete")
onClicked: deleteColumnDialog.accept()
}
HelperWidgets.Button {
text: qsTr("Cancel")
onClicked: deleteColumnDialog.reject()
}
}
}
}
}

View File

@@ -3,7 +3,7 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Qt.labs.platform as PlatformWidgets import QtQuick.Layouts
import HelperWidgets 2.0 as HelperWidgets import HelperWidgets 2.0 as HelperWidgets
import StudioControls 1.0 as StudioControls import StudioControls 1.0 as StudioControls
import StudioTheme as StudioTheme import StudioTheme as StudioTheme
@@ -12,9 +12,10 @@ Item {
id: root id: root
implicitWidth: 300 implicitWidth: 300
implicitHeight: innerRect.height + 6 implicitHeight: innerRect.height + 3
property color textColor property color textColor
property string sourceType
signal selectItem(int itemIndex) signal selectItem(int itemIndex)
signal deleteItem() signal deleteItem()
@@ -23,7 +24,7 @@ Item {
id: boundingRect id: boundingRect
anchors.centerIn: root anchors.centerIn: root
width: root.width - 24 width: parent.width
height: nameHolder.height height: nameHolder.height
clip: true clip: true
@@ -47,21 +48,23 @@ Item {
anchors.fill: parent anchors.fill: parent
} }
Row { RowLayout {
width: parent.width - threeDots.width width: parent.width
leftPadding: 20
Text { Text {
id: moveTool id: moveTool
property StudioTheme.ControlStyle style: StudioTheme.Values.viewBarButtonStyle property StudioTheme.ControlStyle style: StudioTheme.Values.viewBarButtonStyle
width: moveTool.style.squareControlSize.width Layout.preferredWidth: moveTool.style.squareControlSize.width
height: nameHolder.height Layout.preferredHeight: nameHolder.height
Layout.leftMargin: 12
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
text: StudioTheme.Constants.dragmarks text: StudioTheme.Constants.dragmarks
font.family: StudioTheme.Constants.iconFont.family font.family: StudioTheme.Constants.iconFont.family
font.pixelSize: moveTool.style.baseIconFontSize font.pixelSize: moveTool.style.baseIconFontSize
color: root.textColor
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
@@ -69,9 +72,12 @@ Item {
Text { Text {
id: nameHolder id: nameHolder
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
text: collectionName text: collectionName
font.pixelSize: StudioTheme.Values.baseFontSize font.pixelSize: StudioTheme.Values.baseFontSize
color: textColor color: root.textColor
leftPadding: 5 leftPadding: 5
topPadding: 8 topPadding: 8
rightPadding: 8 rightPadding: 8
@@ -79,82 +85,92 @@ Item {
elide: Text.ElideMiddle elide: Text.ElideMiddle
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
}
Text { Text {
id: threeDots id: threeDots
text: StudioTheme.Constants.more_medium Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
font.family: StudioTheme.Constants.iconFont.family text: StudioTheme.Constants.more_medium
font.pixelSize: StudioTheme.Values.baseIconFontSize font.family: StudioTheme.Constants.iconFont.family
color: textColor font.pixelSize: StudioTheme.Values.baseIconFontSize
anchors.right: boundingRect.right color: root.textColor
anchors.verticalCenter: parent.verticalCenter rightPadding: 12
rightPadding: 12 topPadding: nameHolder.topPadding
topPadding: nameHolder.topPadding bottomPadding: nameHolder.bottomPadding
bottomPadding: nameHolder.bottomPadding horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.RightButton + Qt.LeftButton acceptedButtons: Qt.RightButton | Qt.LeftButton
onClicked: (event) => { onClicked: collectionMenu.popup()
collectionMenu.open()
event.accepted = true
} }
} }
} }
} }
PlatformWidgets.Menu { StudioControls.Menu {
id: collectionMenu id: collectionMenu
PlatformWidgets.MenuItem { closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
StudioControls.MenuItem {
text: qsTr("Delete") text: qsTr("Delete")
shortcut: StandardKey.Delete shortcut: StandardKey.Delete
onTriggered: deleteDialog.open() onTriggered: deleteDialog.open()
} }
PlatformWidgets.MenuItem { StudioControls.MenuItem {
text: qsTr("Rename") text: qsTr("Rename")
shortcut: StandardKey.Replace shortcut: StandardKey.Replace
onTriggered: renameDialog.open() onTriggered: renameDialog.open()
} }
} }
component Spacer: Item {
implicitWidth: 1
implicitHeight: StudioTheme.Values.columnGap
}
StudioControls.Dialog { StudioControls.Dialog {
id: deleteDialog id: deleteDialog
title: qsTr("Deleting whole collection") title: qsTr("Deleting the model")
clip: true
contentItem: Column { contentItem: ColumnLayout {
spacing: 2 spacing: 2
Text { Text {
text: qsTr("Are you sure that you want to delete collection \"" + collectionName + "\"?") Layout.fillWidth: true
wrapMode: Text.WordWrap
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
text: {
if (root.sourceType === "json") {
qsTr("Are you sure that you want to delete model \"%1\"?"
+ "\nThe model will be deleted permanently.").arg(collectionName)
} else if (root.sourceType === "csv") {
qsTr("Are you sure that you want to delete model \"%1\"?"
+ "\nThe model will be removed from the project "
+ "but the file will not be deleted.").arg(collectionName)
}
}
} }
Item { // spacer Spacer {}
width: 1
height: 20
}
Row { RowLayout {
anchors.right: parent.right spacing: StudioTheme.Values.sectionRowSpacing
spacing: 10 Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
HelperWidgets.Button { HelperWidgets.Button {
id: btnDelete
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Delete") text: qsTr("Delete")
onClicked: root.deleteItem(index) onClicked: root.deleteItem()
} }
HelperWidgets.Button { HelperWidgets.Button {
text: qsTr("Cancel") text: qsTr("Cancel")
anchors.verticalCenter: parent.verticalCenter
onClicked: deleteDialog.reject() onClicked: deleteDialog.reject()
} }
} }
@@ -164,7 +180,7 @@ Item {
StudioControls.Dialog { StudioControls.Dialog {
id: renameDialog id: renameDialog
title: qsTr("Rename collection") title: qsTr("Rename model")
onAccepted: { onAccepted: {
if (newNameField.text !== "") if (newNameField.text !== "")
@@ -175,7 +191,7 @@ Item {
newNameField.text = collectionName newNameField.text = collectionName
} }
contentItem: Column { contentItem: ColumnLayout {
spacing: 2 spacing: 2
Text { Text {
@@ -183,60 +199,57 @@ Item {
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
} }
Row { Spacer {}
spacing: 10
Text {
text: qsTr("New name:")
color: StudioTheme.Values.themeTextColor
}
StudioControls.TextField { Text {
id: newNameField Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
text: qsTr("New name:")
color: StudioTheme.Values.themeTextColor
}
anchors.verticalCenter: parent.verticalCenter StudioControls.TextField {
actionIndicator.visible: false id: newNameField
translationIndicator.visible: false
validator: newNameValidator
Keys.onEnterPressed: renameDialog.accept() Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Keys.onReturnPressed: renameDialog.accept() Layout.fillWidth: true
Keys.onEscapePressed: renameDialog.reject()
onTextChanged: { actionIndicator.visible: false
btnRename.enabled = newNameField.text !== "" translationIndicator.visible: false
} validator: newNameValidator
Keys.onEnterPressed: renameDialog.accept()
Keys.onReturnPressed: renameDialog.accept()
Keys.onEscapePressed: renameDialog.reject()
onTextChanged: {
btnRename.enabled = newNameField.text !== ""
} }
} }
Item { // spacer Spacer {}
width: 1
height: 20
}
Row { RowLayout {
anchors.right: parent.right Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
spacing: 10 spacing: StudioTheme.Values.sectionRowSpacing
HelperWidgets.Button { HelperWidgets.Button {
id: btnRename id: btnRename
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Rename") text: qsTr("Rename")
onClicked: renameDialog.accept() onClicked: renameDialog.accept()
} }
HelperWidgets.Button { HelperWidgets.Button {
text: qsTr("Cancel") text: qsTr("Cancel")
anchors.verticalCenter: parent.verticalCenter
onClicked: renameDialog.reject() onClicked: renameDialog.reject()
} }
} }
} }
} }
HelperWidgets.RegExpValidator { RegularExpressionValidator {
id: newNameValidator id: newNameValidator
regExp: /^\w+$/ regularExpression: /^\w+$/
} }
states: [ states: [
@@ -277,12 +290,12 @@ Item {
PropertyChanges { PropertyChanges {
target: innerRect target: innerRect
opacity: 1 opacity: 1
color: StudioTheme.Values.themeControlBackgroundInteraction color: StudioTheme.Values.themeIconColorSelected
} }
PropertyChanges { PropertyChanges {
target: root target: root
textColor: StudioTheme.Values.themeIconColorSelected textColor: StudioTheme.Values.themeTextSelectedTextColor
} }
} }
] ]

View File

@@ -3,6 +3,7 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts
import QtQuickDesignerTheme 1.0 import QtQuickDesignerTheme 1.0
import HelperWidgets 2.0 as HelperWidgets import HelperWidgets 2.0 as HelperWidgets
import StudioTheme 1.0 as StudioTheme import StudioTheme 1.0 as StudioTheme
@@ -14,7 +15,8 @@ Item {
property var rootView: CollectionEditorBackend.rootView property var rootView: CollectionEditorBackend.rootView
property var model: CollectionEditorBackend.model property var model: CollectionEditorBackend.model
property var singleCollectionModel: CollectionEditorBackend.singleCollectionModel property var collectionDetailsModel: CollectionEditorBackend.collectionDetailsModel
property var collectionDetailsSortFilterModel: CollectionEditorBackend.collectionDetailsSortFilterModel
function showWarning(title, message) { function showWarning(title, message) {
warningDialog.title = title warningDialog.title = title
@@ -40,6 +42,7 @@ Item {
id: newCollection id: newCollection
backendValue: root.rootView backendValue: root.rootView
sourceModel: root.model
anchors.centerIn: parent anchors.centerIn: parent
} }
@@ -50,57 +53,63 @@ Item {
message: "" message: ""
} }
Rectangle { GridLayout {
id: collectionsRect id: grid
readonly property bool isHorizontal: width >= 500
color: StudioTheme.Values.themeToolbarBackground anchors.fill: parent
width: 300 columns: isHorizontal ? 3 : 1
height: root.height
Column { ColumnLayout {
width: parent.width id: collectionsSideBar
Rectangle { Layout.alignment: Qt.AlignTop | Qt.AlignLeft
width: parent.width Layout.minimumWidth: 300
height: StudioTheme.Values.height + 5 Layout.fillWidth: !grid.isHorizontal
color: StudioTheme.Values.themeToolbarBackground
RowLayout {
spacing: StudioTheme.Values.sectionRowSpacing
Layout.fillWidth: true
Layout.preferredHeight: 50
Text { Text {
id: collectionText Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillWidth: true
anchors.verticalCenter: parent.verticalCenter text: qsTr("Data Models")
text: qsTr("Collections") font.pixelSize: StudioTheme.Values.baseFontSize
font.pixelSize: StudioTheme.Values.mediumIconFont
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
leftPadding: 15 leftPadding: 15
} }
Row { IconTextButton {
anchors.right: parent.right Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
rightPadding: 12
spacing: 2
HelperWidgets.IconButton { icon: StudioTheme.Constants.import_medium
icon: StudioTheme.Constants.downloadjson_large text: qsTr("JSON")
tooltip: qsTr("Import Json") tooltip: qsTr("Import JSON")
radius: StudioTheme.Values.smallRadius
onClicked: jsonImporter.open() onClicked: jsonImporter.open()
} }
HelperWidgets.IconButton { IconTextButton {
icon: StudioTheme.Constants.downloadcsv_large Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
tooltip: qsTr("Import CSV")
onClicked: csvImporter.open() icon: StudioTheme.Constants.import_medium
} text: qsTr("CSV")
tooltip: qsTr("Import CSV")
radius: StudioTheme.Values.smallRadius
onClicked: csvImporter.open()
} }
} }
Rectangle { // Collections Rectangle { // Model Groups
width: parent.width Layout.fillWidth: true
color: StudioTheme.Values.themeBackgroundColorNormal color: StudioTheme.Values.themeBackgroundColorNormal
height: 330 Layout.minimumHeight: 150
Layout.preferredHeight: sourceListView.contentHeight
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
@@ -114,41 +123,46 @@ Item {
ListView { ListView {
id: sourceListView id: sourceListView
width: parent.width anchors.fill: parent
height: contentHeight
model: root.model model: root.model
delegate: ModelSourceItem { delegate: ModelSourceItem {
implicitWidth: sourceListView.width
onDeleteItem: root.model.removeRow(index) onDeleteItem: root.model.removeRow(index)
hasSelectedTarget: root.rootView.targetNodeSelected
onAssignToSelected: root.rootView.assignSourceNodeToSelectedItem(sourceNode)
} }
} }
} }
Rectangle { HelperWidgets.IconButton {
width: parent.width id: addCollectionButton
height: addCollectionButton.height
color: StudioTheme.Values.themeBackgroundColorNormal
IconTextButton { iconSize:16
id: addCollectionButton Layout.fillWidth: true
Layout.minimumWidth: 24
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
anchors.centerIn: parent tooltip: qsTr("Add a new model")
text: qsTr("Add new collection") icon: StudioTheme.Constants.create_medium
icon: StudioTheme.Constants.create_medium onClicked: newCollection.open()
onClicked: newCollection.open()
}
} }
} }
}
SingleCollectionView { Rectangle { // Splitter
model: root.singleCollectionModel Layout.fillWidth: !grid.isHorizontal
anchors { Layout.fillHeight: grid.isHorizontal
left: collectionsRect.right Layout.minimumWidth: 2
right: parent.right Layout.minimumHeight: 2
top: parent.top color: "black"
bottom: parent.bottom }
CollectionDetailsView {
model: root.collectionDetailsModel
backend: root.model
sortedModel: root.collectionDetailsSortFilterModel
Layout.fillHeight: true
Layout.fillWidth: true
} }
} }
} }

View File

@@ -3,6 +3,7 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts
import QtQuickDesignerTheme 1.0 import QtQuickDesignerTheme 1.0
import Qt.labs.platform as PlatformWidgets import Qt.labs.platform as PlatformWidgets
import HelperWidgets 2.0 as HelperWidgets import HelperWidgets 2.0 as HelperWidgets
@@ -32,9 +33,9 @@ StudioControls.Dialog {
fileName.text = "" fileName.text = ""
} }
HelperWidgets.RegExpValidator { RegularExpressionValidator {
id: fileNameValidator id: fileNameValidator
regExp: /^(\w[^*><?|]*)[^/\\:*><?|]$/ regularExpression: /^(\w[^*><?|]*)[^/\\:*><?|]$/
} }
PlatformWidgets.FileDialog { PlatformWidgets.FileDialog {
@@ -51,21 +52,30 @@ StudioControls.Dialog {
onClosed: root.reject() onClosed: root.reject()
} }
contentItem: Column { component Spacer: Item {
spacing: 10 implicitWidth: 1
implicitHeight: StudioTheme.Values.columnGap
}
Row { contentItem: ColumnLayout {
spacing: 10 spacing: 2
Text {
text: qsTr("File name: ") Text {
anchors.verticalCenter: parent.verticalCenter text: qsTr("File name: ")
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
} }
RowLayout {
spacing: StudioTheme.Values.sectionRowSpacing
Layout.fillWidth: true
StudioControls.TextField { StudioControls.TextField {
id: fileName id: fileName
anchors.verticalCenter: parent.verticalCenter Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillWidth: true
actionIndicator.visible: false actionIndicator.visible: false
translationIndicator.visible: false translationIndicator.visible: false
validator: fileNameValidator validator: fileNameValidator
@@ -74,49 +84,58 @@ StudioControls.Dialog {
Keys.onReturnPressed: btnCreate.onClicked() Keys.onReturnPressed: btnCreate.onClicked()
Keys.onEscapePressed: root.reject() Keys.onEscapePressed: root.reject()
onTextChanged: { onTextChanged: root.fileExists = root.backendValue.isCsvFile(fileName.text)
root.fileExists = root.backendValue.isCsvFile(fileName.text)
}
} }
HelperWidgets.Button { HelperWidgets.Button {
id: fileDialogButton id: fileDialogButton
anchors.verticalCenter: parent.verticalCenter Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
text: qsTr("Open") text: qsTr("Open")
onClicked: fileDialog.open() onClicked: fileDialog.open()
} }
} }
Row { Spacer {}
spacing: 10
Text {
text: qsTr("Collection name: ")
anchors.verticalCenter: parent.verticalCenter
color: StudioTheme.Values.themeTextColor
}
StudioControls.TextField {
id: collectionName
anchors.verticalCenter: parent.verticalCenter
actionIndicator.visible: false
translationIndicator.visible: false
validator: HelperWidgets.RegExpValidator {
regExp: /^\w+$/
}
Keys.onEnterPressed: btnCreate.onClicked()
Keys.onReturnPressed: btnCreate.onClicked()
Keys.onEscapePressed: root.reject()
}
}
Text { Text {
text: qsTr("The model name: ")
color: StudioTheme.Values.themeTextColor
}
StudioControls.TextField {
id: collectionName
Layout.fillWidth: true
actionIndicator.visible: false
translationIndicator.visible: false
validator: RegularExpressionValidator {
regularExpression: /^\w+$/
}
Keys.onEnterPressed: btnCreate.onClicked()
Keys.onReturnPressed: btnCreate.onClicked()
Keys.onEscapePressed: root.reject()
}
Spacer { implicitHeight: StudioTheme.Values.controlLabelGap }
Label {
id: fieldErrorText id: fieldErrorText
Layout.fillWidth: true
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
anchors.right: parent.right wrapMode: Label.WordWrap
padding: 5
background: Rectangle {
color: "transparent"
border.width: StudioTheme.Values.border
border.color: StudioTheme.Values.themeWarning
}
states: [ states: [
State { State {
@@ -145,31 +164,28 @@ StudioControls.Dialog {
PropertyChanges { PropertyChanges {
target: fieldErrorText target: fieldErrorText
text: qsTr("Collection name can not be empty") text: qsTr("The model name can not be empty")
visible: true visible: true
} }
} }
] ]
} }
Item { // spacer Spacer {}
width: 1
height: 20
}
Row { RowLayout {
anchors.right: parent.right spacing: StudioTheme.Values.sectionRowSpacing
spacing: 10
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
HelperWidgets.Button { HelperWidgets.Button {
id: btnCreate id: btnCreate
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Import") text: qsTr("Import")
enabled: root.fileExists && collectionName.text !== "" enabled: root.fileExists && collectionName.text !== ""
onClicked: { onClicked: {
let csvLoaded = root.backendValue.loadCsvFile(collectionName.text, fileName.text) let csvLoaded = root.backendValue.loadCsvFile(fileName.text, collectionName.text)
if (csvLoaded) if (csvLoaded)
root.accept() root.accept()
@@ -180,7 +196,6 @@ StudioControls.Dialog {
HelperWidgets.Button { HelperWidgets.Button {
text: qsTr("Cancel") text: qsTr("Cancel")
anchors.verticalCenter: parent.verticalCenter
onClicked: root.reject() onClicked: root.reject()
} }
} }

View File

@@ -0,0 +1,186 @@
// 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.Layouts
import StudioTheme 1.0 as StudioTheme
import StudioControls 1.0 as StudioControls
import HelperWidgets 2.0 as HelperWidgets
StudioControls.Dialog {
id: root
required property var model
property int __propertyIndex: -1
property string __oldName
title: qsTr("Edit Property")
clip: true
function editProperty(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.__oldName = previousName
newNameField.text = previousName
propertyType.initialType = previousType
forceChangeType.checked = false
let newPoint = mapFromGlobal(initialPosition.x, initialPosition.y)
x = newPoint.x
y = newPoint.y
root.open()
}
onAccepted: {
if (newNameField.text !== "" && newNameField.text !== root.__oldName)
root.model.renameColumn(root.__propertyIndex, newNameField.text)
if (propertyType.changed || forceChangeType.checked)
root.model.setPropertyType(root.__propertyIndex, propertyType.currentText, forceChangeType.checked)
}
onRejected: {
let currentDatatype = propertyType.initialType
propertyType.currentIndex = propertyType.find(currentDatatype)
}
component Spacer: Item {
implicitWidth: 1
implicitHeight: StudioTheme.Values.columnGap
}
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+$/
}
onTextChanged: {
editButton.enabled = newNameField.text !== ""
}
}
Spacer {}
Text {
text: qsTr("Type")
color: StudioTheme.Values.themeTextColor
}
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
}
Text {
text: qsTr("Force update values")
color: StudioTheme.Values.themeTextColor
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
}
}
Spacer {
visible: warningBox.visible
implicitHeight: StudioTheme.Values.controlLabelGap
}
Rectangle {
id: warningBox
Layout.fillWidth: true
Layout.preferredHeight: warning.height
visible: propertyType.initialType !== propertyType.currentText
color: "transparent"
clip: true
border.color: StudioTheme.Values.themeWarning
RowLayout {
id: warning
width: parent.width
HelperWidgets.IconLabel {
icon: StudioTheme.Constants.warning_medium
Layout.leftMargin: 10
}
Text {
text: qsTr("Conversion from %1 to %2 may lead to irreversible data loss")
.arg(propertyType.initialType)
.arg(propertyType.currentText)
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
HelperWidgets.Button {
id: editButton
text: qsTr("Edit")
onClicked: root.accept()
}
HelperWidgets.Button {
text: qsTr("Cancel")
onClicked: root.reject()
}
}
}
}

View File

@@ -2,6 +2,8 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import StudioTheme as StudioTheme import StudioTheme as StudioTheme
Rectangle { Rectangle {
@@ -10,42 +12,45 @@ Rectangle {
required property string text required property string text
required property string icon required property string icon
property alias tooltip: toolTip.text
property StudioTheme.ControlStyle style: StudioTheme.Values.viewBarButtonStyle property StudioTheme.ControlStyle style: StudioTheme.Values.viewBarButtonStyle
property int fontSize: StudioTheme.Values.baseFontSize
implicitHeight: style.squareControlSize.height implicitHeight: style.squareControlSize.height
implicitWidth: rowAlign.width implicitWidth: rowAlign.width
signal clicked() signal clicked()
Row { RowLayout {
id: rowAlign id: rowAlign
spacing: 0
leftPadding: StudioTheme.Values.inputHorizontalPadding anchors.verticalCenter: parent.verticalCenter
rightPadding: StudioTheme.Values.inputHorizontalPadding spacing: StudioTheme.Values.inputHorizontalPadding
Text { Text {
id: iconItem id: iconItem
width: root.style.squareControlSize.width
height: root.height Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
text: root.icon text: root.icon
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
font.family: StudioTheme.Constants.iconFont.family font.family: StudioTheme.Constants.iconFont.family
font.pixelSize: root.style.baseIconFontSize font.pixelSize: StudioTheme.Values.bigFont
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
leftPadding: StudioTheme.Values.inputHorizontalPadding
} }
Text { Text {
id: textItem id: textItem
height: root.height
anchors.verticalCenter: parent.verticalCenter Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
text: root.text text: root.text
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
font.family: StudioTheme.Constants.font.family font.family: StudioTheme.Constants.font.family
font.pixelSize: root.style.baseIconFontSize font.pixelSize: StudioTheme.Values.baseFontSize
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
rightPadding: StudioTheme.Values.inputHorizontalPadding
} }
} }
@@ -56,13 +61,20 @@ Rectangle {
onClicked: root.clicked() onClicked: root.clicked()
} }
ToolTip {
id: toolTip
visible: mouseArea.containsMouse && text !== ""
delay: 1000
}
states: [ states: [
State { State {
name: "default" name: "default"
when: !mouseArea.pressed && !mouseArea.containsMouse when: !mouseArea.pressed && !mouseArea.containsMouse
PropertyChanges { PropertyChanges {
target: root target: root
color: StudioTheme.Values.themeBackgroundColorNormal color: StudioTheme.Values.themeControlBackground
} }
}, },
State { State {

View File

@@ -3,6 +3,7 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts
import QtQuickDesignerTheme 1.0 import QtQuickDesignerTheme 1.0
import Qt.labs.platform as PlatformWidgets import Qt.labs.platform as PlatformWidgets
import HelperWidgets 2.0 as HelperWidgets import HelperWidgets 2.0 as HelperWidgets
@@ -12,7 +13,7 @@ import StudioTheme as StudioTheme
StudioControls.Dialog { StudioControls.Dialog {
id: root id: root
title: qsTr("Import Collections") title: qsTr("Import Models")
anchors.centerIn: parent anchors.centerIn: parent
closePolicy: Popup.CloseOnEscape closePolicy: Popup.CloseOnEscape
modal: true modal: true
@@ -21,7 +22,7 @@ StudioControls.Dialog {
property bool fileExists: false property bool fileExists: false
onOpened: { onOpened: {
fileName.text = qsTr("New Json File") fileName.text = qsTr("New JSON File")
fileName.selectAll() fileName.selectAll()
fileName.forceActiveFocus() fileName.forceActiveFocus()
} }
@@ -30,9 +31,9 @@ StudioControls.Dialog {
fileName.text = "" fileName.text = ""
} }
HelperWidgets.RegExpValidator { RegularExpressionValidator {
id: fileNameValidator id: fileNameValidator
regExp: /^(\w[^*><?|]*)[^/\\:*><?|]$/ regularExpression: /^(\w[^*><?|]*)[^/\\:*><?|]$/
} }
PlatformWidgets.FileDialog { PlatformWidgets.FileDialog {
@@ -49,21 +50,25 @@ StudioControls.Dialog {
onClosed: root.reject() onClosed: root.reject()
} }
contentItem: Column { contentItem: ColumnLayout {
spacing: 2 spacing: 2
Row { Text {
spacing: 10 text: qsTr("File name: ")
Text { color: StudioTheme.Values.themeTextColor
text: qsTr("File name: ") }
anchors.verticalCenter: parent.verticalCenter
color: StudioTheme.Values.themeTextColor RowLayout {
} spacing: StudioTheme.Values.sectionRowSpacing
Layout.fillWidth: true
StudioControls.TextField { StudioControls.TextField {
id: fileName id: fileName
anchors.verticalCenter: parent.verticalCenter Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillWidth: true
actionIndicator.visible: false actionIndicator.visible: false
translationIndicator.visible: false translationIndicator.visible: false
validator: fileNameValidator validator: fileNameValidator
@@ -72,38 +77,52 @@ StudioControls.Dialog {
Keys.onReturnPressed: btnCreate.onClicked() Keys.onReturnPressed: btnCreate.onClicked()
Keys.onEscapePressed: root.reject() Keys.onEscapePressed: root.reject()
onTextChanged: { onTextChanged: root.fileExists = root.backendValue.isJsonFile(fileName.text)
root.fileExists = root.backendValue.isJsonFile(fileName.text)
}
} }
HelperWidgets.Button { HelperWidgets.Button {
id: fileDialogButton id: fileDialogButton
anchors.verticalCenter: parent.verticalCenter
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
text: qsTr("Open") text: qsTr("Open")
onClicked: fileDialog.open() onClicked: fileDialog.open()
} }
} }
Text { Item { // spacer
implicitWidth: 1
implicitHeight: StudioTheme.Values.controlLabelGap
}
Label {
Layout.fillWidth: true
padding: 5
text: qsTr("File name cannot be empty.") text: qsTr("File name cannot be empty.")
wrapMode: Label.WordWrap
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
anchors.right: parent.right
visible: fileName.text === "" visible: fileName.text === ""
background: Rectangle {
color: "transparent"
border.width: StudioTheme.Values.border
border.color: StudioTheme.Values.themeWarning
}
} }
Item { // spacer Item { // spacer
width: 1 implicitWidth: 1
height: 20 implicitHeight: StudioTheme.Values.sectionColumnSpacing
} }
Row { RowLayout {
anchors.right: parent.right spacing: StudioTheme.Values.sectionRowSpacing
spacing: 10
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
HelperWidgets.Button { HelperWidgets.Button {
id: btnCreate id: btnCreate
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Import") text: qsTr("Import")
enabled: root.fileExists enabled: root.fileExists
@@ -119,7 +138,6 @@ StudioControls.Dialog {
HelperWidgets.Button { HelperWidgets.Button {
text: qsTr("Cancel") text: qsTr("Cancel")
anchors.verticalCenter: parent.verticalCenter
onClicked: root.reject() onClicked: root.reject()
} }
} }

View File

@@ -3,6 +3,7 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts
import HelperWidgets 2.0 as HelperWidgets import HelperWidgets 2.0 as HelperWidgets
import StudioControls 1.0 as StudioControls import StudioControls 1.0 as StudioControls
import StudioTheme as StudioTheme import StudioTheme as StudioTheme
@@ -17,22 +18,22 @@ StudioControls.Dialog {
implicitWidth: 300 implicitWidth: 300
modal: true modal: true
contentItem: Column { contentItem: ColumnLayout {
spacing: 20 spacing: StudioTheme.Values.sectionColumnSpacing
width: parent.width
Text { Text {
Layout.fillWidth: true
text: root.message text: root.message
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
width: root.width
leftPadding: 10 leftPadding: 10
rightPadding: 10 rightPadding: 10
} }
HelperWidgets.Button { HelperWidgets.Button {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
text: qsTr("Close") text: qsTr("Close")
anchors.right: parent.right
onClicked: root.reject() onClicked: root.reject()
} }
} }

View File

@@ -3,6 +3,7 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts
import HelperWidgets 2.0 as HelperWidgets import HelperWidgets 2.0 as HelperWidgets
import StudioControls 1.0 as StudioControls import StudioControls 1.0 as StudioControls
import StudioTheme as StudioTheme import StudioTheme as StudioTheme
@@ -11,7 +12,9 @@ Item {
id: root id: root
implicitWidth: 300 implicitWidth: 300
implicitHeight: wholeColumn.height + 6 implicitHeight: wholeColumn.height
property bool hasSelectedTarget
property color textColor property color textColor
property var collectionModel property var collectionModel
@@ -20,16 +23,24 @@ Item {
signal selectItem(int itemIndex) signal selectItem(int itemIndex)
signal deleteItem() signal deleteItem()
signal assignToSelected()
Column { function toggleExpanded() {
if (collectionListView.count > 0)
root.expanded = !root.expanded || sourceIsSelected;
}
ColumnLayout {
id: wholeColumn id: wholeColumn
width: parent.width
spacing: 0
Item { Item {
id: boundingRect id: boundingRect
anchors.centerIn: root Layout.fillWidth: true
width: root.width - 24 Layout.preferredHeight: nameHolder.height
height: nameHolder.height Layout.leftMargin: 6
clip: true clip: true
MouseArea { MouseArea {
@@ -48,8 +59,7 @@ Item {
} }
onDoubleClicked: (event) => { onDoubleClicked: (event) => {
if (collectionListView.count > 0) root.toggleExpanded()
root.expanded = !root.expanded;
} }
} }
@@ -58,26 +68,27 @@ Item {
anchors.fill: parent anchors.fill: parent
} }
Row { RowLayout {
width: parent.width - threeDots.width width: parent.width
leftPadding: 20
Text { Text {
id: expandButton id: expandButton
property StudioTheme.ControlStyle style: StudioTheme.Values.viewBarButtonStyle property StudioTheme.ControlStyle style: StudioTheme.Values.viewBarButtonStyle
width: expandButton.style.squareControlSize.width Layout.preferredWidth: expandButton.style.squareControlSize.width
height: nameHolder.height Layout.preferredHeight: nameHolder.height
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
text: StudioTheme.Constants.startNode text: StudioTheme.Constants.startNode
font.family: StudioTheme.Constants.iconFont.family font.family: StudioTheme.Constants.iconFont.family
font.pixelSize: expandButton.style.baseIconFontSize font.pixelSize: 6
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
color: textColor color: textColor
rotation: root.expanded ? 90 : 0 rotation: root.expanded ? 90 : 0
visible: collectionListView.count > 0
Behavior on rotation { Behavior on rotation {
SpringAnimation { spring: 2; damping: 0.2 } SpringAnimation { spring: 2; damping: 0.2 }
@@ -85,18 +96,17 @@ Item {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.RightButton + Qt.LeftButton acceptedButtons: Qt.RightButton | Qt.LeftButton
onClicked: (event) => { onClicked: root.toggleExpanded()
root.expanded = !root.expanded
event.accepted = true
}
} }
visible: collectionListView.count > 0
} }
Text { Text {
id: nameHolder id: nameHolder
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
text: sourceName text: sourceName
font.pixelSize: StudioTheme.Values.baseFontSize font.pixelSize: StudioTheme.Values.baseFontSize
color: textColor color: textColor
@@ -105,30 +115,29 @@ Item {
rightPadding: 8 rightPadding: 8
bottomPadding: 8 bottomPadding: 8
elide: Text.ElideMiddle elide: Text.ElideMiddle
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
}
Text { Text {
id: threeDots id: threeDots
text: StudioTheme.Constants.more_medium Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
font.family: StudioTheme.Constants.iconFont.family
font.pixelSize: StudioTheme.Values.baseIconFontSize
color: textColor
anchors.right: boundingRect.right
anchors.verticalCenter: parent.verticalCenter
rightPadding: 12
topPadding: nameHolder.topPadding
bottomPadding: nameHolder.bottomPadding
verticalAlignment: Text.AlignVCenter
MouseArea { text: StudioTheme.Constants.more_medium
anchors.fill: parent font.family: StudioTheme.Constants.iconFont.family
acceptedButtons: Qt.RightButton + Qt.LeftButton font.pixelSize: StudioTheme.Values.baseIconFontSize
onClicked: (event) => { color: textColor
collectionMenu.popup() rightPadding: 12
event.accepted = true topPadding: nameHolder.topPadding
bottomPadding: nameHolder.bottomPadding
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton | Qt.LeftButton
onClicked: collectionMenu.popup()
} }
} }
} }
@@ -137,18 +146,20 @@ Item {
ListView { ListView {
id: collectionListView id: collectionListView
width: parent.width Layout.fillWidth: true
height: root.expanded ? contentHeight : 0 Layout.preferredHeight: root.expanded ? contentHeight : 0
model: collections Layout.leftMargin: 6
model: internalModels
clip: true clip: true
Behavior on height { Behavior on Layout.preferredHeight {
NumberAnimation {duration: 500} NumberAnimation {duration: 500}
} }
delegate: CollectionItem { delegate: CollectionItem {
width: parent.width width: collectionListView.width
onDeleteItem: root.model.removeRow(index) sourceType: collectionListView.model.sourceType
onDeleteItem: collectionListView.model.removeRow(index)
} }
} }
} }
@@ -156,6 +167,8 @@ Item {
StudioControls.Menu { StudioControls.Menu {
id: collectionMenu id: collectionMenu
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
StudioControls.MenuItem { StudioControls.MenuItem {
text: qsTr("Delete") text: qsTr("Delete")
shortcut: StandardKey.Delete shortcut: StandardKey.Delete
@@ -167,6 +180,17 @@ Item {
shortcut: StandardKey.Replace shortcut: StandardKey.Replace
onTriggered: renameDialog.open() onTriggered: renameDialog.open()
} }
StudioControls.MenuItem {
text: qsTr("Assign to the selected node")
enabled: root.hasSelectedTarget
onTriggered: root.assignToSelected()
}
}
component Spacer: Item {
implicitWidth: 1
implicitHeight: StudioTheme.Values.sectionColumnSpacing
} }
StudioControls.Dialog { StudioControls.Dialog {
@@ -174,22 +198,17 @@ Item {
title: qsTr("Deleting source") title: qsTr("Deleting source")
contentItem: Column { contentItem: ColumnLayout {
spacing: 2 spacing: StudioTheme.Values.sectionColumnSpacing
Text { Text {
text: qsTr("Are you sure that you want to delete source \"" + sourceName + "\"?") text: qsTr("Are you sure that you want to delete source \"" + sourceName + "\"?")
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
} }
Item { // spacer RowLayout {
width: 1 Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
height: 20 spacing: StudioTheme.Values.sectionRowSpacing
}
Row {
anchors.right: parent.right
spacing: 10
HelperWidgets.Button { HelperWidgets.Button {
id: btnDelete id: btnDelete
@@ -220,7 +239,7 @@ Item {
newNameField.text = sourceName newNameField.text = sourceName
} }
contentItem: Column { contentItem: ColumnLayout {
spacing: 2 spacing: 2
Text { Text {
@@ -228,38 +247,35 @@ Item {
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
} }
Row { Spacer {}
spacing: 10
Text {
text: qsTr("New name:")
color: StudioTheme.Values.themeTextColor
}
StudioControls.TextField { Text {
id: newNameField text: qsTr("New name:")
color: StudioTheme.Values.themeTextColor
}
actionIndicator.visible: false StudioControls.TextField {
translationIndicator.visible: false id: newNameField
validator: newNameValidator
Keys.onEnterPressed: renameDialog.accept() Layout.fillWidth: true
Keys.onReturnPressed: renameDialog.accept() actionIndicator.visible: false
Keys.onEscapePressed: renameDialog.reject() translationIndicator.visible: false
validator: newNameValidator
onTextChanged: { Keys.onEnterPressed: renameDialog.accept()
btnRename.enabled = newNameField.text !== "" Keys.onReturnPressed: renameDialog.accept()
} Keys.onEscapePressed: renameDialog.reject()
onTextChanged: {
btnRename.enabled = newNameField.text !== ""
} }
} }
Item { // spacer Spacer {}
width: 1
height: 20
}
Row { RowLayout {
anchors.right: parent.right Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
spacing: 10 spacing: StudioTheme.Values.sectionRowSpacing
HelperWidgets.Button { HelperWidgets.Button {
id: btnRename id: btnRename
@@ -276,9 +292,9 @@ Item {
} }
} }
HelperWidgets.RegExpValidator { RegularExpressionValidator {
id: newNameValidator id: newNameValidator
regExp: /^\w+$/ regularExpression: /^\w+$/
} }
states: [ states: [
@@ -325,6 +341,12 @@ Item {
PropertyChanges { PropertyChanges {
target: root target: root
textColor: StudioTheme.Values.themeIconColorSelected textColor: StudioTheme.Values.themeIconColorSelected
expanded: true
}
PropertyChanges {
target: expandButton
enabled: false
} }
} }
] ]

View File

@@ -3,23 +3,37 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts
import QtQuickDesignerTheme 1.0 import QtQuickDesignerTheme 1.0
import Qt.labs.platform as PlatformWidgets
import HelperWidgets 2.0 as HelperWidgets import HelperWidgets 2.0 as HelperWidgets
import StudioControls 1.0 as StudioControls import StudioControls 1.0 as StudioControls
import StudioTheme as StudioTheme import StudioTheme as StudioTheme
import CollectionEditor 1.0
StudioControls.Dialog { StudioControls.Dialog {
id: root id: root
title: qsTr("Add a new Collection") enum SourceType { NewJson, NewCsv, ExistingCollection, NewCollectionToJson }
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 anchors.centerIn: parent
closePolicy: Popup.CloseOnEscape closePolicy: Popup.CloseOnEscape
modal: true modal: true
required property var backendValue
onOpened: { onOpened: {
collectionName.text = "Collection" collectionName.text = qsTr("Model")
updateType()
updateJsonSourceIndex()
updateCollectionExists()
} }
onRejected: { onRejected: {
@@ -27,65 +41,252 @@ StudioControls.Dialog {
} }
onAccepted: { onAccepted: {
if (collectionName.text !== "") if (root.isValid) {
root.backendValue.addCollection(collectionName.text) root.backendValue.addCollection(collectionName.text,
root.collectionType,
newCollectionPath.text,
jsonCollections.currentValue)
}
} }
contentItem: Column { function updateType() {
spacing: 10 newCollectionPath.text = ""
Row { if (typeMode.currentValue === NewCollectionDialog.SourceType.NewJson) {
spacing: 10 newCollectionFileDialog.nameFilters = ["JSON Files (*.json)"]
newCollectionFileDialog.fileMode = PlatformWidgets.FileDialog.SaveFile
newCollectionPath.enabled = true
jsonCollections.enabled = false
typeMode.collectionType = "json"
} else if (typeMode.currentValue === NewCollectionDialog.SourceType.NewCsv) {
newCollectionFileDialog.nameFilters = ["Comma-Separated Values (*.csv)"]
newCollectionFileDialog.fileMode = PlatformWidgets.FileDialog.SaveFile
newCollectionPath.enabled = true
jsonCollections.enabled = false
typeMode.collectionType = "csv"
} else if (typeMode.currentValue === NewCollectionDialog.SourceType.ExistingCollection) {
newCollectionFileDialog.nameFilters = ["All Model Group Files (*.json *.csv)",
"JSON Files (*.json)",
"Comma-Separated Values (*.csv)"]
newCollectionFileDialog.fileMode = PlatformWidgets.FileDialog.OpenFile
newCollectionPath.enabled = true
jsonCollections.enabled = false
typeMode.collectionType = "existing"
} else if (typeMode.currentValue === NewCollectionDialog.SourceType.NewCollectionToJson) {
newCollectionFileDialog.nameFilters = [""]
newCollectionPath.enabled = false
jsonCollections.enabled = true
typeMode.collectionType = "json"
}
}
function updateJsonSourceIndex() {
if (!jsonCollections.enabled) {
jsonCollections.currentIndex = -1
return
}
if (jsonCollections.currentIndex === -1 && jsonCollections.model.rowCount())
jsonCollections.currentIndex = 0
}
function updateCollectionExists() {
collectionName.alreadyExists = sourceModel.collectionExists(jsonCollections.currentValue,
collectionName.text)
}
component NameField: Text {
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
horizontalAlignment: Qt.AlignRight
verticalAlignment: Qt.AlignCenter
color: StudioTheme.Values.themeTextColor
font.family: StudioTheme.Constants.font.family
font.pixelSize: StudioTheme.Values.baseIconFontSize
}
component ErrorField: Text {
Layout.columnSpan: 2
color: StudioTheme.Values.themeError
font.family: StudioTheme.Constants.font.family
font.pixelSize: StudioTheme.Values.baseIconFontSize
}
component Spacer: Item {
Layout.minimumWidth: 1
Layout.preferredHeight: StudioTheme.Values.columnGap
}
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 { Text {
text: qsTr("Collection name: ") id: newCollectionPath
anchors.verticalCenter: parent.verticalCenter
color: StudioTheme.Values.themeTextColor 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
} }
StudioControls.TextField { HelperWidgets.Button {
id: collectionName Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
text: qsTr("Select")
anchors.verticalCenter: parent.verticalCenter onClicked: newCollectionFileDialog.open()
actionIndicator.visible: false
translationIndicator.visible: false PlatformWidgets.FileDialog {
validator: HelperWidgets.RegExpValidator { id: newCollectionFileDialog
regExp: /^\w+$/
title: qsTr("Select source file")
fileMode: PlatformWidgets.FileDialog.OpenFile
acceptLabel: newCollectionFileDialog.fileMode === PlatformWidgets.FileDialog.OpenFile
? qsTr("Open")
: qsTr("Add")
onAccepted: newCollectionPath.text = newCollectionFileDialog.currentFile
} }
Keys.onEnterPressed: btnCreate.onClicked()
Keys.onReturnPressed: btnCreate.onClicked()
Keys.onEscapePressed: root.reject()
} }
} }
Text { ErrorField {
id: fieldErrorText visible: !newCollectionPath.isValid
color: StudioTheme.Values.themeTextColor text: qsTr("Select a file to continue")
anchors.right: parent.right
text: qsTr("Collection name can not be empty")
visible: collectionName.text === ""
} }
Item { // spacer Spacer { visible: newCollectionPath.enabled }
width: 1
height: 20 NameField {
text: qsTr("JSON model group")
visible: jsonCollections.enabled
} }
Row { StudioControls.ComboBox {
anchors.right: parent.right id: jsonCollections
spacing: 10
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
}
StudioControls.TextField {
id: collectionName
readonly property bool isValid: !collectionName.enabled
|| (collectionName.text !== "" && !collectionName.alreadyExists)
property bool alreadyExists
Layout.fillWidth: true
visible: collectionName.enabled
actionIndicator.visible: false
translationIndicator.visible: false
validator: RegularExpressionValidator {
regularExpression: /^\w+$/
}
Keys.onEnterPressed: btnCreate.onClicked()
Keys.onReturnPressed: btnCreate.onClicked()
Keys.onEscapePressed: root.reject()
onTextChanged: root.updateCollectionExists()
}
ErrorField {
text: qsTr("The model name can not be empty")
visible: collectionName.enabled && collectionName.text === ""
}
ErrorField {
text: qsTr("The model name already exists %1").arg(collectionName.text)
visible: collectionName.enabled && collectionName.alreadyExists
}
Spacer { visible: collectionName.visible }
RowLayout {
spacing: StudioTheme.Values.sectionRowSpacing
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
HelperWidgets.Button { HelperWidgets.Button {
id: btnCreate id: btnCreate
anchors.verticalCenter: parent.verticalCenter
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
text: qsTr("Create") text: qsTr("Create")
enabled: collectionName.text !== "" enabled: root.isValid
onClicked: root.accept() onClicked: root.accept()
} }
HelperWidgets.Button { HelperWidgets.Button {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
text: qsTr("Cancel") text: qsTr("Cancel")
anchors.verticalCenter: parent.verticalCenter
onClicked: root.reject() onClicked: root.reject()
} }
} }

View File

@@ -1,134 +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 StudioTheme 1.0 as StudioTheme
Rectangle {
id: root
required property var model
property alias leftPadding: topRow.leftPadding
property real rightPadding: topRow.rightPadding
color: StudioTheme.Values.themeBackgroundColorAlternate
Column {
id: topRow
spacing: 0
width: parent.width
leftPadding: 20
rightPadding: 0
topPadding: 5
Text {
id: collectionNameText
leftPadding: 8
rightPadding: 8
topPadding: 3
bottomPadding: 3
color: StudioTheme.Values.themeTextColor
text: root.model.collectionName
font.pixelSize: StudioTheme.Values.mediumIconFont
elide: Text.ElideRight
Rectangle {
anchors.fill: parent
z: parent.z - 1
color: StudioTheme.Values.themeBackgroundColorNormal
}
}
Item { // spacer
width: 1
height: 10
}
HorizontalHeaderView {
id: headerView
property real topPadding: 5
property real bottomPadding: 5
height: headerMetrics.height + topPadding + bottomPadding
syncView: tableView
clip: true
delegate: Rectangle {
implicitWidth: 100
implicitHeight: headerText.height
color: StudioTheme.Values.themeControlBackground
border.width: 2
border.color: StudioTheme.Values.themeControlOutline
clip: true
Text {
id: headerText
topPadding: headerView.topPadding
bottomPadding: headerView.bottomPadding
leftPadding: 5
rightPadding: 5
text: display
font.pixelSize: headerMetrics.font
color: StudioTheme.Values.themeIdleGreen
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
anchors.centerIn: parent
elide: Text.ElideRight
}
}
TextMetrics {
id: headerMetrics
font.pixelSize: StudioTheme.Values.baseFontSize
text: "Xq"
}
}
}
TableView {
id: tableView
anchors {
top: topRow.bottom
left: parent.left
right: parent.right
bottom: parent.bottom
leftMargin: root.leftPadding
rightMargin: root.rightPadding
}
model: root.model
delegate: Rectangle {
implicitWidth: 100
implicitHeight: itemText.height
color: StudioTheme.Values.themeControlBackground
border.width: 1
border.color: StudioTheme.Values.themeControlOutline
Text {
id: itemText
text: display
width: parent.width
leftPadding: 5
topPadding: 3
bottomPadding: 3
font.pixelSize: StudioTheme.Values.baseFontSize
color: StudioTheme.Values.themeTextColor
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
}
}
}

View File

@@ -2,11 +2,13 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick import QtQuick
import StudioTheme 1.0 as StudioTheme import StudioTheme as StudioTheme
import StudioControls as StudioControls
import HelperWidgets as HelperWidgets import HelperWidgets as HelperWidgets
PopupDialog { StudioControls.PopupDialog {
property alias backend: form.backend property alias backend: form.backend
titleBar: Row { titleBar: Row {
spacing: 30 // TODO spacing: 30 // TODO
anchors.fill: parent anchors.fill: parent
@@ -16,6 +18,7 @@ PopupDialog {
text: qsTr("Owner") text: qsTr("Owner")
font.pixelSize: StudioTheme.Values.myFontSize font.pixelSize: StudioTheme.Values.myFontSize
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
HelperWidgets.ToolTipArea { HelperWidgets.ToolTipArea {
anchors.fill: parent anchors.fill: parent
tooltip: qsTr("The owner of the property") tooltip: qsTr("The owner of the property")
@@ -27,14 +30,10 @@ PopupDialog {
font.pixelSize: StudioTheme.Values.myFontSize font.pixelSize: StudioTheme.Values.myFontSize
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: form.backend.targetNode text: form.backend.targetNode
} }
} }
BindingsDialogForm { BindingsDialogForm {
id: form id: form
y: 32
height: 160
} }
} }

View File

@@ -1,6 +1,6 @@
// Copyright (C) 2023 The Qt Company Ltd. // Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import StudioControls as StudioControls import StudioControls as StudioControls
@@ -10,12 +10,11 @@ Column {
id: root id: root
readonly property real horizontalSpacing: 10 readonly property real horizontalSpacing: 10
readonly property real verticalSpacing: 16 readonly property real verticalSpacing: 12
readonly property real columnWidth: (root.width - root.horizontalSpacing) / 2 readonly property real columnWidth: (root.width - root.horizontalSpacing) / 2
property var backend property var backend
y: StudioTheme.Values.popupMargin
width: parent.width width: parent.width
spacing: root.verticalSpacing spacing: root.verticalSpacing
@@ -53,8 +52,8 @@ Column {
PopupLabel { PopupLabel {
width: root.columnWidth width: root.columnWidth
text: backend.targetNode text: backend.targetNode
anchors.verticalCenter: parent.verticalCenter
} }
} }
Row { Row {

View File

@@ -15,12 +15,6 @@ ListView {
property bool adsFocus: false property bool adsFocus: false
// Temporarily remove due to dockwidget focus issue
//onAdsFocusChanged: {
// if (!root.adsFocus)
// dialog.close()
//}
clip: true clip: true
interactive: true interactive: true
highlightMoveDuration: 0 highlightMoveDuration: 0
@@ -43,9 +37,7 @@ ListView {
&& verticalScrollBar.isNeeded && verticalScrollBar.isNeeded
} }
onVisibleChanged: { onVisibleChanged: dialog.close()
dialog.hide()
}
property int modelCurrentIndex: root.model.currentIndex ?? 0 property int modelCurrentIndex: root.model.currentIndex ?? 0
@@ -72,7 +64,7 @@ ListView {
function addBinding() { function addBinding() {
ConnectionsEditorEditorBackend.bindingModel.add() ConnectionsEditorEditorBackend.bindingModel.add()
if (root.currentItem) if (root.currentItem)
dialog.popup(root.currentItem.delegateMouseArea) dialog.show(root.currentItem.delegateMouseArea)
} }
function resetIndex() { function resetIndex() {
@@ -104,9 +96,11 @@ ListView {
property alias delegateMouseArea: mouseArea property alias delegateMouseArea: mouseArea
property bool hovered: mouseArea.containsMouse || toolTipArea.containsMouse
width: ListView.view.width width: ListView.view.width
height: root.style.squareControlSize.height height: root.style.squareControlSize.height
color: mouseArea.containsMouse ? color: itemDelegate.hovered ?
itemDelegate.ListView.isCurrentItem ? root.style.interactionHover itemDelegate.ListView.isCurrentItem ? root.style.interactionHover
: root.style.background.hover : root.style.background.hover
: "transparent" : "transparent"
@@ -120,7 +114,7 @@ ListView {
onClicked: { onClicked: {
root.model.currentIndex = itemDelegate.index root.model.currentIndex = itemDelegate.index
root.currentIndex = itemDelegate.index root.currentIndex = itemDelegate.index
dialog.popup(mouseArea) dialog.show(mouseArea)
} }
} }

View File

@@ -2,12 +2,13 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick import QtQuick
import StudioControls 1.0 as StudioControls import StudioControls as StudioControls
import StudioTheme 1.0 as StudioTheme import StudioTheme as StudioTheme
import HelperWidgets 2.0 as HelperWidgets import HelperWidgets as HelperWidgets
PopupDialog { StudioControls.PopupDialog {
id: root id: root
property alias backend: form.backend property alias backend: form.backend
titleBar: Row { titleBar: Row {
@@ -19,6 +20,7 @@ PopupDialog {
text: qsTr("Target") text: qsTr("Target")
font.pixelSize: StudioTheme.Values.myFontSize font.pixelSize: StudioTheme.Values.myFontSize
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
HelperWidgets.ToolTipArea { HelperWidgets.ToolTipArea {
anchors.fill: parent anchors.fill: parent
tooltip: qsTr("Sets the Component that is connected to a <b>Signal</b>.") tooltip: qsTr("Sets the Component that is connected to a <b>Signal</b>.")
@@ -46,6 +48,9 @@ PopupDialog {
function onPopupShouldClose() { function onPopupShouldClose() {
root.close() root.close()
} }
function onPopupShouldOpen() {
root.showGlobal()
}
} }
} }
} }

View File

@@ -16,7 +16,6 @@ Column {
property var backend property var backend
y: StudioTheme.Values.popupMargin
width: parent.width width: parent.width
spacing: root.verticalSpacing spacing: root.verticalSpacing
@@ -45,6 +44,7 @@ Column {
StudioControls.TopLevelComboBox { StudioControls.TopLevelComboBox {
id: signal id: signal
style: StudioTheme.Values.connectionPopupControlStyle style: StudioTheme.Values.connectionPopupControlStyle
width: root.columnWidth width: root.columnWidth
@@ -216,71 +216,100 @@ Column {
&& backend.hasCondition && backend.hasElse && backend.hasCondition && backend.hasElse
} }
HelperWidgets.AbstractButton { // code preview toolbar
id: editorButton Column {
buttonIcon: StudioTheme.Constants.codeEditor_medium id: miniToolbarEditor
tooltip: qsTr("Write the conditions for the components and the signals manually.")
onClicked: expressionDialogLoader.show()
}
// Editor
Rectangle {
id: editor
width: parent.width width: parent.width
height: 150 spacing: -2
color: StudioTheme.Values.themeConnectionCodeEditor
Text { Rectangle {
id: code id: miniToolbar
anchors.fill: parent width: parent.width
anchors.margins: 4 height: editorButton.height + 2
text: backend.indentedSource radius: 4
color: StudioTheme.Values.themeTextColor z: -1
font.pixelSize: StudioTheme.Values.myFontSize color: StudioTheme.Values.themeConnectionEditorMicroToolbar
wrapMode: Text.Wrap
horizontalAlignment: code.lineCount === 1 ? Text.AlignHCenter : Text.AlignLeft
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
} Row {
spacing: 2
Loader { HelperWidgets.AbstractButton {
id: expressionDialogLoader id: editorButton
parent: editor style: StudioTheme.Values.microToolbarButtonStyle
anchors.fill: parent buttonIcon: StudioTheme.Constants.codeEditor_medium
visible: false tooltip: qsTr("Write the conditions for the components and the signals manually.")
active: visible onClicked: expressionDialogLoader.show()
function show() {
expressionDialogLoader.visible = true
}
sourceComponent: Item {
id: bindingEditorParent
Component.onCompleted: {
bindingEditor.showWidget()
bindingEditor.text = backend.source
bindingEditor.showControls(false)
bindingEditor.setMultilne(true)
bindingEditor.updateWindowName()
} }
HelperWidgets.AbstractButton {
ActionEditor { id: jumpToCodeButton
id: bindingEditor style: StudioTheme.Values.microToolbarButtonStyle
buttonIcon: StudioTheme.Constants.jumpToCode_medium
onRejected: { tooltip: qsTr("Jump to the code.")
hideWidget() onClicked: backend.jumpToCode()
expressionDialogLoader.visible = false
}
onAccepted: {
backend.setNewSource(bindingEditor.text)
hideWidget()
expressionDialogLoader.visible = false
}
} }
} }
} }
}
} // Editor
Rectangle {
id: editor
width: parent.width
height: 150
color: StudioTheme.Values.themeConnectionCodeEditor
Text {
id: code
anchors.fill: parent
anchors.margins: 4
text: backend.indentedSource
color: StudioTheme.Values.themeTextColor
font.pixelSize: StudioTheme.Values.myFontSize
wrapMode: Text.Wrap
horizontalAlignment: code.lineCount === 1 ? Text.AlignHCenter : Text.AlignLeft
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
Loader {
id: expressionDialogLoader
parent: editor
anchors.fill: parent
visible: false
active: visible
function show() {
expressionDialogLoader.visible = true
}
sourceComponent: Item {
id: bindingEditorParent
Component.onCompleted: {
bindingEditor.showWidget()
bindingEditor.text = backend.source
bindingEditor.showControls(false)
bindingEditor.setMultilne(true)
bindingEditor.updateWindowName()
}
ActionEditor {
id: bindingEditor
onRejected: {
hideWidget()
expressionDialogLoader.visible = false
}
onAccepted: {
backend.setNewSource(bindingEditor.text)
hideWidget()
expressionDialogLoader.visible = false
}
}
}
} // loader
} // rect
} //col 2
}//col1

View File

@@ -15,12 +15,6 @@ ListView {
property bool adsFocus: false property bool adsFocus: false
// Temporarily remove due to dockwidget focus issue
//onAdsFocusChanged: {
// if (!root.adsFocus)
// dialog.close()
//}
clip: true clip: true
interactive: true interactive: true
highlightMoveDuration: 0 highlightMoveDuration: 0
@@ -43,9 +37,7 @@ ListView {
&& verticalScrollBar.isNeeded && verticalScrollBar.isNeeded
} }
onVisibleChanged: { onVisibleChanged: dialog.close()
dialog.hide()
}
property int modelCurrentIndex: root.model.currentIndex ?? 0 property int modelCurrentIndex: root.model.currentIndex ?? 0
@@ -73,7 +65,7 @@ ListView {
function addConnection() { function addConnection() {
ConnectionsEditorEditorBackend.connectionModel.add() ConnectionsEditorEditorBackend.connectionModel.add()
if (root.currentItem) if (root.currentItem)
dialog.popup(root.currentItem.delegateMouseArea) dialog.show(root.currentItem.delegateMouseArea)
} }
function resetIndex() { function resetIndex() {
@@ -124,7 +116,7 @@ ListView {
root.model.currentIndex = itemDelegate.index root.model.currentIndex = itemDelegate.index
root.currentIndex = itemDelegate.index root.currentIndex = itemDelegate.index
dialog.backend.currentRow = itemDelegate.index dialog.backend.currentRow = itemDelegate.index
dialog.popup(mouseArea) dialog.show(mouseArea)
} }
} }

View File

@@ -22,7 +22,7 @@ Rectangle {
Column { Column {
id: column id: column
anchors.fill: parent anchors.fill: parent
spacing: 8 // TODO spacing: 5 // TODO
Rectangle { Rectangle {
id: toolbar id: toolbar

View File

@@ -1,107 +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 HelperWidgets 2.0 as HelperWidgets
import StudioTheme 1.0 as StudioTheme
import ConnectionsEditorEditorBackend
Window {
id: window
property alias titleBar: titleBarContent.children
default property alias content: mainContent.children
property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle
width: 300
height: column.implicitHeight
visible: true
flags: Qt.FramelessWindowHint | Qt.Dialog
Rectangle {
anchors.fill: parent
color: StudioTheme.Values.themePopoutBackground
border.color: "#636363"//StudioTheme.Values.themeTextColor
}
function ensureVerticalPosition() {
if ((window.y + window.height) > (Screen.height - window.style.dialogScreenMargin)) {
window.y = (Screen.height - window.height - window.style.dialogScreenMargin)
}
}
onHeightChanged: window.ensureVerticalPosition()
function popup(item) {
var padding = 12
var p = item.mapToGlobal(0, 0)
window.x = p.x - window.width - padding
if (window.x < 0)
window.x = p.x + item.width + padding
window.y = p.y
window.ensureVerticalPosition()
window.show()
window.raise()
}
Column {
id: column
anchors.fill: parent
Item {
id: titleBarItem
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
anchors.leftMargin: StudioTheme.Values.popupMargin
anchors.rightMargin: StudioTheme.Values.popupMargin
spacing: 0
Item {
id: titleBarContent
width: row.width - closeIndicator.width //- row.anchors.leftMargin
height: row.height
}
HelperWidgets.IconIndicator {
id: closeIndicator
anchors.verticalCenter: parent.verticalCenter
icon: StudioTheme.Constants.colorPopupClose
pixelSize: StudioTheme.Values.myIconFontSize// * 1.4
onClicked: window.close()
}
}
}
Rectangle {
width: parent.width - 8
height: 1
anchors.horizontalCenter: parent.horizontalCenter
color: "#636363"
}
Item {
id: mainContent
width: parent.width - 2 * StudioTheme.Values.popupMargin
height: mainContent.childrenRect.height + 2 * StudioTheme.Values.popupMargin
anchors.horizontalCenter: parent.horizontalCenter
}
}
}

View File

@@ -2,10 +2,11 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick import QtQuick
import StudioTheme 1.0 as StudioTheme import StudioTheme as StudioTheme
import HelperWidgets as HelperWidgets import HelperWidgets as HelperWidgets
import StudioControls as StudioControls
PopupDialog { StudioControls.PopupDialog {
property alias backend: form.backend property alias backend: form.backend
titleBar: Row { titleBar: Row {
@@ -17,6 +18,7 @@ PopupDialog {
text: qsTr("Owner") text: qsTr("Owner")
font.pixelSize: StudioTheme.Values.myFontSize font.pixelSize: StudioTheme.Values.myFontSize
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
HelperWidgets.ToolTipArea { HelperWidgets.ToolTipArea {
anchors.fill: parent anchors.fill: parent
tooltip: qsTr("The owner of the property") tooltip: qsTr("The owner of the property")
@@ -29,12 +31,9 @@ PopupDialog {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: form.backend.targetNode text: form.backend.targetNode
} }
} }
PropertiesDialogForm { PropertiesDialogForm {
id: form id: form
y: 32
height: 180
} }
} }

View File

@@ -10,12 +10,11 @@ Column {
id: root id: root
readonly property real horizontalSpacing: 10 readonly property real horizontalSpacing: 10
readonly property real verticalSpacing: 16 readonly property real verticalSpacing: 12
readonly property real columnWidth: (root.width - root.horizontalSpacing) / 2 readonly property real columnWidth: (root.width - root.horizontalSpacing) / 2
property var backend property var backend
y: StudioTheme.Values.popupMargin
width: parent.width width: parent.width
spacing: root.verticalSpacing spacing: root.verticalSpacing
@@ -23,11 +22,11 @@ Column {
text: qsTr("Type") text: qsTr("Type")
tooltip: qsTr("Sets the category of the <b>Local Custom Property</b>.") tooltip: qsTr("Sets the category of the <b>Local Custom Property</b>.")
} }
StudioControls.TopLevelComboBox { StudioControls.TopLevelComboBox {
id: type id: type
style: StudioTheme.Values.connectionPopupControlStyle style: StudioTheme.Values.connectionPopupControlStyle
width: root.columnWidth width: root.columnWidth
//width: root.width
model: backend.type.model ?? [] model: backend.type.model ?? []
onActivated: backend.type.activateIndex(type.currentIndex) onActivated: backend.type.activateIndex(type.currentIndex)
@@ -53,6 +52,7 @@ Column {
Row { Row {
spacing: root.horizontalSpacing spacing: root.horizontalSpacing
StudioControls.TextField { StudioControls.TextField {
id: name id: name
@@ -61,10 +61,9 @@ Column {
translationIndicatorVisible: false translationIndicatorVisible: false
text: backend.name.text ?? "" text: backend.name.text ?? ""
onEditingFinished: { onEditingFinished: backend.name.activateText(name.text)
backend.name.activateText(name.text)
}
} }
StudioControls.TextField { StudioControls.TextField {
id: value id: value
@@ -74,9 +73,7 @@ Column {
text: backend.value.text ?? "" text: backend.value.text ?? ""
onEditingFinished: { onEditingFinished: backend.value.activateText(value.text)
backend.value.activateText(value.text)
}
} }
} }
} }

View File

@@ -15,12 +15,6 @@ ListView {
property bool adsFocus: false property bool adsFocus: false
// Temporarily remove due to dockwidget focus issue
//onAdsFocusChanged: {
// if (!root.adsFocus)
// dialog.close()
//}
clip: true clip: true
interactive: true interactive: true
highlightMoveDuration: 0 highlightMoveDuration: 0
@@ -43,9 +37,7 @@ ListView {
&& verticalScrollBar.isNeeded && verticalScrollBar.isNeeded
} }
onVisibleChanged: { onVisibleChanged: dialog.close()
dialog.hide()
}
property int modelCurrentIndex: root.model.currentIndex ?? 0 property int modelCurrentIndex: root.model.currentIndex ?? 0
@@ -72,7 +64,7 @@ ListView {
function addProperty() { function addProperty() {
ConnectionsEditorEditorBackend.dynamicPropertiesModel.add() ConnectionsEditorEditorBackend.dynamicPropertiesModel.add()
if (root.currentItem) if (root.currentItem)
dialog.popup(root.currentItem.delegateMouseArea) dialog.show(root.currentItem.delegateMouseArea)
} }
function resetIndex() { function resetIndex() {
@@ -104,9 +96,11 @@ ListView {
property alias delegateMouseArea: mouseArea property alias delegateMouseArea: mouseArea
property bool hovered: mouseArea.containsMouse || toolTipArea.containsMouse
width: ListView.view.width width: ListView.view.width
height: root.style.squareControlSize.height height: root.style.squareControlSize.height
color: mouseArea.containsMouse ? color: itemDelegate.hovered ?
itemDelegate.ListView.isCurrentItem ? root.style.interactionHover itemDelegate.ListView.isCurrentItem ? root.style.interactionHover
: root.style.background.hover : root.style.background.hover
: "transparent" : "transparent"
@@ -124,7 +118,7 @@ ListView {
function onClicked() { function onClicked() {
root.model.currentIndex = itemDelegate.index root.model.currentIndex = itemDelegate.index
root.currentIndex = itemDelegate.index root.currentIndex = itemDelegate.index
dialog.popup(mouseArea) dialog.show(mouseArea)
} }
} }
} }

View File

@@ -19,8 +19,7 @@ Item {
objectName: "__mainSrollView" objectName: "__mainSrollView"
// Called also from C++ to close context menu on focus out // Called also from C++ to close context menu on focus out
function closeContextMenu() function closeContextMenu() {
{
materialsView.closeContextMenu() materialsView.closeContextMenu()
texturesView.closeContextMenu() texturesView.closeContextMenu()
environmentsView.closeContextMenu() environmentsView.closeContextMenu()
@@ -29,9 +28,43 @@ Item {
} }
// Called from C++ // Called from C++
function clearSearchFilter() function clearSearchFilter() {
{ searchBox.clear()
searchBox.clear(); }
property int numColumns: 4
property real thumbnailSize: 100
readonly property int minThumbSize: 100
readonly property int maxThumbSize: 150
function responsiveResize(width: int, height: int) {
width -= 2 * StudioTheme.Values.sectionPadding
let numColumns = Math.floor(width / root.minThumbSize)
let remainder = width % root.minThumbSize
let space = (numColumns - 1) * StudioTheme.Values.sectionGridSpacing
if (remainder < space)
numColumns -= 1
if (numColumns < 1)
return
let maxItems = Math.max(materialsView.count,
texturesView.count,
environmentsView.count,
effectsView.count)
if (numColumns > maxItems)
numColumns = maxItems
let rest = width - (numColumns * root.minThumbSize)
- ((numColumns - 1) * StudioTheme.Values.sectionGridSpacing)
root.thumbnailSize = Math.min(root.minThumbSize + (rest / numColumns),
root.maxThumbSize)
root.numColumns = numColumns
} }
Column { Column {
@@ -46,11 +79,11 @@ Item {
Column { Column {
anchors.fill: parent anchors.fill: parent
anchors.topMargin: 6 anchors.topMargin: StudioTheme.Values.toolbarVerticalMargin
anchors.bottomMargin: 6 anchors.bottomMargin: StudioTheme.Values.toolbarVerticalMargin
anchors.leftMargin: 10 anchors.leftMargin: StudioTheme.Values.toolbarHorizontalMargin
anchors.rightMargin: 10 anchors.rightMargin: StudioTheme.Values.toolbarHorizontalMargin
spacing: 12 spacing: StudioTheme.Values.toolbarColumnSpacing
StudioControls.SearchBox { StudioControls.SearchBox {
id: searchBox id: searchBox
@@ -94,16 +127,24 @@ Item {
} }
StackLayout { StackLayout {
id: stackLayout
width: root.width width: root.width
height: root.height - y height: root.height - y
currentIndex: tabBar.currIndex currentIndex: tabBar.currIndex
onWidthChanged: root.responsiveResize(stackLayout.width, stackLayout.height)
ContentLibraryMaterialsView { ContentLibraryMaterialsView {
id: materialsView id: materialsView
adsFocus: root.adsFocus adsFocus: root.adsFocus
width: root.width width: root.width
cellWidth: root.thumbnailSize
cellHeight: root.thumbnailSize + 20
numColumns: root.numColumns
hideHorizontalScrollBar: true
searchBox: searchBox searchBox: searchBox
onUnimport: (bundleMat) => { onUnimport: (bundleMat) => {
@@ -111,6 +152,8 @@ Item {
confirmUnimportDialog.targetBundleType = "material" confirmUnimportDialog.targetBundleType = "material"
confirmUnimportDialog.open() confirmUnimportDialog.open()
} }
onCountChanged: root.responsiveResize(stackLayout.width, stackLayout.height)
} }
ContentLibraryTexturesView { ContentLibraryTexturesView {
@@ -118,10 +161,18 @@ Item {
adsFocus: root.adsFocus adsFocus: root.adsFocus
width: root.width width: root.width
cellWidth: root.thumbnailSize
cellHeight: root.thumbnailSize
numColumns: root.numColumns
hideHorizontalScrollBar: true
model: ContentLibraryBackend.texturesModel model: ContentLibraryBackend.texturesModel
sectionCategory: "ContentLib_Tex" sectionCategory: "ContentLib_Tex"
searchBox: searchBox searchBox: searchBox
onCountChanged: root.responsiveResize(stackLayout.width, stackLayout.height)
} }
ContentLibraryTexturesView { ContentLibraryTexturesView {
@@ -129,10 +180,18 @@ Item {
adsFocus: root.adsFocus adsFocus: root.adsFocus
width: root.width width: root.width
cellWidth: root.thumbnailSize
cellHeight: root.thumbnailSize
numColumns: root.numColumns
hideHorizontalScrollBar: true
model: ContentLibraryBackend.environmentsModel model: ContentLibraryBackend.environmentsModel
sectionCategory: "ContentLib_Env" sectionCategory: "ContentLib_Env"
searchBox: searchBox searchBox: searchBox
onCountChanged: root.responsiveResize(stackLayout.width, stackLayout.height)
} }
ContentLibraryEffectsView { ContentLibraryEffectsView {
@@ -141,6 +200,11 @@ Item {
adsFocus: root.adsFocus adsFocus: root.adsFocus
width: root.width width: root.width
cellWidth: root.thumbnailSize
cellHeight: root.thumbnailSize + 20
numColumns: root.numColumns
hideHorizontalScrollBar: true
searchBox: searchBox searchBox: searchBox
onUnimport: (bundleItem) => { onUnimport: (bundleItem) => {
@@ -148,6 +212,8 @@ Item {
confirmUnimportDialog.targetBundleType = "effect" confirmUnimportDialog.targetBundleType = "effect"
confirmUnimportDialog.open() confirmUnimportDialog.open()
} }
onCountChanged: root.responsiveResize(stackLayout.width, stackLayout.height)
} }
} }
} }

View File

@@ -36,12 +36,10 @@ Item {
anchors.fill: parent anchors.fill: parent
spacing: 1 spacing: 1
Item { width: 1; height: 5 } // spacer
Image { Image {
id: img id: img
width: root.width - 10 width: root.width
height: img.width height: img.width
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
source: modelData.bundleItemIcon source: modelData.bundleItemIcon
@@ -74,16 +72,13 @@ Item {
} }
Text { Text {
text: modelData.bundleItemName
width: img.width width: img.width
clip: true
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: TextInput.AlignHCenter horizontalAlignment: TextInput.AlignHCenter
text: modelData.bundleItemName
elide: Text.ElideRight
font.pixelSize: StudioTheme.Values.myFontSize font.pixelSize: StudioTheme.Values.myFontSize
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
} }
} // Column } // Column

View File

@@ -14,20 +14,28 @@ HelperWidgets.ScrollView {
interactive: !ctxMenu.opened && !ContentLibraryBackend.rootView.isDragging interactive: !ctxMenu.opened && !ContentLibraryBackend.rootView.isDragging
&& !HelperWidgets.Controller.contextMenuOpened && !HelperWidgets.Controller.contextMenuOpened
readonly property int cellWidth: 100 property real cellWidth: 100
readonly property int cellHeight: 120 property real cellHeight: 120
property int numColumns: 4
property int count: 0
function assignMaxCount() {
let c = 0
for (let i = 0; i < categoryRepeater.count; ++i)
c = Math.max(c, categoryRepeater.itemAt(i)?.count ?? 0)
root.count = c
}
required property var searchBox required property var searchBox
signal unimport(var bundleItem); signal unimport(var bundleItem);
function closeContextMenu() function closeContextMenu() {
{
ctxMenu.close() ctxMenu.close()
} }
function expandVisibleSections() function expandVisibleSections() {
{
for (let i = 0; i < categoryRepeater.count; ++i) { for (let i = 0; i < categoryRepeater.count; ++i) {
let cat = categoryRepeater.itemAt(i) let cat = categoryRepeater.itemAt(i)
if (cat.visible && !cat.expanded) if (cat.visible && !cat.expanded)
@@ -48,10 +56,15 @@ HelperWidgets.ScrollView {
model: ContentLibraryBackend.effectsModel model: ContentLibraryBackend.effectsModel
delegate: HelperWidgets.Section { delegate: HelperWidgets.Section {
id: section
width: root.width width: root.width
leftPadding: StudioTheme.Values.sectionPadding
rightPadding: StudioTheme.Values.sectionPadding
topPadding: StudioTheme.Values.sectionPadding
bottomPadding: StudioTheme.Values.sectionPadding
caption: bundleCategoryName caption: bundleCategoryName
addTopPadding: false
sectionBackgroundColor: "transparent"
visible: bundleCategoryVisible && !ContentLibraryBackend.effectsModel.isEmpty visible: bundleCategoryVisible && !ContentLibraryBackend.effectsModel.isEmpty
expanded: bundleCategoryExpanded expanded: bundleCategoryExpanded
expandOnClick: false expandOnClick: false
@@ -65,14 +78,17 @@ HelperWidgets.ScrollView {
bundleCategoryExpanded = true bundleCategoryExpanded = true
} }
property alias count: repeater.count
onCountChanged: root.assignMaxCount()
Grid { Grid {
width: root.width width: section.width - section.leftPadding - section.rightPadding
leftPadding: 5 spacing: StudioTheme.Values.sectionGridSpacing
rightPadding: 5 columns: root.numColumns
bottomPadding: 5
columns: root.width / root.cellWidth
Repeater { Repeater {
id: repeater
model: bundleCategoryItems model: bundleCategoryItems
delegate: ContentLibraryEffect { delegate: ContentLibraryEffect {
@@ -81,6 +97,8 @@ HelperWidgets.ScrollView {
onShowContextMenu: ctxMenu.popupMenu(modelData) onShowContextMenu: ctxMenu.popupMenu(modelData)
} }
onCountChanged: root.assignMaxCount()
} }
} }
} }

View File

@@ -1,16 +1,15 @@
// Copyright (C) 2022 The Qt Company Ltd. // Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick 2.15 import QtQuick
import QtQuick.Layouts 1.15
import QtQuickDesignerTheme 1.0
import HelperWidgets 2.0
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts
import QtQuickDesignerTheme
import HelperWidgets
import StudioTheme as StudioTheme
import StudioTheme 1.0 as StudioTheme
import ContentLibraryBackend import ContentLibraryBackend
import WebFetcher
import WebFetcher 1.0
Item { Item {
id: root id: root
@@ -45,8 +44,6 @@ Item {
anchors.fill: parent anchors.fill: parent
spacing: 1 spacing: 1
Item { width: 1; height: 5 } // spacer
DownloadPane { DownloadPane {
id: downloadPane id: downloadPane
width: root.width - 10 width: root.width - 10
@@ -59,7 +56,7 @@ Item {
Image { Image {
id: img id: img
width: root.width - 10 width: root.width
height: img.width height: img.width
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
source: modelData.bundleMaterialIcon source: modelData.bundleMaterialIcon
@@ -108,7 +105,7 @@ Item {
onClicked: { onClicked: {
ContentLibraryBackend.materialsModel.addToProject(modelData) ContentLibraryBackend.materialsModel.addToProject(modelData)
} }
} // IconButton }
IconButton { IconButton {
id: downloadIcon id: downloadIcon
@@ -154,29 +151,22 @@ Item {
root.downloadState = "" root.downloadState = ""
downloader.start() downloader.start()
} }
} // IconButton }
} // Image }
TextInput { Text {
id: matName id: matName
text: modelData.bundleMaterialName
width: img.width width: img.width
clip: true
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: TextInput.AlignHCenter horizontalAlignment: TextInput.AlignHCenter
text: modelData.bundleMaterialName
elide: Text.ElideRight
font.pixelSize: StudioTheme.Values.myFontSize font.pixelSize: StudioTheme.Values.myFontSize
readOnly: true
selectByMouse: !matName.readOnly
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
selectionColor: StudioTheme.Values.themeTextSelectionColor
selectedTextColor: StudioTheme.Values.themeTextSelectedTextColor
} }
} // Column }
Timer { Timer {
id: delayedFinish id: delayedFinish
@@ -223,6 +213,6 @@ Item {
probeUrl: false probeUrl: false
downloadEnabled: true downloadEnabled: true
targetFilePath: downloader.nextTargetPath targetFilePath: downloader.nextTargetPath
} // FileDownloader }
} // MultiFileDownloader }
} }

View File

@@ -14,8 +14,18 @@ HelperWidgets.ScrollView {
interactive: !ctxMenu.opened && !ContentLibraryBackend.rootView.isDragging interactive: !ctxMenu.opened && !ContentLibraryBackend.rootView.isDragging
&& !HelperWidgets.Controller.contextMenuOpened && !HelperWidgets.Controller.contextMenuOpened
readonly property int cellWidth: 100 property real cellWidth: 100
readonly property int cellHeight: 120 property real cellHeight: 120
property int numColumns: 4
property int count: 0
function assignMaxCount() {
let c = 0
for (let i = 0; i < categoryRepeater.count; ++i)
c = Math.max(c, categoryRepeater.itemAt(i)?.count ?? 0)
root.count = c
}
property var currMaterialItem: null property var currMaterialItem: null
property var rootItem: null property var rootItem: null
@@ -23,15 +33,13 @@ HelperWidgets.ScrollView {
required property var searchBox required property var searchBox
signal unimport(var bundleMat); signal unimport(var bundleMat)
function closeContextMenu() function closeContextMenu() {
{
ctxMenu.close() ctxMenu.close()
} }
function expandVisibleSections() function expandVisibleSections() {
{
for (let i = 0; i < categoryRepeater.count; ++i) { for (let i = 0; i < categoryRepeater.count; ++i) {
let cat = categoryRepeater.itemAt(i) let cat = categoryRepeater.itemAt(i)
if (cat.visible && !cat.expanded) if (cat.visible && !cat.expanded)
@@ -56,10 +64,15 @@ HelperWidgets.ScrollView {
model: materialsModel model: materialsModel
delegate: HelperWidgets.Section { delegate: HelperWidgets.Section {
id: section
width: root.width width: root.width
leftPadding: StudioTheme.Values.sectionPadding
rightPadding: StudioTheme.Values.sectionPadding
topPadding: StudioTheme.Values.sectionPadding
bottomPadding: StudioTheme.Values.sectionPadding
caption: bundleCategoryName caption: bundleCategoryName
addTopPadding: false
sectionBackgroundColor: "transparent"
visible: bundleCategoryVisible && !materialsModel.isEmpty visible: bundleCategoryVisible && !materialsModel.isEmpty
expanded: bundleCategoryExpanded expanded: bundleCategoryExpanded
expandOnClick: false expandOnClick: false
@@ -73,14 +86,17 @@ HelperWidgets.ScrollView {
bundleCategoryExpanded = true bundleCategoryExpanded = true
} }
property alias count: repeater.count
onCountChanged: root.assignMaxCount()
Grid { Grid {
width: root.width width: section.width - section.leftPadding - section.rightPadding
leftPadding: 5 spacing: StudioTheme.Values.sectionGridSpacing
rightPadding: 5 columns: root.numColumns
bottomPadding: 5
columns: root.width / root.cellWidth
Repeater { Repeater {
id: repeater
model: bundleCategoryMaterials model: bundleCategoryMaterials
delegate: ContentLibraryMaterial { delegate: ContentLibraryMaterial {
@@ -89,6 +105,8 @@ HelperWidgets.ScrollView {
onShowContextMenu: ctxMenu.popupMenu(modelData) onShowContextMenu: ctxMenu.popupMenu(modelData)
} }
onCountChanged: root.assignMaxCount()
} }
} }
} }

View File

@@ -1,16 +1,15 @@
// Copyright (C) 2022 The Qt Company Ltd. // Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick 2.15 import QtQuick
import QtQuick.Layouts 1.15
import QtQuickDesignerTheme 1.0
import HelperWidgets 2.0
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts
import QtQuickDesignerTheme
import HelperWidgets
import StudioTheme as StudioTheme
import StudioTheme 1.0 as StudioTheme
import WebFetcher 1.0
import ContentLibraryBackend import ContentLibraryBackend
import WebFetcher
Item { Item {
id: root id: root
@@ -30,8 +29,7 @@ Item {
signal showContextMenu() signal showContextMenu()
function statusText() function statusText() {
{
if (root.downloadState === "downloaded") if (root.downloadState === "downloaded")
return qsTr("Texture was already downloaded.") return qsTr("Texture was already downloaded.")
if (root.downloadState === "unavailable") if (root.downloadState === "unavailable")
@@ -42,16 +40,14 @@ Item {
return qsTr("Click to download the texture.") return qsTr("Click to download the texture.")
} }
function startDownload(message) function startDownload(message) {
{
if (root.downloadState !== "" && root.downloadState !== "failed") if (root.downloadState !== "" && root.downloadState !== "failed")
return return
root._startDownload(textureDownloader, message) root._startDownload(textureDownloader, message)
} }
function updateTexture() function updateTexture() {
{
if (root.downloadState !== "downloaded") if (root.downloadState !== "downloaded")
return return
@@ -59,8 +55,7 @@ Item {
root._startDownload(textureDownloader, qsTr("Updating...")) root._startDownload(textureDownloader, qsTr("Updating..."))
} }
function _startDownload(downloader, message) function _startDownload(downloader, message) {
{
progressBar.visible = true progressBar.visible = true
tooltip.visible = false tooltip.visible = false
root.progressText = message root.progressText = message
@@ -144,8 +139,8 @@ Item {
font.pixelSize: 12 font.pixelSize: 12
} }
} }
} // TextureProgressBar }
} // Rectangle }
Image { Image {
id: image id: image
@@ -197,7 +192,7 @@ Item {
onClicked: { onClicked: {
root.startDownload(qsTr("Downloading...")) root.startDownload(qsTr("Downloading..."))
} }
} // IconButton }
IconButton { IconButton {
id: updateButton id: updateButton
@@ -233,7 +228,7 @@ Item {
scale: updateButton.containsMouse ? 1.2 : 1 scale: updateButton.containsMouse ? 1.2 : 1
} }
} // Update IconButton }
Rectangle { Rectangle {
id: isNewFlag id: isNewFlag
@@ -282,7 +277,7 @@ Item {
visible: false visible: false
} }
} }
} // Image }
FileDownloader { FileDownloader {
id: textureDownloader id: textureDownloader

View File

@@ -14,8 +14,18 @@ HelperWidgets.ScrollView {
interactive: !ctxMenu.opened && !ContentLibraryBackend.rootView.isDragging interactive: !ctxMenu.opened && !ContentLibraryBackend.rootView.isDragging
&& !HelperWidgets.Controller.contextMenuOpened && !HelperWidgets.Controller.contextMenuOpened
readonly property int cellWidth: 100 property int cellWidth: 100
readonly property int cellHeight: 100 property int cellHeight: 100
property int numColumns: 4
property int count: 0
function assignMaxCount() {
let c = 0
for (let i = 0; i < categoryRepeater.count; ++i)
c = Math.max(c, categoryRepeater.itemAt(i)?.count ?? 0)
root.count = c
}
property var currMaterialItem: null property var currMaterialItem: null
property var rootItem: null property var rootItem: null
@@ -24,21 +34,20 @@ HelperWidgets.ScrollView {
required property var model required property var model
required property string sectionCategory required property string sectionCategory
signal unimport(var bundleMat); signal unimport(var bundleMat)
function closeContextMenu() function closeContextMenu() {
{
ctxMenu.close() ctxMenu.close()
} }
function expandVisibleSections() function expandVisibleSections() {
{
for (let i = 0; i < categoryRepeater.count; ++i) { for (let i = 0; i < categoryRepeater.count; ++i) {
let cat = categoryRepeater.itemAt(i) let cat = categoryRepeater.itemAt(i)
if (cat.visible && !cat.expanded) if (cat.visible && !cat.expanded)
cat.expandSection() cat.expandSection()
} }
} }
Column { Column {
ContentLibraryTextureContextMenu { ContentLibraryTextureContextMenu {
id: ctxMenu id: ctxMenu
@@ -52,10 +61,15 @@ HelperWidgets.ScrollView {
model: root.model model: root.model
delegate: HelperWidgets.Section { delegate: HelperWidgets.Section {
id: section
width: root.width width: root.width
leftPadding: StudioTheme.Values.sectionPadding
rightPadding: StudioTheme.Values.sectionPadding
topPadding: StudioTheme.Values.sectionPadding
bottomPadding: StudioTheme.Values.sectionPadding
caption: bundleCategoryName caption: bundleCategoryName
addTopPadding: false
sectionBackgroundColor: "transparent"
visible: bundleCategoryVisible && !root.model.isEmpty visible: bundleCategoryVisible && !root.model.isEmpty
expanded: bundleCategoryExpanded expanded: bundleCategoryExpanded
expandOnClick: false expandOnClick: false
@@ -69,15 +83,17 @@ HelperWidgets.ScrollView {
bundleCategoryExpanded = true bundleCategoryExpanded = true
} }
property alias count: repeater.count
onCountChanged: root.assignMaxCount()
Grid { Grid {
width: root.width width: section.width - section.leftPadding - section.rightPadding
leftPadding: 5 spacing: StudioTheme.Values.sectionGridSpacing
rightPadding: 5 columns: root.numColumns
bottomPadding: 5
spacing: 5
columns: root.width / root.cellWidth
Repeater { Repeater {
id: repeater
model: bundleCategoryTextures model: bundleCategoryTextures
delegate: ContentLibraryTexture { delegate: ContentLibraryTexture {
@@ -86,6 +102,8 @@ HelperWidgets.ScrollView {
onShowContextMenu: ctxMenu.popupMenu(modelData) onShowContextMenu: ctxMenu.popupMenu(modelData)
} }
onCountChanged: root.assignMaxCount()
} }
} }
} }

View File

@@ -69,6 +69,9 @@
"EnterComponentIcon": { "EnterComponentIcon": {
"iconName": "editComponent_small" "iconName": "editComponent_small"
}, },
"JumpToCodeIcon": {
"iconName": "jumpToCode_small"
},
"EventListIcon": { "EventListIcon": {
"iconName": "events_small" "iconName": "events_small"
}, },
@@ -218,6 +221,9 @@
"EditColorIcon": { "EditColorIcon": {
"iconName": "colorSelection_medium" "iconName": "colorSelection_medium"
}, },
"BakeLightIcon": {
"iconName": "bakeLights_medium"
},
"EditLightIcon": { "EditLightIcon": {
"Off": { "Off": {
"iconName": "editLightOff_medium" "iconName": "editLightOff_medium"
@@ -264,6 +270,9 @@
"SnappingConfIcon": { "SnappingConfIcon": {
"iconName": "snapping_conf_medium" "iconName": "snapping_conf_medium"
}, },
"SplitViewIcon": {
"iconName": "splitScreen_medium"
},
"ToggleGroupIcon": { "ToggleGroupIcon": {
"Off": { "Off": {
"iconName": "selectOutline_medium" "iconName": "selectOutline_medium"

View File

@@ -12,14 +12,7 @@ import EffectMakerBackend
HelperWidgets.Section { HelperWidgets.Section {
id: root id: root
// model properties caption: nodeName
required property string nodeName
required property bool nodeEnabled
required property var nodeUniformsModel
required property int index
caption: root.nodeName
category: "EffectMaker" category: "EffectMaker"
draggable: true draggable: true
@@ -32,18 +25,18 @@ HelperWidgets.Section {
} }
showEyeButton: true showEyeButton: true
eyeEnabled: root.nodeEnabled eyeEnabled: nodeEnabled
eyeButtonToolTip: qsTr("Enable/Disable Node") eyeButtonToolTip: qsTr("Enable/Disable Node")
onEyeButtonClicked: { onEyeButtonClicked: {
root.nodeEnabled = root.eyeEnabled nodeEnabled = root.eyeEnabled
} }
Column { Column {
spacing: 10 spacing: 10
Repeater { Repeater {
model: root.nodeUniformsModel model: nodeUniformsModel
EffectCompositionNodeUniform { EffectCompositionNodeUniform {
width: root.width width: root.width

View File

@@ -28,7 +28,7 @@ Item {
valueLoader.source = "ValueBool.qml" valueLoader.source = "ValueBool.qml"
else if (uniformType === "color") else if (uniformType === "color")
valueLoader.source = "ValueColor.qml" valueLoader.source = "ValueColor.qml"
else if (uniformType === "image") else if (uniformType === "sampler2D")
valueLoader.source = "ValueImage.qml" valueLoader.source = "ValueImage.qml"
else if (uniformType === "define") else if (uniformType === "define")
valueLoader.source = "ValueDefine.qml" valueLoader.source = "ValueDefine.qml"
@@ -50,6 +50,7 @@ Item {
Layout.maximumWidth: 140 Layout.maximumWidth: 140
Layout.minimumWidth: 140 Layout.minimumWidth: 140
Layout.preferredWidth: 140 Layout.preferredWidth: 140
elide: Text.ElideRight
HelperWidgets.ToolTipArea { HelperWidgets.ToolTipArea {
anchors.fill: parent anchors.fill: parent

View File

@@ -14,6 +14,18 @@ Item {
property var secsY: [] property var secsY: []
property int moveFromIdx: 0 property int moveFromIdx: 0
property int moveToIdx: 0 property int moveToIdx: 0
property bool previewAnimationRunning: false
SaveDialog {
id: saveDialog
compositionName: EffectMakerBackend.effectMakerModel.currentComposition
anchors.centerIn: parent
onAccepted: {
let name = saveDialog.compositionName
EffectMakerBackend.effectMakerModel.exportComposition(name)
EffectMakerBackend.effectMakerModel.exportResources(name)
}
}
Column { Column {
id: col id: col
@@ -21,11 +33,17 @@ Item {
spacing: 1 spacing: 1
EffectMakerTopBar { EffectMakerTopBar {
onSaveClicked: saveDialog.open()
} }
EffectMakerPreview { EffectMakerPreview {
mainRoot: root mainRoot: root
FrameAnimation {
id: previewFrameTimer
running: true
paused: !previewAnimationRunning
}
} }
Rectangle { Rectangle {
@@ -47,11 +65,14 @@ Item {
style: StudioTheme.Values.viewBarButtonStyle style: StudioTheme.Values.viewBarButtonStyle
buttonIcon: StudioTheme.Constants.code buttonIcon: StudioTheme.Constants.code
tooltip: qsTr("Open Shader in Code Editor") tooltip: qsTr("Open Shader in Code Editor")
visible: false // TODO: to be implemented
onClicked: {} // TODO onClicked: {} // TODO
} }
} }
Component.onCompleted: HelperWidgets.Controller.mainScrollView = scrollView
HelperWidgets.ScrollView { HelperWidgets.ScrollView {
id: scrollView id: scrollView

View File

@@ -2,7 +2,6 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick import QtQuick
import QtQuick.Layouts
import QtQuickDesignerTheme import QtQuickDesignerTheme
import HelperWidgets as HelperWidgets import HelperWidgets as HelperWidgets
import StudioControls as StudioControls import StudioControls as StudioControls
@@ -12,24 +11,58 @@ import EffectMakerBackend
Column { Column {
id: root id: root
property real animatedTime: previewFrameTimer.elapsedTime
property int animatedFrame: previewFrameTimer.currentFrame
property bool timeRunning: previewAnimationRunning
width: parent.width width: parent.width
required property Item mainRoot required property Item mainRoot
property var effectMakerModel: EffectMakerBackend.effectMakerModel property var effectMakerModel: EffectMakerBackend.effectMakerModel
property alias source: source property alias source: source
// The delay in ms to wait until updating the effect // The delay in ms to wait until updating the effect
readonly property int updateDelay: 200 readonly property int updateDelay: 100
// Create a dummy parent to host the effect qml object
function createNewComponent() {
// If we have a working effect, do not show preview image as it shows through
// transparent parts of the final image
source.visible = false;
var oldComponent = componentParent.children[0];
if (oldComponent)
oldComponent.destroy();
try {
const newObject = Qt.createQmlObject(
effectMakerModel.qmlComponentString,
componentParent,
""
);
effectMakerModel.resetEffectError(0);
} catch (error) {
let errorString = "QML: ERROR: ";
let errorLine = -1;
if (error.qmlErrors.length > 0) {
// Show the first QML error
let e = error.qmlErrors[0];
errorString += e.lineNumber + ": " + e.message;
errorLine = e.lineNumber;
}
effectMakerModel.setEffectError(errorString, 0, errorLine);
source.visible = true;
}
}
Rectangle { // toolbar Rectangle { // toolbar
width: parent.width width: parent.width
height: StudioTheme.Values.toolbarHeight height: StudioTheme.Values.toolbarHeight
color: StudioTheme.Values.themeToolbarBackground color: StudioTheme.Values.themeToolbarBackground
RowLayout { Row {
anchors.fill: parent
spacing: 5 spacing: 5
anchors.rightMargin: 5
anchors.leftMargin: 5 anchors.leftMargin: 5
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
PreviewImagesComboBox { PreviewImagesComboBox {
id: imagesComboBox id: imagesComboBox
@@ -37,9 +70,19 @@ Column {
mainRoot: root.mainRoot mainRoot: root.mainRoot
} }
Item { StudioControls.ColorEditor {
Layout.fillWidth: true id: colorEditor
actionIndicatorVisible: false
showHexTextField: false
color: "#dddddd"
} }
}
Row {
spacing: 5
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
HelperWidgets.AbstractButton { HelperWidgets.AbstractButton {
enabled: sourceImage.scale > .4 enabled: sourceImage.scale > .4
@@ -73,20 +116,24 @@ Column {
sourceImage.scale = 1 sourceImage.scale = 1
} }
} }
}
Item { Row {
Layout.fillWidth: true spacing: 5
} anchors.rightMargin: 5
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
Column { Column {
Text { Text {
text: "0.000s" text: animatedTime >= 100
? animatedTime.toFixed(1) + " s" : animatedTime.toFixed(3) + " s"
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
font.pixelSize: 10 font.pixelSize: 10
} }
Text { Text {
text: "0000000" text: (animatedFrame).toString().padStart(6, '0')
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
font.pixelSize: 10 font.pixelSize: 10
} }
@@ -97,15 +144,20 @@ Column {
buttonIcon: StudioTheme.Constants.toStartFrame_medium buttonIcon: StudioTheme.Constants.toStartFrame_medium
tooltip: qsTr("Restart Animation") tooltip: qsTr("Restart Animation")
onClicked: {} // TODO onClicked: {
previewFrameTimer.reset()
}
} }
HelperWidgets.AbstractButton { HelperWidgets.AbstractButton {
style: StudioTheme.Values.viewBarButtonStyle style: StudioTheme.Values.viewBarButtonStyle
buttonIcon: StudioTheme.Constants.topToolbar_runProject buttonIcon: previewAnimationRunning ? StudioTheme.Constants.pause_medium
: StudioTheme.Constants.playOutline_medium
tooltip: qsTr("Play Animation") tooltip: qsTr("Play Animation")
onClicked: {} // TODO onClicked: {
previewAnimationRunning = !previewAnimationRunning
}
} }
} }
} }
@@ -113,7 +165,7 @@ Column {
Rectangle { // preview image Rectangle { // preview image
id: preview id: preview
color: "#dddddd" color: colorEditor.color
width: parent.width width: parent.width
height: 200 height: 200
clip: true clip: true
@@ -146,54 +198,27 @@ Column {
id: componentParent id: componentParent
width: source.width width: source.width
height: source.height height: source.height
anchors.centerIn: parent anchors.centerIn: parent
scale: 1 //TODO should come from toolbar
// Cache the layer. This way heavy shaders rendering doesn't // Cache the layer. This way heavy shaders rendering doesn't
// slow down code editing & rest of the UI. // slow down code editing & rest of the UI.
layer.enabled: true layer.enabled: true
layer.smooth: true layer.smooth: true
} }
// Create a dummy parent to host the effect qml object
function createNewComponent() {
var oldComponent = componentParent.children[0];
if (oldComponent)
oldComponent.destroy();
try {
const newObject = Qt.createQmlObject(
effectMakerModel.qmlComponentString,
componentParent,
""
);
effectMakerModel.resetEffectError(0);
} catch (error) {
let errorString = "QML: ERROR: ";
let errorLine = -1;
if (error.qmlErrors.length > 0) {
// Show the first QML error
let e = error.qmlErrors[0];
errorString += e.lineNumber + ": " + e.message;
errorLine = e.lineNumber;
}
effectMakerModel.setEffectError(errorString, 0, errorLine);
}
}
Connections { Connections {
target: effectMakerModel target: effectMakerModel
function onShadersBaked() { function onShadersBaked() {
console.log("Shaders Baked!") console.log("Shaders Baked!")
//updateTimer.restart(); // Disable for now updateTimer.restart()
} }
} }
Timer { Timer {
id: updateTimer id: updateTimer
interval: updateDelay; interval: updateDelay
onTriggered: { onTriggered: {
effectMakerModel.updateQmlComponent(); effectMakerModel.updateQmlComponent()
createNewComponent(); createNewComponent()
} }
} }
} }

View File

@@ -15,15 +15,15 @@ Rectangle {
height: StudioTheme.Values.toolbarHeight height: StudioTheme.Values.toolbarHeight
color: StudioTheme.Values.themeToolbarBackground color: StudioTheme.Values.themeToolbarBackground
signal saveClicked
HelperWidgets.Button { HelperWidgets.Button {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
x: 5 x: 5
text: qsTr("Save in Library") text: qsTr("Save in Library")
onClicked: { onClicked: root.saveClicked()
// TODO
}
} }
HelperWidgets.AbstractButton { HelperWidgets.AbstractButton {

View File

@@ -47,7 +47,7 @@ StudioControls.ComboBox {
width: row.width + 2 // 2: scrollView left and right 1px margins 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 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 flags: Qt.Dialog | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint
onActiveFocusItemChanged: { onActiveFocusItemChanged: {
if (!window.activeFocusItem && !root.indicator.hover && root.popup.opened) if (!window.activeFocusItem && !root.indicator.hover && root.popup.opened)

View File

@@ -67,7 +67,7 @@ StudioControls.ComboBox {
width: col.width + 2 // 2: scrollView left and right 1px margins 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 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 flags: Qt.Dialog | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint
onActiveFocusItemChanged: { onActiveFocusItemChanged: {
if (!window.activeFocusItem && !root.indicator.hover && root.popup.opened) if (!window.activeFocusItem && !root.indicator.hover && root.popup.opened)

View File

@@ -0,0 +1,97 @@
// 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 AssetsLibraryBackend
StudioControls.Dialog {
id: root
title: qsTr("Save Effect")
closePolicy: Popup.CloseOnEscape
modal: true
implicitWidth: 250
implicitHeight: 160
property string compositionName: ""
onOpened: {
nameText.text = compositionName //TODO: Generate unique name
emptyText.text = ""
nameText.forceActiveFocus()
}
contentItem: Item {
Column {
spacing: 2
Row {
id: row
Text {
text: qsTr("Effect name: ")
anchors.verticalCenter: parent.verticalCenter
color: StudioTheme.Values.themeTextColor
}
StudioControls.TextField {
id: nameText
actionIndicator.visible: false
translationIndicator.visible: false
onTextChanged: {
let errMsg = ""
if (/[^A-Za-z0-9_]+/.test(text))
errMsg = qsTr("Name contains invalid characters.")
else if (!/^[A-Z]/.test(text))
errMsg = qsTr("Name must start with a capital letter")
else if (text.length < 3)
errMsg = qsTr("Name must have at least 3 characters")
else if (/\s/.test(text))
errMsg = qsTr("Name cannot contain white space")
emptyText.text = errMsg
btnSave.enabled = errMsg.length === 0
}
Keys.onEnterPressed: btnSave.onClicked()
Keys.onReturnPressed: btnSave.onClicked()
Keys.onEscapePressed: root.reject()
}
}
Text {
id: emptyText
color: StudioTheme.Values.themeError
anchors.right: row.right
}
}
Row {
anchors.right: parent.right
anchors.bottom: parent.bottom
spacing: 2
HelperWidgets.Button {
id: btnSave
text: qsTr("Save")
enabled: nameText.text !== ""
onClicked: {
root.compositionName = nameText.text
root.accept() //TODO: Check if name is unique
}
}
HelperWidgets.Button {
text: qsTr("Cancel")
onClicked: root.reject()
}
}
}
}

View File

@@ -3,7 +3,7 @@
import QtQuick import QtQuick
import QtQuickDesignerTheme import QtQuickDesignerTheme
import HelperWidgets as HelperWidgets import StudioControls as StudioControls
import StudioTheme 1.0 as StudioTheme import StudioTheme 1.0 as StudioTheme
import EffectMakerBackend import EffectMakerBackend
@@ -13,11 +13,11 @@ Row {
width: parent.width width: parent.width
spacing: 5 spacing: 5
HelperWidgets.ColorEditor { StudioControls.ColorEditor {
backendValue: uniformBackendValue actionIndicatorVisible: false
showExtendedFunctionButton: false Component.onCompleted: color = uniformValue
onValueChanged: uniformValue = convertColorToString(color) onColorChanged: uniformValue = color
} }
} }

View File

@@ -55,7 +55,7 @@ Rectangle {
id: column id: column
width: root.width width: root.width
Section { HelperWidgets.Section {
id: trackingSection id: trackingSection
caption: qsTr("Tracking") caption: qsTr("Tracking")
anchors.left: parent.left anchors.left: parent.left
@@ -167,7 +167,7 @@ Rectangle {
} }
} }
Section { HelperWidgets.Section {
id: predefinedSection id: predefinedSection
caption: qsTr("Predefined Categories") caption: qsTr("Predefined Categories")
anchors.left: parent.left anchors.left: parent.left
@@ -227,7 +227,7 @@ Rectangle {
} }
} }
Item { width: 1; height: 4} Item { width: 1; height: 4 }
Repeater { Repeater {
model: insightModel model: insightModel
@@ -268,7 +268,7 @@ Rectangle {
} }
} }
Section { HelperWidgets.Section {
id: customSection id: customSection
caption: qsTr("Custom Categories") caption: qsTr("Custom Categories")
anchors.left: parent.left anchors.left: parent.left
@@ -325,7 +325,7 @@ Rectangle {
} }
} }
Item { width: 1; height: 4} Item { width: 1; height: 4 }
Repeater { Repeater {
id: customRepeater id: customRepeater
@@ -387,7 +387,7 @@ Rectangle {
} }
} }
Item { width: 1; height: 4} Item { width: 1; height: 4 }
Row { Row {
spacing: StudioTheme.Values.checkBoxSpacing spacing: StudioTheme.Values.checkBoxSpacing

View File

@@ -237,9 +237,7 @@ Item {
Repeater { Repeater {
model: ItemLibraryBackend.itemLibraryModel // to be set in Qml context model: ItemLibraryBackend.itemLibraryModel // to be set in Qml context
delegate: HelperWidgets.Section { delegate: HelperWidgets.Section {
width: itemsView.width - width: itemsView.width
(verticalScrollView.verticalScrollBarVisible
? verticalScrollView.verticalThickness : 0)
caption: importName caption: importName
visible: importVisible visible: importVisible
sectionHeight: 30 sectionHeight: 30
@@ -270,9 +268,7 @@ Item {
Repeater { Repeater {
model: categoryModel model: categoryModel
delegate: HelperWidgets.Section { delegate: HelperWidgets.Section {
width: itemsView.width - width: itemsView.width
(verticalScrollView.verticalScrollBarVisible
? verticalScrollView.verticalThickness : 0)
sectionBackgroundColor: "transparent" sectionBackgroundColor: "transparent"
showTopSeparator: index > 0 showTopSeparator: index > 0
hideHeader: categoryModel.rowCount() <= 1 hideHeader: categoryModel.rowCount() <= 1
@@ -351,9 +347,7 @@ Item {
Repeater { Repeater {
model: ItemLibraryBackend.itemLibraryModel // to be set in Qml context model: ItemLibraryBackend.itemLibraryModel // to be set in Qml context
delegate: HelperWidgets.Section { delegate: HelperWidgets.Section {
width: 265 - width: 265
(horizontalScrollView.verticalScrollBarVisible
? horizontalScrollView.verticalThickness : 0)
caption: importName caption: importName
visible: importVisible visible: importVisible
sectionHeight: 30 sectionHeight: 30
@@ -384,9 +378,7 @@ Item {
Repeater { Repeater {
model: categoryModel model: categoryModel
delegate: Rectangle { delegate: Rectangle {
width: 265 - width: 265
(horizontalScrollView.verticalScrollBarVisible
? horizontalScrollView.verticalThickness : 0)
height: 25 height: 25
visible: categoryVisible visible: categoryVisible
border.width: StudioTheme.Values.border border.width: StudioTheme.Values.border

View File

@@ -12,8 +12,8 @@ Item {
id: root id: root
focus: true focus: true
readonly property int cellWidth: 100 readonly property real cellWidth: root.thumbnailSize
readonly property int cellHeight: 120 readonly property real cellHeight: root.thumbnailSize + 20
readonly property bool enableUiElements: materialBrowserModel.hasMaterialLibrary readonly property bool enableUiElements: materialBrowserModel.hasMaterialLibrary
&& materialBrowserModel.hasQuick3DImport && materialBrowserModel.hasQuick3DImport
@@ -22,30 +22,60 @@ Item {
property var materialBrowserModel: MaterialBrowserBackend.materialBrowserModel property var materialBrowserModel: MaterialBrowserBackend.materialBrowserModel
property var materialBrowserTexturesModel: MaterialBrowserBackend.materialBrowserTexturesModel property var materialBrowserTexturesModel: MaterialBrowserBackend.materialBrowserTexturesModel
property int numColumns: 0
property real thumbnailSize: 100
readonly property int minThumbSize: 100
readonly property int maxThumbSize: 150
function responsiveResize(width: int, height: int) {
width -= 2 * StudioTheme.Values.sectionPadding
let numColumns = Math.floor(width / root.minThumbSize)
let remainder = width % root.minThumbSize
let space = (numColumns - 1) * StudioTheme.Values.sectionGridSpacing
if (remainder < space)
numColumns -= 1
if (numColumns < 1)
return
let maxItems = Math.max(texturesRepeater.count, materialRepeater.count)
if (numColumns > maxItems)
numColumns = maxItems
let rest = width - (numColumns * root.minThumbSize)
- ((numColumns - 1) * StudioTheme.Values.sectionGridSpacing)
root.thumbnailSize = Math.min(root.minThumbSize + (rest / numColumns),
root.maxThumbSize)
root.numColumns = numColumns
}
onWidthChanged: root.responsiveResize(root.width, root.height)
// Called also from C++ to close context menu on focus out // Called also from C++ to close context menu on focus out
function closeContextMenu() function closeContextMenu() {
{
ctxMenu.close() ctxMenu.close()
ctxMenuTextures.close() ctxMenuTextures.close()
HelperWidgets.Controller.closeContextMenu() HelperWidgets.Controller.closeContextMenu()
} }
// Called from C++ to refresh a preview material after it changes // Called from C++ to refresh a preview material after it changes
function refreshPreview(idx) function refreshPreview(idx) {
{
var item = materialRepeater.itemAt(idx); var item = materialRepeater.itemAt(idx);
if (item) if (item)
item.refreshPreview(); item.refreshPreview()
} }
// Called from C++ // Called from C++
function clearSearchFilter() function clearSearchFilter() {
{ searchBox.clear()
searchBox.clear();
} }
function nextVisibleItem(idx, count, itemModel) function nextVisibleItem(idx, count, itemModel) {
{
if (count === 0) if (count === 0)
return idx return idx
@@ -66,8 +96,7 @@ Item {
return newIdx return newIdx
} }
function visibleItemCount(itemModel) function visibleItemCount(itemModel) {
{
let curIdx = 0 let curIdx = 0
let count = 0 let count = 0
@@ -79,8 +108,7 @@ Item {
return count return count
} }
function rowIndexOfItem(idx, rowSize, itemModel) function rowIndexOfItem(idx, rowSize, itemModel) {
{
if (rowSize === 1) if (rowSize === 1)
return 1 return 1
@@ -98,8 +126,7 @@ Item {
return count % rowSize return count % rowSize
} }
function selectNextVisibleItem(delta) function selectNextVisibleItem(delta) {
{
if (searchBox.activeFocus) if (searchBox.activeFocus)
return return
@@ -112,36 +139,36 @@ Item {
if (delta < 0) { if (delta < 0) {
if (matSecFocused) { if (matSecFocused) {
targetIdx = nextVisibleItem(materialBrowserModel.selectedIndex, targetIdx = root.nextVisibleItem(materialBrowserModel.selectedIndex,
delta, materialBrowserModel) delta, materialBrowserModel)
if (targetIdx >= 0) if (targetIdx >= 0)
materialBrowserModel.selectMaterial(targetIdx) materialBrowserModel.selectMaterial(targetIdx)
} else if (texSecFocused) { } else if (texSecFocused) {
targetIdx = nextVisibleItem(materialBrowserTexturesModel.selectedIndex, targetIdx = root.nextVisibleItem(materialBrowserTexturesModel.selectedIndex,
delta, materialBrowserTexturesModel) delta, materialBrowserTexturesModel)
if (targetIdx >= 0) { if (targetIdx >= 0) {
materialBrowserTexturesModel.selectTexture(targetIdx) materialBrowserTexturesModel.selectTexture(targetIdx)
} else if (!materialBrowserModel.isEmpty && materialsSection.expanded) { } else if (!materialBrowserModel.isEmpty && materialsSection.expanded) {
targetIdx = nextVisibleItem(materialBrowserModel.rowCount(), -1, materialBrowserModel) targetIdx = root.nextVisibleItem(materialBrowserModel.rowCount(), -1, materialBrowserModel)
if (targetIdx >= 0) { if (targetIdx >= 0) {
if (delta !== -1) { if (delta !== -1) {
// Try to match column when switching between materials/textures // Try to match column when switching between materials/textures
origRowIdx = rowIndexOfItem(materialBrowserTexturesModel.selectedIndex, origRowIdx = root.rowIndexOfItem(materialBrowserTexturesModel.selectedIndex,
-delta, materialBrowserTexturesModel) -delta, materialBrowserTexturesModel)
if (visibleItemCount(materialBrowserModel) > origRowIdx) { if (root.visibleItemCount(materialBrowserModel) > origRowIdx) {
rowIdx = rowIndexOfItem(targetIdx, -delta, materialBrowserModel) rowIdx = root.rowIndexOfItem(targetIdx, -delta, materialBrowserModel)
if (rowIdx >= origRowIdx) { if (rowIdx >= origRowIdx) {
newTargetIdx = nextVisibleItem(targetIdx, newTargetIdx = root.nextVisibleItem(targetIdx,
-(rowIdx - origRowIdx), -(rowIdx - origRowIdx),
materialBrowserModel) materialBrowserModel)
} else { } else {
newTargetIdx = nextVisibleItem(targetIdx, newTargetIdx = root.nextVisibleItem(targetIdx,
-(-delta - origRowIdx + rowIdx), -(-delta - origRowIdx + rowIdx),
materialBrowserModel) materialBrowserModel)
} }
} else { } else {
newTargetIdx = nextVisibleItem(materialBrowserModel.rowCount(), newTargetIdx = root.nextVisibleItem(materialBrowserModel.rowCount(),
-1, materialBrowserModel) -1, materialBrowserModel)
} }
if (newTargetIdx >= 0) if (newTargetIdx >= 0)
targetIdx = newTargetIdx targetIdx = newTargetIdx
@@ -153,25 +180,25 @@ Item {
} }
} else if (delta > 0) { } else if (delta > 0) {
if (matSecFocused) { if (matSecFocused) {
targetIdx = nextVisibleItem(materialBrowserModel.selectedIndex, targetIdx = root.nextVisibleItem(materialBrowserModel.selectedIndex,
delta, materialBrowserModel) delta, materialBrowserModel)
if (targetIdx >= 0) { if (targetIdx >= 0) {
materialBrowserModel.selectMaterial(targetIdx) materialBrowserModel.selectMaterial(targetIdx)
} else if (!materialBrowserTexturesModel.isEmpty && texturesSection.expanded) { } else if (!materialBrowserTexturesModel.isEmpty && texturesSection.expanded) {
targetIdx = nextVisibleItem(-1, 1, materialBrowserTexturesModel) targetIdx = root.nextVisibleItem(-1, 1, materialBrowserTexturesModel)
if (targetIdx >= 0) { if (targetIdx >= 0) {
if (delta !== 1) { if (delta !== 1) {
// Try to match column when switching between materials/textures // Try to match column when switching between materials/textures
origRowIdx = rowIndexOfItem(materialBrowserModel.selectedIndex, origRowIdx = root.rowIndexOfItem(materialBrowserModel.selectedIndex,
delta, materialBrowserModel) delta, materialBrowserModel)
if (visibleItemCount(materialBrowserTexturesModel) > origRowIdx) { if (root.visibleItemCount(materialBrowserTexturesModel) > origRowIdx) {
if (origRowIdx > 0) { if (origRowIdx > 0) {
newTargetIdx = nextVisibleItem(targetIdx, origRowIdx, newTargetIdx = root.nextVisibleItem(targetIdx, origRowIdx,
materialBrowserTexturesModel) materialBrowserTexturesModel)
} }
} else { } else {
newTargetIdx = nextVisibleItem(materialBrowserTexturesModel.rowCount(), newTargetIdx = root.nextVisibleItem(materialBrowserTexturesModel.rowCount(),
-1, materialBrowserTexturesModel) -1, materialBrowserTexturesModel)
} }
if (newTargetIdx >= 0) if (newTargetIdx >= 0)
targetIdx = newTargetIdx targetIdx = newTargetIdx
@@ -181,8 +208,8 @@ Item {
} }
} }
} else if (texSecFocused) { } else if (texSecFocused) {
targetIdx = nextVisibleItem(materialBrowserTexturesModel.selectedIndex, targetIdx = root.nextVisibleItem(materialBrowserTexturesModel.selectedIndex,
delta, materialBrowserTexturesModel) delta, materialBrowserTexturesModel)
if (targetIdx >= 0) if (targetIdx >= 0)
materialBrowserTexturesModel.selectTexture(targetIdx) materialBrowserTexturesModel.selectTexture(targetIdx)
} }
@@ -190,24 +217,12 @@ Item {
} }
Keys.enabled: true Keys.enabled: true
Keys.onDownPressed: { Keys.onDownPressed: root.selectNextVisibleItem(gridMaterials.columns)
selectNextVisibleItem(gridMaterials.columns) Keys.onUpPressed: root.selectNextVisibleItem(-gridMaterials.columns)
} Keys.onLeftPressed: root.selectNextVisibleItem(-1)
Keys.onRightPressed: root.selectNextVisibleItem(1)
Keys.onUpPressed: { function handleEnterPress() {
selectNextVisibleItem(-gridMaterials.columns)
}
Keys.onLeftPressed: {
selectNextVisibleItem(-1)
}
Keys.onRightPressed: {
selectNextVisibleItem(1)
}
function handleEnterPress()
{
if (searchBox.activeFocus) if (searchBox.activeFocus)
return return
@@ -217,13 +232,8 @@ Item {
materialBrowserTexturesModel.openTextureEditor() materialBrowserTexturesModel.openTextureEditor()
} }
Keys.onEnterPressed: { Keys.onEnterPressed: root.handleEnterPress()
handleEnterPress() Keys.onReturnPressed: root.handleEnterPress()
}
Keys.onReturnPressed: {
handleEnterPress()
}
MouseArea { MouseArea {
id: focusGrabber id: focusGrabber
@@ -249,10 +259,10 @@ Item {
onClicked: (mouse) => { onClicked: (mouse) => {
if (!root.enableUiElements) if (!root.enableUiElements)
return; return
var matsSecBottom = mapFromItem(materialsSection, 0, materialsSection.y).y var matsSecBottom = mapFromItem(materialsSection, 0, materialsSection.y).y
+ materialsSection.height; + materialsSection.height
if (mouse.y < matsSecBottom) if (mouse.y < matsSecBottom)
ctxMenu.popupMenu() ctxMenu.popupMenu()
@@ -261,8 +271,7 @@ Item {
} }
} }
function ensureVisible(yPos, itemHeight) function ensureVisible(yPos, itemHeight) {
{
let currentY = contentYBehavior.targetValue && scrollViewAnim.running let currentY = contentYBehavior.targetValue && scrollViewAnim.running
? contentYBehavior.targetValue : scrollView.contentY ? contentYBehavior.targetValue : scrollView.contentY
@@ -286,18 +295,18 @@ Item {
return false return false
} }
function ensureSelectedVisible() function ensureSelectedVisible() {
{
if (rootView.materialSectionFocused && materialsSection.expanded && root.currMaterialItem if (rootView.materialSectionFocused && materialsSection.expanded && root.currMaterialItem
&& materialBrowserModel.isVisible(materialBrowserModel.selectedIndex)) { && materialBrowserModel.isVisible(materialBrowserModel.selectedIndex)) {
return ensureVisible(root.currMaterialItem.mapToItem(scrollView.contentItem, 0, 0).y, return root.ensureVisible(root.currMaterialItem.mapToItem(scrollView.contentItem, 0, 0).y,
root.currMaterialItem.height) root.currMaterialItem.height)
} else if (!rootView.materialSectionFocused && texturesSection.expanded) { } else if (!rootView.materialSectionFocused && texturesSection.expanded) {
let currItem = texturesRepeater.itemAt(materialBrowserTexturesModel.selectedIndex) let currItem = texturesRepeater.itemAt(materialBrowserTexturesModel.selectedIndex)
if (currItem && materialBrowserTexturesModel.isVisible(materialBrowserTexturesModel.selectedIndex)) if (currItem && materialBrowserTexturesModel.isVisible(materialBrowserTexturesModel.selectedIndex))
return ensureVisible(currItem.mapToItem(scrollView.contentItem, 0, 0).y, currItem.height) return root.ensureVisible(currItem.mapToItem(scrollView.contentItem, 0, 0).y,
currItem.height)
} else { } else {
return ensureVisible(0, 90) return root.ensureVisible(0, 90)
} }
} }
@@ -310,15 +319,14 @@ Item {
onTriggered: { onTriggered: {
// Redo until ensuring didn't change things // Redo until ensuring didn't change things
if (!root.ensureSelectedVisible()) { if (!root.ensureSelectedVisible()) {
stop() ensureTimer.stop()
interval = 20 ensureTimer.interval = 20
triggeredOnStart = true ensureTimer.triggeredOnStart = true
} }
} }
} }
function startDelayedEnsureTimer(delay) function startDelayedEnsureTimer(delay) {
{
// Ensuring visibility immediately in some cases like before new search results are rendered // Ensuring visibility immediately in some cases like before new search results are rendered
// causes mapToItem return incorrect values, leading to undesirable flicker, // causes mapToItem return incorrect values, leading to undesirable flicker,
// so delay ensuring visibility a bit. // so delay ensuring visibility a bit.
@@ -330,8 +338,7 @@ Item {
Connections { Connections {
target: materialBrowserModel target: materialBrowserModel
function onSelectedIndexChanged() function onSelectedIndexChanged() {
{
// commit rename upon changing selection // commit rename upon changing selection
if (root.currMaterialItem) if (root.currMaterialItem)
root.currMaterialItem.forceFinishEditing(); root.currMaterialItem.forceFinishEditing();
@@ -341,8 +348,7 @@ Item {
ensureTimer.start() ensureTimer.start()
} }
function onIsEmptyChanged() function onIsEmptyChanged() {
{
ensureTimer.start() ensureTimer.start()
} }
} }
@@ -350,13 +356,11 @@ Item {
Connections { Connections {
target: materialBrowserTexturesModel target: materialBrowserTexturesModel
function onSelectedIndexChanged() function onSelectedIndexChanged() {
{
ensureTimer.start() ensureTimer.start()
} }
function onIsEmptyChanged() function onIsEmptyChanged() {
{
ensureTimer.start() ensureTimer.start()
} }
} }
@@ -364,8 +368,7 @@ Item {
Connections { Connections {
target: rootView target: rootView
function onMaterialSectionFocusedChanged() function onMaterialSectionFocusedChanged() {
{
ensureTimer.start() ensureTimer.start()
} }
} }
@@ -614,9 +617,10 @@ Item {
id: scrollView id: scrollView
width: root.width width: root.width
height: root.height - toolbar.height height: root.height - toolbar.height - col.spacing
clip: true clip: true
visible: root.enableUiElements visible: root.enableUiElements
hideHorizontalScrollBar: true
interactive: !ctxMenu.opened && !ctxMenuTextures.opened && !rootView.isDragging interactive: !ctxMenu.opened && !ctxMenuTextures.opened && !rootView.isDragging
&& !HelperWidgets.Controller.contextMenuOpened && !HelperWidgets.Controller.contextMenuOpened
@@ -637,6 +641,12 @@ Item {
id: materialsSection id: materialsSection
width: root.width width: root.width
leftPadding: StudioTheme.Values.sectionPadding
rightPadding: StudioTheme.Values.sectionPadding
topPadding: StudioTheme.Values.sectionPadding
bottomPadding: StudioTheme.Values.sectionPadding
caption: qsTr("Materials") caption: qsTr("Materials")
dropEnabled: true dropEnabled: true
category: "MaterialBrowser" category: "MaterialBrowser"
@@ -671,11 +681,10 @@ Item {
Grid { Grid {
id: gridMaterials id: gridMaterials
width: scrollView.width width: scrollView.width - materialsSection.leftPadding
leftPadding: 5 - materialsSection.rightPadding
rightPadding: 5 spacing: StudioTheme.Values.sectionGridSpacing
bottomPadding: 5 columns: root.numColumns
columns: root.width / root.cellWidth
Repeater { Repeater {
id: materialRepeater id: materialRepeater
@@ -691,10 +700,10 @@ Item {
width: root.cellWidth width: root.cellWidth
height: root.cellHeight height: root.cellHeight
onShowContextMenu: { onShowContextMenu: ctxMenu.popupMenu(this, model)
ctxMenu.popupMenu(this, model)
}
} }
onCountChanged: root.responsiveResize(root.width, root.height)
} }
} }
@@ -727,6 +736,11 @@ Item {
id: texturesSection id: texturesSection
width: root.width width: root.width
leftPadding: StudioTheme.Values.sectionPadding
rightPadding: StudioTheme.Values.sectionPadding
topPadding: StudioTheme.Values.sectionPadding
bottomPadding: StudioTheme.Values.sectionPadding
caption: qsTr("Textures") caption: qsTr("Textures")
category: "MaterialBrowser" category: "MaterialBrowser"
@@ -768,11 +782,10 @@ Item {
Grid { Grid {
id: gridTextures id: gridTextures
width: scrollView.width width: scrollView.width - texturesSection.leftPadding
leftPadding: 5 - texturesSection.rightPadding
rightPadding: 5 spacing: StudioTheme.Values.sectionGridSpacing
bottomPadding: 5 columns: root.numColumns
columns: root.width / root.cellWidth
Repeater { Repeater {
id: texturesRepeater id: texturesRepeater
@@ -782,10 +795,10 @@ Item {
width: root.cellWidth width: root.cellWidth
height: root.cellHeight height: root.cellHeight
onShowContextMenu: { onShowContextMenu: ctxMenuTextures.popupMenu(model)
ctxMenuTextures.popupMenu(model)
}
} }
onCountChanged: root.responsiveResize(root.width, root.height)
} }
} }

View File

@@ -8,32 +8,24 @@ import HelperWidgets 2.0
import StudioTheme 1.0 as StudioTheme import StudioTheme 1.0 as StudioTheme
import MaterialBrowserBackend import MaterialBrowserBackend
Rectangle { Item {
id: root id: root
signal showContextMenu() signal showContextMenu()
function refreshPreview() function refreshPreview() {
{
img.source = "" img.source = ""
img.source = "image://materialBrowser/" + materialInternalId img.source = "image://materialBrowser/" + materialInternalId
} }
function forceFinishEditing() function forceFinishEditing() {
{
matName.commitRename() matName.commitRename()
} }
function startRename() function startRename() {
{
matName.startRename() matName.startRename()
} }
border.width: MaterialBrowserBackend.materialBrowserModel.selectedIndex === index ? MaterialBrowserBackend.rootView.materialSectionFocused ? 3 : 1 : 0
border.color: MaterialBrowserBackend.materialBrowserModel.selectedIndex === index
? StudioTheme.Values.themeControlOutlineInteraction
: "transparent"
color: "transparent"
visible: materialVisible visible: materialVisible
DropArea { DropArea {
@@ -81,12 +73,10 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
spacing: 1 spacing: 1
Item { width: 1; height: 5 } // spacer
Image { Image {
id: img id: img
width: root.width - 10 width: root.width
height: img.width height: img.width
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
source: "image://materialBrowser/" + materialInternalId source: "image://materialBrowser/" + materialInternalId
@@ -94,8 +84,8 @@ Rectangle {
} }
// Eat keys so they are not passed to parent while editing name // Eat keys so they are not passed to parent while editing name
Keys.onPressed: (e) => { Keys.onPressed: (event) => {
e.accepted = true; event.accepted = true
} }
MaterialBrowserItemName { MaterialBrowserItemName {
@@ -116,4 +106,14 @@ Rectangle {
} }
} }
} }
Rectangle {
id: marker
anchors.fill: parent
border.width: MaterialBrowserBackend.materialBrowserModel.selectedIndex === index ? MaterialBrowserBackend.rootView.materialSectionFocused ? 3 : 1 : 0
border.color: MaterialBrowserBackend.materialBrowserModel.selectedIndex === index
? StudioTheme.Values.themeControlOutlineInteraction
: "transparent"
color: "transparent"
}
} }

View File

@@ -9,22 +9,14 @@ import HelperWidgets
import StudioTheme as StudioTheme import StudioTheme as StudioTheme
import MaterialBrowserBackend import MaterialBrowserBackend
Rectangle { Item {
id: root id: root
visible: textureVisible visible: textureVisible
color: "transparent"
border.width: MaterialBrowserBackend.materialBrowserTexturesModel.selectedIndex === index
? !MaterialBrowserBackend.rootView.materialSectionFocused ? 3 : 1 : 0
border.color: MaterialBrowserBackend.materialBrowserTexturesModel.selectedIndex === index
? StudioTheme.Values.themeControlOutlineInteraction
: "transparent"
signal showContextMenu() signal showContextMenu()
function forceFinishEditing() function forceFinishEditing() {
{
txtId.commitRename() txtId.commitRename()
} }
@@ -68,12 +60,11 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
spacing: 1 spacing: 1
Item { width: 1; height: 5 } // spacer
Image { Image {
id: img id: img
source: "image://materialBrowserTex/" + textureSource source: "image://materialBrowserTex/" + textureSource
asynchronous: true asynchronous: true
width: root.width - 10 width: root.width
height: img.width height: img.width
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
smooth: true smooth: true
@@ -81,8 +72,8 @@ Rectangle {
} }
// Eat keys so they are not passed to parent while editing name // Eat keys so they are not passed to parent while editing name
Keys.onPressed: (e) => { Keys.onPressed: (event) => {
e.accepted = true; event.accepted = true
} }
MaterialBrowserItemName { MaterialBrowserItemName {
@@ -103,4 +94,16 @@ Rectangle {
} }
} }
} }
Rectangle {
id: marker
anchors.fill: parent
color: "transparent"
border.width: MaterialBrowserBackend.materialBrowserTexturesModel.selectedIndex === index
? !MaterialBrowserBackend.rootView.materialSectionFocused ? 3 : 1 : 0
border.color: MaterialBrowserBackend.materialBrowserTexturesModel.selectedIndex === index
? StudioTheme.Values.themeControlOutlineInteraction
: "transparent"
}
} }

View File

@@ -1,4 +0,0 @@
import QtQuick.Window 2.15
Window {
}

View File

@@ -1,4 +0,0 @@
import QtQuick 2.15
Window {
}

View File

@@ -20,6 +20,21 @@ Section {
// TODO position property, what should be the range?! // TODO position property, what should be the range?!
SectionLayout { SectionLayout {
PropertyLabel {
text: qsTr("Source")
tooltip: qsTr("Adds an image from the local file system.")
}
SecondColumnLayout {
UrlChooser {
backendValue: backendValues.source
filter: "*.avi *.mp4 *.mpeg *.wav"
}
ExpandingSpacer {}
}
PropertyLabel { text: qsTr("Playback rate") } PropertyLabel { text: qsTr("Playback rate") }
SecondColumnLayout { SecondColumnLayout {

View File

@@ -0,0 +1,364 @@
// 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.Layouts
import HelperWidgets
import StudioControls as StudioControls
import StudioTheme as StudioTheme
Column {
anchors.left: parent.left
anchors.right: parent.right
Section {
caption: qsTr("Animated Sprite")
anchors.left: parent.left
anchors.right: parent.right
SectionLayout {
PropertyLabel {
text: qsTr("Source")
tooltip: qsTr("Adds an image from the local file system.")
}
SecondColumnLayout {
UrlChooser {
backendValue: backendValues.source
}
ExpandingSpacer {}
}
PropertyLabel {
text: qsTr("Frame size")
tooltip: qsTr("Sets the width and height of the frame.")
blockedByTemplate: !(backendValues.frameWidth.isAvailable || backendValues.frameHeight.isAvailable)
}
SecondColumnLayout {
SpinBox {
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
backendValue: backendValues.frameWidth
minimumValue: 0
maximumValue: 8192
decimals: 0
enabled: backendValue.isAvailable
}
Spacer { implicitWidth: StudioTheme.Values.controlLabelGap }
ControlLabel {
//: The width of the animated sprite frame
text: qsTr("W", "width")
tooltip: qsTr("Width.")
enabled: backendValues.frameWidth.isAvailable
}
Spacer { implicitWidth: StudioTheme.Values.controlGap }
SpinBox {
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
backendValue: backendValues.frameHeight
minimumValue: 0
maximumValue: 8192
decimals: 0
enabled: backendValue.isAvailable
}
Spacer { implicitWidth: StudioTheme.Values.controlLabelGap }
ControlLabel {
//: The height of the animated sprite frame
text: qsTr("H", "height")
tooltip: qsTr("Height.")
enabled: backendValues.frameHeight.isAvailable
}
/*
TODO QDS-4836
Spacer { implicitWidth: StudioTheme.Values.controlGap }
LinkIndicator2D {}
*/
ExpandingSpacer {}
}
PropertyLabel {
text: qsTr("Frame coordinates")
tooltip: qsTr("Sets the coordinates of the first frame of the animated sprite.")
blockedByTemplate: !(backendValues.frameX.isAvailable || backendValues.frameY.isAvailable)
}
SecondColumnLayout {
SpinBox {
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
backendValue: backendValues.frameX
minimumValue: 0
maximumValue: 8192
decimals: 0
enabled: backendValue.isAvailable
}
Spacer { implicitWidth: StudioTheme.Values.controlLabelGap }
ControlLabel {
//: The width of the animated sprite frame
text: qsTr("X", "Frame X")
tooltip: qsTr("Frame X coordinate.")
enabled: backendValues.frameX.isAvailable
}
Spacer { implicitWidth: StudioTheme.Values.controlGap }
SpinBox {
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
backendValue: backendValues.frameY
minimumValue: 0
maximumValue: 8192
decimals: 0
enabled: backendValue.isAvailable
}
Spacer { implicitWidth: StudioTheme.Values.controlLabelGap }
ControlLabel {
//: The height of the animated sprite frame
text: qsTr("Y", "Frame Y")
tooltip: qsTr("Frame Y coordinate.")
enabled: backendValues.frameY.isAvailable
}
/*
TODO QDS-4836
Spacer { implicitWidth: StudioTheme.Values.controlGap }
LinkIndicator2D {}
*/
ExpandingSpacer {}
}
PropertyLabel {
text: qsTr("Frame count")
tooltip: qsTr("Sets the number of frames in this animated sprite.")
blockedByTemplate: !backendValues.frameCount.isAvailable
}
SecondColumnLayout {
SpinBox {
implicitWidth: StudioTheme.Values.singleControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
backendValue: backendValues.frameCount
decimals: 0
minimumValue: 0
maximumValue: 10000
enabled: backendValue.isAvailable
}
ExpandingSpacer {}
}
//frame rate OR frame duration OR frame sync should be used
//frame rate has priority over frame duration
//frame sync has priority over rate and duration
PropertyLabel {
text: qsTr("Frame rate")
tooltip: qsTr("Sets the number of frames per second to show in the animation.")
blockedByTemplate: !backendValues.frameRate.isAvailable
}
SecondColumnLayout {
SpinBox {
implicitWidth: StudioTheme.Values.singleControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
backendValue: backendValues.frameRate
decimals: 2
minimumValue: 0
maximumValue: 1000
enabled: backendValue.isAvailable
}
ExpandingSpacer {}
}
//frame duration OR frame rate OR frame sync should be used
//frame rate has priority over frame duration
//frame sync has priority over rate and duration
PropertyLabel {
text: qsTr("Frame duration")
tooltip: qsTr("Sets the duration of each frame of the animation in milliseconds.")
blockedByTemplate: !backendValues.frameDuration.isAvailable
}
SecondColumnLayout {
SpinBox {
implicitWidth: StudioTheme.Values.singleControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
backendValue: backendValues.frameDuration
decimals: 0
minimumValue: 0
maximumValue: 100000
enabled: backendValue.isAvailable
}
ExpandingSpacer {}
}
//frame sync OR frame rate OR frame duration should be used
//frame rate has priority over frame duration
//frame sync has priority over rate and duration
PropertyLabel {
text: qsTr("Frame sync")
tooltip: qsTr("Sets frame advancements one frame each time a frame is rendered to the screen.")
blockedByTemplate: !backendValues.frameSync.isAvailable
}
SecondColumnLayout {
CheckBox {
text: backendValues.frameSync.valueToString
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
backendValue: backendValues.frameSync
enabled: backendValue.isAvailable
}
ExpandingSpacer {}
}
PropertyLabel {
text: qsTr("Loops")
tooltip: qsTr("After playing the animation this many times, the animation will automatically stop.")
blockedByTemplate: !backendValues.loops.isAvailable
}
SecondColumnLayout {
SpinBox {
implicitWidth: StudioTheme.Values.singleControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
backendValue: backendValues.loops
decimals: 0
minimumValue: -1 //AnimatedSprite.Infinite = -1
maximumValue: 100000
enabled: backendValue.isAvailable
}
ExpandingSpacer {}
}
PropertyLabel {
text: qsTr("Interpolate")
tooltip: qsTr("If true, interpolation will occur between sprite frames to make the animation appear smoother.")
blockedByTemplate: !backendValues.interpolate.isAvailable
}
SecondColumnLayout {
CheckBox {
text: backendValues.interpolate.valueToString
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
backendValue: backendValues.interpolate
enabled: backendValue.isAvailable
}
ExpandingSpacer {}
}
PropertyLabel {
text: qsTr("Finish behavior")
tooltip: qsTr("Sets the behavior when the animation finishes on its own.")
blockedByTemplate: !backendValues.finishBehavior.isAvailable
}
SecondColumnLayout {
ComboBox {
implicitWidth: StudioTheme.Values.singleControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
width: implicitWidth
scope: "AnimatedSprite"
model: ["FinishAtInitialFrame", "FinishAtFinalFrame"]
backendValue: backendValues.finishBehavior
enabled: backendValue.isAvailable
}
ExpandingSpacer {}
}
PropertyLabel {
text: qsTr("Reverse")
tooltip: qsTr("If true, the animation will be played in reverse.")
blockedByTemplate: !backendValues.reverse.isAvailable
}
SecondColumnLayout {
CheckBox {
text: backendValues.reverse.valueToString
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
backendValue: backendValues.reverse
enabled: backendValue.isAvailable
}
ExpandingSpacer {}
}
PropertyLabel {
text: qsTr("Running")
tooltip: qsTr("Whether the sprite is animating or not.")
blockedByTemplate: !backendValues.running.isAvailable
}
SecondColumnLayout {
CheckBox {
text: backendValues.running.valueToString
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
backendValue: backendValues.running
enabled: backendValue.isAvailable
}
ExpandingSpacer {}
}
PropertyLabel {
text: qsTr("Paused")
tooltip: qsTr("When paused, the current frame can be advanced manually.")
blockedByTemplate: !backendValues.paused.isAvailable
}
SecondColumnLayout {
CheckBox {
text: backendValues.paused.valueToString
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
backendValue: backendValues.paused
enabled: backendValue.isAvailable
}
ExpandingSpacer {}
}
PropertyLabel {
text: qsTr("Current frame")
tooltip: qsTr("When paused, the current frame can be advanced manually by setting this property.")
blockedByTemplate: !backendValues.currentFrame.isAvailable
}
SecondColumnLayout {
SpinBox {
implicitWidth: StudioTheme.Values.singleControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
backendValue: backendValues.currentFrame
decimals: 0
minimumValue: 0
maximumValue: 100000
enabled: backendValue.isAvailable
}
ExpandingSpacer {}
}
}
}
}

View File

@@ -12,6 +12,11 @@ PropertyEditorPane {
ComponentSection {} ComponentSection {}
DynamicPropertiesSection {
propertiesModel: SelectionDynamicPropertiesModel {}
visible: !hasMultiSelection
}
Column { Column {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right

View File

@@ -1,13 +1,14 @@
// Copyright (C) 2021 The Qt Company Ltd. // Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick 2.15
import QtQuick.Layouts 1.15 import QtQuick
import QtQuick.Shapes 1.15 import QtQuick.Layouts
import QtQuick.Templates 2.15 as T import QtQuick.Shapes
import QtQuickDesignerTheme 1.0 import QtQuick.Templates as T
import StudioTheme 1.0 as StudioTheme import StudioTheme as StudioTheme
import StudioControls 1.0 as StudioControls import StudioControls as StudioControls
import QtQuickDesignerColorPalette 1.0 import QtQuickDesignerTheme
import QtQuickDesignerColorPalette
SecondColumnLayout { SecondColumnLayout {
id: colorEditor id: colorEditor
@@ -29,7 +30,7 @@ SecondColumnLayout {
return colorEditor.backendValue.value return colorEditor.backendValue.value
} }
property alias gradientPropertyName: popupLoader.gradientPropertyName property alias gradientPropertyName: popupDialog.gradientPropertyName
property alias gradientThumbnail: gradientThumbnail property alias gradientThumbnail: gradientThumbnail
property alias shapeGradientThumbnail: shapeGradientThumbnail property alias shapeGradientThumbnail: shapeGradientThumbnail
@@ -66,12 +67,12 @@ SecondColumnLayout {
target: colorEditor target: colorEditor
function onValueChanged() { function onValueChanged() {
if (popupLoader.isNotInGradientMode()) if (popupDialog.isSolid())
colorEditor.syncColor() colorEditor.syncColor()
} }
function onBackendValueChanged() { function onBackendValueChanged() {
if (popupLoader.isNotInGradientMode()) if (popupDialog.isSolid())
colorEditor.syncColor() colorEditor.syncColor()
} }
} }
@@ -101,13 +102,13 @@ SecondColumnLayout {
if (colorEditor.__block) if (colorEditor.__block)
return return
if (!popupLoader.isInValidState) if (!popupDialog.isInValidState)
return return
popupLoader.commitToGradient() popupDialog.commitToGradient()
// Delay setting the color to keep ui responsive // Delay setting the color to keep ui responsive
if (popupLoader.isNotInGradientMode()) if (popupDialog.isSolid())
colorEditorTimer.restart() colorEditorTimer.restart()
} }
@@ -127,17 +128,16 @@ SecondColumnLayout {
id: gradientThumbnail id: gradientThumbnail
anchors.fill: parent anchors.fill: parent
anchors.margins: StudioTheme.Values.border anchors.margins: StudioTheme.Values.border
visible: !popupLoader.isNotInGradientMode() visible: !popupDialog.isSolid()
&& !colorEditor.shapeGradients && !colorEditor.shapeGradients
&& popupLoader.hasLinearGradient() && popupDialog.isLinearGradient()
} }
Shape { Shape {
id: shape id: shape
anchors.fill: parent anchors.fill: parent
anchors.margins: StudioTheme.Values.border anchors.margins: StudioTheme.Values.border
visible: !popupLoader.isNotInGradientMode() visible: !popupDialog.isSolid() && colorEditor.shapeGradients
&& colorEditor.shapeGradients
ShapePath { ShapePath {
id: shapeGradientThumbnail id: shapeGradientThumbnail
@@ -171,78 +171,77 @@ SecondColumnLayout {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
popupLoader.opened ? popupLoader.close() : popupLoader.open() popupDialog.visibility ? popupDialog.close() : popupDialog.open()
forceActiveFocus() forceActiveFocus()
} }
} }
QtObject { StudioControls.PopupDialog {
id: popupLoader id: popupDialog
property bool isInValidState: popupLoader.active ? popupLoader.dialog.isInValidState : true
property QtObject dialog: popupLoader.loader.item
property bool opened: popupLoader.active ? popupLoader.dialog.opened : false
property bool isInValidState: loader.active ? popupDialog.loaderItem.isInValidState : true
property QtObject loaderItem: loader.item
property string gradientPropertyName property string gradientPropertyName
width: 260
maximumHeight: Screen.desktopAvailableHeight * 0.7
function commitToGradient() { function commitToGradient() {
if (!popupLoader.active) if (!loader.active)
return return
if (colorEditor.supportGradient && popupLoader.dialog.gradientModel.hasGradient) { if (colorEditor.supportGradient && popupDialog.loaderItem.gradientModel.hasGradient) {
var hexColor = convertColorToString(colorEditor.color) var hexColor = convertColorToString(colorEditor.color)
hexTextField.text = hexColor hexTextField.text = hexColor
popupLoader.dialog.commitGradientColor() popupDialog.loaderItem.commitGradientColor()
} }
} }
function isNotInGradientMode() { function isSolid() {
if (!popupLoader.active) if (!loader.active)
return true return true
return popupLoader.dialog.isNotInGradientMode()
return popupDialog.loaderItem.isSolid()
} }
function hasLinearGradient(){ function isLinearGradient(){
if (!popupLoader.active) if (!loader.active)
return false return false
return popupLoader.dialog.hasLinearGradient()
return popupDialog.loaderItem.isLinearGradient()
} }
function ensureLoader() { function ensureLoader() {
if (!popupLoader.active) if (!loader.active)
popupLoader.active = true loader.active = true
} }
function open() { function open() {
popupLoader.ensureLoader() popupDialog.ensureLoader()
popupLoader.dialog.open() popupDialog.show(preview)
}
function close() {
popupLoader.ensureLoader()
popupLoader.dialog.close()
} }
function determineActiveColorMode() { function determineActiveColorMode() {
if (popupLoader.active && popupLoader.dialog) if (loader.active && popupDialog.loaderItem)
popupLoader.dialog.determineActiveColorMode() popupDialog.loaderItem.determineActiveColorMode()
else else
colorEditor.syncColor() colorEditor.syncColor()
} }
property alias active: popupLoader.loader.active Loader {
property Loader loader: Loader { id: loader
parent: preview
active: colorEditor.supportGradient active: colorEditor.supportGradient
sourceComponent: ColorEditorPopup { sourceComponent: ColorEditorPopup {
id: cePopup shapeGradients: colorEditor.shapeGradients
x: cePopup.__defaultX supportGradient: colorEditor.supportGradient
y: cePopup.__defaultY width: popupDialog.contentWidth
}
onLoaded: {
popupDialog.loaderItem.initEditor()
popupDialog.titleBar = loader.item.titleBarContent
} }
onLoaded: popupLoader.dialog.initEditor()
} }
} }
} }
@@ -255,19 +254,26 @@ SecondColumnLayout {
id: hexTextField id: hexTextField
implicitWidth: StudioTheme.Values.twoControlColumnWidth implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth + StudioTheme.Values.actionIndicatorWidth
width: implicitWidth width: hexTextField.implicitWidth
enabled: popupLoader.isNotInGradientMode() enabled: popupDialog.isSolid()
writeValueManually: true writeValueManually: true
validator: RegExpValidator { validator: RegularExpressionValidator {
regExp: /#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?/g regularExpression: /#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?/g
} }
showTranslateCheckBox: false showTranslateCheckBox: false
indicatorVisible: true
indicator.icon.text: StudioTheme.Constants.copy_small
indicator.onClicked: {
hexTextField.selectAll()
hexTextField.copy()
hexTextField.deselect()
}
backendValue: colorEditor.backendValue backendValue: colorEditor.backendValue
onAccepted: colorEditor.color = colorFromString(hexTextField.text) onAccepted: colorEditor.color = colorFromString(hexTextField.text)
onCommitData: { onCommitData: {
colorEditor.color = colorFromString(hexTextField.text) colorEditor.color = colorFromString(hexTextField.text)
if (popupLoader.isNotInGradientMode()) { if (popupDialog.isSolid()) {
if (colorEditor.isVector3D) { if (colorEditor.isVector3D) {
backendValue.value = Qt.vector3d(colorEditor.color.r, backendValue.value = Qt.vector3d(colorEditor.color.r,
colorEditor.color.g, colorEditor.color.g,
@@ -283,9 +289,7 @@ SecondColumnLayout {
id: spacer id: spacer
} }
Component.onCompleted: popupLoader.determineActiveColorMode() Component.onCompleted: popupDialog.determineActiveColorMode()
onBackendValueChanged: { onBackendValueChanged: popupDialog.determineActiveColorMode()
popupLoader.determineActiveColorMode()
}
} }

View File

@@ -1,30 +0,0 @@
// Copyright (C) 2021 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 HelperWidgets 2.0
import StudioTheme 1.0 as StudioTheme
Item {
property alias currentColor: colorLine.color
width: 300
height: StudioTheme.Values.colorEditorPopupLineHeight
Image {
id: checkerboard
anchors.fill: colorLine
source: "images/checkers.png"
fillMode: Image.Tile
}
Rectangle {
id: colorLine
height: StudioTheme.Values.hueSliderHeight
width: parent.width
border.color: StudioTheme.Values.themeControlOutline
border.width: StudioTheme.Values.border
color: "white"
anchors.bottom: parent.bottom
}
}

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