Merge remote-tracking branch 'origin/7.0' into 8.0

Change-Id: I7cd5d3808007ef739212f4347ba9b16e7b298943
This commit is contained in:
Tim Jenssen
2022-07-01 11:19:21 +02:00
151 changed files with 2805 additions and 986 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -60,7 +60,7 @@
\li \l{Lists and Other Data Models} \li \l{Lists and Other Data Models}
\row \row
\li Timeline \li Timeline
\li \l{Creating Timelines} \li \l{Creating a Timeline}
\if defined(qtdesignstudio) \if defined(qtdesignstudio)
\row \row
\li Event List \li Event List

View File

@@ -39,6 +39,7 @@
\list \list
\li \l{Form Editor} \li \l{Form Editor}
\li \l{3D Editor} \li \l{3D Editor}
\li \l{Material Editor and Browser}
\li \l{Components} \li \l{Components}
\li \l{Assets} \li \l{Assets}
\li \l{Navigator} \li \l{Navigator}

View File

@@ -26,7 +26,7 @@
/*! /*!
\previouspage qtquick-form-editor.html \previouspage qtquick-form-editor.html
\page studio-3d-editor.html \page studio-3d-editor.html
\nextpage quick-components-view.html \nextpage studio-material-editor.html
\title 3D Editor \title 3D Editor

View File

@@ -32,7 +32,7 @@
\title Materials and Shaders \title Materials and Shaders
\image studio-qtquick-3d-material.png "Material attached to model in Design mode" \image studio-qtquick-3d-material.webp "Material attached to model in Design mode"
Materials and shaders define how object surfaces are rendered in \QDS and Materials and shaders define how object surfaces are rendered in \QDS and
live preview. As you change the properties of materials, new shaders are live preview. As you change the properties of materials, new shaders are
@@ -40,6 +40,10 @@
a shader depends on a combination of the properties that are set on it, and a shader depends on a combination of the properties that are set on it, and
the context of the scene itself. the context of the scene itself.
It is recommended that you use the \l {Material Editor and Browser} when
working with materials, but you can also add materials using the components
library.
The materials that you used in your imported scenes are imported to \QDS The materials that you used in your imported scenes are imported to \QDS
as \l{Qt Quick 3D} components. When you add a View3D component, it contains as \l{Qt Quick 3D} components. When you add a View3D component, it contains
a DefaultMaterial component. You can use the following predefined Qt Quick a DefaultMaterial component. You can use the following predefined Qt Quick
@@ -62,171 +66,12 @@
defines an image and how the image is mapped to meshes in a 3D scene. For defines an image and how the image is mapped to meshes in a 3D scene. For
more information, see \l {Textures}. more information, see \l {Textures}.
You can modify material properties in the \uicontrol Properties view, as You can create and modify materials in
instructed in the following sections. The availability of the properties \uicontrol {Material Editor} and \uicontrol {Material Browser}. The availability
depends on the material type. of the properties depends on the material type.
\image studio-qtquick-3d-default-material.png "DefaultMaterial properties" \image studio-qtquick-3d-default-material.webp "DefaultMaterial properties"
To enable the material to use vertex colors from the mesh, select the
\uicontrol {Enable vertex colors} check box. These are multiplied
by any other colors specified for the material.
You can animate material properties in the \uicontrol Timeline view, as You can animate material properties in the \uicontrol Timeline view, as
instructed in \l {Creating Timeline Animations}. instructed in \l {Creating Timeline Animations}.
\section1 Blending Colors
To determine how the colors of a model blend with the colors of the models
behind it, set the \uicontrol {Blend mode} property. To make opaque objects
occlude the objects behind them, select \uicontrol {SourceOver}.
For a lighter result, select \uicontrol Screen to blend colors using an
inverted multiply or \uicontrol ColorDodge to blend them by inverted
division. Color dodge procudes an even lighter result than screen.
For a darker result, select \uicontrol Multiply to blend colors using a
multiply or \uicontrol ColorBurn to blend them by inverted division, where
the result also is inverted. Color burn produces an even darker result than
multiply.
The screen and multiply modes are order-independent, so select them to
avoid \e popping, which can happen when using semi-opaque objects and
sorting the back and front faces or models.
For a result with higher contrast, select \uicontrol Overlay, which is a mix
of the multiply and screen modes.
\section1 Lighting Materials
To set the lighting method for generating a material, use the
\uicontrol Lighting property. Select \uicontrol {Fragment lighting} to
calculate diffuse and specular lighting for each rendered pixel. Some
effects, such as Fresnel or a bump map, require fragment lighting.
To skip lighting calculation, select \uicontrol {No lighting}. This is very
fast and quite effective when using image maps that do not need to be shaded
by lighting.
To set the base color for the material, use the \uicontrol {Diffuse Color}
property. You can either use the color picker or specify a RBG value. Set
the diffuse color to black to create purely-specular materials, such as
metals or mirrors. To apply a texture to a material, set it as the value of
the \uicontrol {Diffuse map} property. Using a texture with transparency
also applies the alpha channel as an \uicontrol {Opacity map}. You can set
the opacity of the material independently of the model as the value of the
\uicontrol Opacity property.
\section1 Self-Illuminating Materials
To set the color and amount of self-illumination for a material, use the
\uicontrol {Emissive color} and \uicontrol {Emissive factor} properties. In
a scene with black ambient lighting, a material with an emissive factor of 0
is black where the light does not shine on it. Setting the emissive factor
to 1 shows the material in its diffuse color instead.
To use a Texture for specifying the emissive factor for different parts of
the material, set the \uicontrol {Emissive map} property. Using a grayscale
image does not affect the color of the result, while using a color image
produces glowing regions with the color affected by the emissive map.
\section1 Using Highlights and Reflections
You can control the highlights and reflections on a material by setting the
properties in the \uicontrol Specular group. You can use the color picker
or set a RGB value to specify the color used to adjust specular reflections.
Use white for no effect
To use a color texture to modulate the amount and the color of specularity
across the surface of a material, set the \uicontrol {Specular map}
property. Set the \uicontrol {Specular amount} property to specify the
strength of specularity. This property does not affect the specular
reflection map, but it does affect the amount of reflections from a scene's
light probe.
\note Unless your mesh is high-resolution, you may need to use fragment
lighting to get good specular highlights from scene lights.
To determine how to calculate specular highlights for lights in the scene,
set the \uicontrol {Specular model}. In addition to the default mode, you
can use the GGX or Ward lighting model.
To use a Texture for specular highlighting on a material, set the
\uicontrol {Reflection map} property. When the texture is applied using
environmental mapping (not UV mapping), the map appears to be reflecting
from the environment as you rotate the model. Specular reflection maps are
an easy way to add a high-quality look at a relatively low cost.
To specify an image to use as the specular reflection map, set the
\uicontrol {Light probe} property.
Crisp images cause your material to look very glossy. The more you
blur your image, the softer your material appears.
To decrease head-on reflections (looking directly at the surface)
while maintaining reflections seen at grazing angles, set the
\uicontrol {Fresnel power} property. To select the angles to control,
set the \uicontrol {Index of refraction} property.
To control the size of the specular highlights generated from lights and the
clarity of reflections in general, set the \uicontrol {Specular roughness}
property. Larger values increase the roughness, while softening specular
highlights and blurring reflections. To control the specular roughness of
the material using a Texture, set the \uicontrol {Roughness map property}.
\section1 Simulating Geometry Displacement
Specify the properties in the \uicontrol {Bump/Normal} group to simulate
fine geometry displacement across the surface of the material. Set the
\uicontrol {Bump map} property to use a grayscale texture for the
simulation. Brighter pixels indicate raised regions.
To use a RGB image for simulation, set the \uicontrol {Normal map} property.
The RGB channels indicate XYZ normal deviations.
The amount of displacement is controlled by the \uicontrol {Bump amount}
property.
Bump and normal maps do not affect the silhouette of a model. To affect the
silhouette, set the \uicontrol {Displacement map} property. It specifies a
grayscale image used to offset the vertices of geometry across the surface
of the material. The \uicontrol {Displacement amount} property specifies the
offset amount.
\section1 Specifying Material Translucency
Set the properties in the \uicontrol Translucency group to control how much
light can pass through the material from behind. To use a grayscale texture,
specify it as the value of the \uicontrol {Translucency map} property.
To specify the amount of light wrap for the translucency map, set the
\uicontrol {Diffuse light wrap} property. A value of 0 does not wrap the
light at all, while a value of 1 wraps the light all around the object.
To specify the amount of falloff for the translucency based on
the angle of the normals of the object to the light source, set
the \uicontrol {Translucency falloff} property.
\section1 Culling Faces
Set the \uicontrol {Culling mode} property to determine whether the front
and back faces of a model are rendered. Culling modes check whether the
points in the polygon appear in clockwise or counter-clockwise order when
projected onto the screen. If front-facing polygons have a clockwise
winding, but the polygon projected on the screen has a counter-clockwise
winding, the projected polygon is rotated to face away from the camera and
is not rendered. Culling makes rendering objects quicker and more efficient
by reducing the number of polygons to draw.
\section1 Applying Materials to Models
To apply materials to models, you should first delete the default material,
and then drag-and-drop a new material from \l Assets to a model component
in \l Navigator.
You can apply the same material to another component as well. Again,
delete the default material first. You should then select the component and
go to the \uicontrol Properties view. Find the \uicontrol Materials property,
select the \inlineimage icons/plus.png
icon, and choose the new material in the dropdown menu.
*/ */

View File

@@ -25,7 +25,7 @@
/*! /*!
\page quick-components-view.html \page quick-components-view.html
\previouspage studio-3d-editor.html \previouspage studio-material-editor.html
\nextpage quick-assets.html \nextpage quick-assets.html
\title Components \title Components

View File

@@ -38,9 +38,9 @@
\image studio-timeline-empty.png "Empty Timeline view" \image studio-timeline-empty.png "Empty Timeline view"
Select the \inlineimage icons/plus.png Select the \inlineimage icons/plus.png
(\uicontrol {Add Timeline}) button to \l{Creating Timelines} (\uicontrol {Add Timeline}) button to
{create a timeline} and specify settings for it in the \l{Creating a Timeline}{create a timeline} and specify settings for it in
\uicontrol {Timeline Settings} dialog. the \uicontrol {Timeline Settings} dialog.
\image studio-timeline-settings.png "Timeline Settings dialog" \image studio-timeline-settings.png "Timeline Settings dialog"
@@ -124,11 +124,11 @@
\li \inlineimage icons/animation.png \li \inlineimage icons/animation.png
\li Opens the \uicontrol {Timeline Settings} dialog for editing \li Opens the \uicontrol {Timeline Settings} dialog for editing
timeline settings. timeline settings.
\li \l{Creating Timelines} \li \l{Creating a Timeline}
\row \row
\li Timeline ID \li Timeline ID
\li Displays the ID of the current timeline. \li Displays the ID of the current timeline.
\li \l{Creating Timelines} \li \l{Creating a Timeline}
\row \row
\li \inlineimage icons/to_first_frame.png \li \inlineimage icons/to_first_frame.png
\li \uicontrol {To Start (Home)} moves to the first frame on the \li \uicontrol {To Start (Home)} moves to the first frame on the
@@ -170,7 +170,7 @@
\li Specifies the first frame of the timeline. Negative values are \li Specifies the first frame of the timeline. Negative values are
allowed. The difference between the start frame and the end frame allowed. The difference between the start frame and the end frame
determines the duration of the animation. determines the duration of the animation.
\li \l{Creating Timelines} \li \l{Creating a Timeline}
\row \row
\li \inlineimage icons/zoom_small.png \li \inlineimage icons/zoom_small.png
\li \uicontrol {Zoom Out} (\key Ctrl+-) zooms out of the view. \li \uicontrol {Zoom Out} (\key Ctrl+-) zooms out of the view.
@@ -189,7 +189,7 @@
the start frame and the end frame determines the duration of the the start frame and the end frame determines the duration of the
animation, so if the start frame is 0, the end frame equals the animation, so if the start frame is 0, the end frame equals the
duration. duration.
\li \l{Creating Timelines} \li \l{Creating a Timeline}
\row \row
\li State Name \li State Name
\li Displays the name of the current state. \li Displays the name of the current state.

View File

@@ -30,18 +30,19 @@
\title Creating Timeline Animations \title Creating Timeline Animations
You can create timeline and keyframe based animations for linear You can create timelines and keyframe-based animations for linear
interpolation through intermediate values at specified keyframes interpolation through intermediate values at specified keyframes
instead of immediately changing to the target value. instead of immediately changing to the target value.
\section1 Creating Timelines You can also bind the timeline to a property value of a component such as
a slider and control the animation this way.
You specify settings for the timeline and for running the animation in the \section1 Creating an Animation
\uicontrol {Timeline Settings} dialog. The \uicontrol Animation radio button
is selected for a timeline animation and the \uicontrol {Expression binding}
radio button for a \l{Setting Bindings}{property animation}.
\image studio-timeline-settings.png "Timeline Settings dialog" To create an animation, whether it's a keyframe animation or an animation
bound to a property value, you first need to create a timeline.
\section2 Creating a Timeline
To create a timeline to animate a UI component: To create a timeline to animate a UI component:
@@ -50,95 +51,97 @@
(\uicontrol {Add Timeline}) button to specify settings (\uicontrol {Add Timeline}) button to specify settings
for the timeline and running the animation for the timeline and running the animation
in the \uicontrol {Timeline Settings} dialog. in the \uicontrol {Timeline Settings} dialog.
\li In the \uicontrol {Timeline ID} field, enter an ID that describes \li On the \uicontrol {Timeline Settings} tab:
the animated component. \list
\li In the \uicontrol {Start frame} field, set the first frame of the \li In the \uicontrol {Timeline ID} field, enter an id that
timeline. Negative values are allowed. describes the timeline.
\li In the \uicontrol {End frame} field, set the last frame of the \li In the \uicontrol {Start frame} field, set the first frame
timeline. of the timeline. Negative values are allowed.
\li In the \uicontrol {End frame} field, set the last frame
of the timeline.
\image timeline-settings-dialog.png
\endlist
\li On the \uicontrol {Animation Settings} tab:
\list
\li In the \uicontrol {Animation ID} field, enter an ID for the \li In the \uicontrol {Animation ID} field, enter an ID for the
animation. animation.
\li Select the \uicontrol {Running in Base State} check box to run the \li Optional. Select the \uicontrol {Running in Base State}
animation when the base state is applied. Deselect the check box check box to run the animation when the base state is applied.
if you want to run the animation when some other state is applied. Clear the check box to run the animation when some other state
For more information, see \l{Binding Animations to States}. is applied. For more information, see
\li In the \uicontrol {Start frame} field, set the first frame of the \l{Binding Animations to States}.
animation. \li In the \uicontrol {Start frame} field, set the first frame
\li In the \uicontrol {End frame} field, set the last frame of the of the animation.
animation. \li In the \uicontrol {End frame} field, set the last frame of
the animation.
\li In the \uicontrol {Duration} field, set the length of the \li In the \uicontrol {Duration} field, set the length of the
animation from the start frame to the end frame. If you set a animation in milliseconds.
shorter duration than the number of frames, frames are left out \li Optional. Select the \uicontrol Continuous check box to
from the end of the animation when viewing it. loop the animation indefinitely.
\li Select the \uicontrol Continuous check box to loop the animation \li Optional. In the \uicontrol Loops field, set the number of
indefinitely. times to run the animation. The default number of
\li In the \uicontrol Loops field, select the number of times to run loops is one, which means that you must restart the animation
the animation as a loop. The default number of loops is one, which to see it again.
means that you must restart the animation to see it again \li Optional. Select the \uicontrol {Ping pong} check box to
\li Select the \uicontrol {Ping pong} check box to play the animation play the animation backwards back to the beginning when it
backwards back to the beginning when it reaches the end. reaches the end.
\li In the \uicontrol Finished field, select the state \li Optional. In the \uicontrol Finished field, select the state
to apply when the animation finishes. to transition to when the animation finishes.
\endlist
\li Select \uicontrol Close to close the dialog and save the settings. \li Select \uicontrol Close to close the dialog and save the settings.
\endlist \endlist
To create additional timelines, select the \inlineimage icons/plus.png Now, with the settings set for the timeline and the animation, you
(\uicontrol {Add Timeline}) button next to the set the keyframes for the properties to animate.
\uicontrol {Timeline Settings} tab.
To specify settings for running timeline animations, select the \section3 Creating Additional Timelines
You can create more than one timeline. The purpose of several timelines is
to use different timelines in different states.
To create a timeline for a second state:
\list 1
\li In \uicontrol {Timeline}, open the \uicontrol {Timeline Settings}
dialog.
\li Next to the \uicontrol {Timeline Settings} tab, select
\inlineimage icons/plus.png \inlineimage icons/plus.png
(\uicontrol {Add Animation}) button next to the . This creates another timeline.
\uicontrol {Animation Settings} tab. For example, you could create \li In the table below the \uicontrol {Animation Settings} tab, set the
settings for running a part of the timeline animation between specified Timeline for the state where you want to use it.
frames or for running the animation backwards from the last frame to the \image timeline-settings-dialog-second.png
first. \endlist
To set the keyframe values for the timeline you created, first select the
state in \uicontrol {States} and the timeline is available in
\uicontrol{Timelines}.
To modify the settings, select the \inlineimage icons/animation.png \image timeline-states.png
(\uicontrol {Timeline Settings (S)}) button on the \l{Timeline Toolbar}
{toolbar} (or press \key S) in the \l Timeline view.
\section2 Binding Animations to States
The table at the bottom of the \uicontrol {Timeline Settings} dialog lists
the available states. Double-click the values in the \uicontrol Timeline
and \uicontrol Animation column to bind the states to animations. In the
\uicontrol {Fixed Frame} column, you can bind the states that don't have
animations to fixed frames.
\section1 Managing Keyframes
To animate components in the \l Timeline view, move to a frame
on the timeline and specify changes in the values of a property. \QC
automatically adds keyframes between two keyframes and sets their values
evenly to create an appearance of movement or transformation.
\image studio-timeline-with-tracks.png "Timeline view"
\section2 Setting Keyframe Values \section2 Setting Keyframe Values
You can insert keyframes for all the properties of all the components that When you create a timeline, \QDS creates one animation for the timeline.
you want to animate first, and then record the changes in their values by You can create as many animations for a timeline as you want. For example,
selecting the \inlineimage icons/local_record_keyframes.png you can create animations to run just a small section of the timeline or to
(\uicontrol {Per Property Recording}) button for one property at a time. run the timeline backwards.
For example, you can hide and show components by turning their visibility
off and on or by setting their opacity to 0 or 1.
You can also select the \uicontrol {Auto Key (K)} button (or press \key K) To animate components in the \l Timeline view, you set keyframe values for
to record changes in property values, but you need to be more careful about the property to animate. \QDS automatically adds keyframes between two
which property values you are changing to avoid surprises. keyframes and sets their values evenly to create, for example, movement or
transformation.
To record the changes of property values: To set keyframe values for a component property:
\list 1 \list 1
\li In the \l Navigator view, select the component to animate. \li In the \l Navigator view, select the component to animate.
\li In the \l Properties view, select \inlineimage icons/action-icon.png \li In the \l Properties view, select \inlineimage icons/action-icon.png
(\uicontrol Actions) > \uicontrol {Insert Keyframe} for the property (\uicontrol Actions) > \uicontrol {Insert Keyframe} for the property
that you want to animate. that you want to animate.
\image timeline-insert-keyframe.png
\li In the \l Timeline view, select the \li In the \l Timeline view, select the
\uicontrol {Per Property Recording} button \uicontrol {Per Property Recording} button
to start recording property changes. to start recording property changes.
\li Check that the playhead is in frame 0 and enter the value of the \image timeline-per-property-recording.png
\li Ensure that the playhead is in frame 0 and enter the value of the
property in the field next to the property name on the timeline. property in the field next to the property name on the timeline.
Press \key Enter to save the value. Press \key Enter to save the value.
\li Move the playhead to another frame on the timeline and specify \li Move the playhead to another frame on the timeline and specify
@@ -148,11 +151,67 @@
\uicontrol {Per Property Recording} again to stop recording. \uicontrol {Per Property Recording} again to stop recording.
\endlist \endlist
\section2 Binding a Timeline to a Property
When you bind a timeline to a component property, the animation's
current frame is controlled by the value of the property.
In this example, you bind the timeline to a slider component.
With a timeline created and keyframe values set:
\list 1
\li From \uicontrol {Components}, drag a slider to
\uicontrol {Form Editor} or \uicontrol {Navigator}.
\li In \uicontrol {Navigator}, select \e slider and in
\uicontrol {Properties}, set:
\list
\li \uicontrol To to 1000.
\note The \uicontrol From and \uicontrol To values of the slider
should match the \uicontrol {Start Frame} and
\uicontrol {End Frame} values of the timeline if you want to
control the complete animation with the slider.
\endlist
\li In the \uicontrol {Timeline Settings} dialog, select
\inlineimage icons/minus.png
next to the \uicontrol {Animation Settings} tab to delete the
animation. If you have several animations, delete all.
\li In \uicontrol {Expression binding}, enter \c {slider.value}.
\image timeline-settings-property-binding.png
\endlist
\section2 Binding Animations to States
You can bind animations to states, this means that the animation will run
when you enter the state.
To bind an animation to a state:
\list 1
\li In the table at the bottom of the \uicontrol {Timeline Settings}
dialog lists:
\list
\li Double-click the value in the \uicontrol Timeline field and select
the timeline with the animation you want to bind to the state.
\li Double-click the value in the \uicontrol Animation field and
select the animation you want to bind to the state.
\image timeline-bind-animation-state.png
\endlist
\endlist
To bind a state to a certain keyframe in an animation without running the
animation, set the keyframe in the \uicontrol{Fixed Frame} field.
\section1 Managing Keyframes
\image studio-timeline-with-tracks.png "Timeline view"
\section2 Editing Keyframes
To remove all the changes you recorded for a property, right-click the To remove all the changes you recorded for a property, right-click the
property name on the timeline and select \uicontrol {Remove Property}. property name on the timeline and select \uicontrol {Remove Property}.
To add keyframes to the keyframe track of a component at the current To add keyframes to the keyframe track of a component at the current
position of the playhead, select \uicontrol {Add Keyframes at Current Frame}. position of the playhead, right-click the component name on the timeline and
select \uicontrol {Add Keyframes at Current Frame}.
Keyframes are marked on the timeline by using \l{keyframe_marker}{markers} Keyframes are marked on the timeline by using \l{keyframe_marker}{markers}
of different colors and shapes, depending on whether they are active or of different colors and shapes, depending on whether they are active or
@@ -162,7 +221,7 @@
\section2 Editing Keyframe Values \section2 Editing Keyframe Values
To fine-tune the value of a keyframe, double-click a keyframe marker or To fine-tune the value of a keyframe, double-click a keyframe marker or
select \uicontrol {Edit Keyframe} in the context menu. right-click it and select \uicontrol {Edit Keyframe} in the context menu.
The \uicontrol {Edit Keyframe} dialog displays the name of the property The \uicontrol {Edit Keyframe} dialog displays the name of the property
you are animating and its current value at the frame specified in the you are animating and its current value at the frame specified in the
@@ -173,27 +232,36 @@
\section2 Copying Keyframes \section2 Copying Keyframes
You can copy the keyframes from the keyframe track for a component and You can copy the keyframes from the keyframe track for a component and
paste them to the keyframe track of another component. To copy all paste them to the keyframe track of another component.
keyframes from one track to another one, first right-click the component ID
and select \uicontrol {Copy All Keyframes} in the context menu. To copy all keyframes from one track to another one:
Then right-click the other component ID, and select \list 1
\li Right-click the component ID and select
\uicontrol {Copy All Keyframes} in the context menu.
\li Right-click the other component ID, and select
\uicontrol {Paste Keyframes} in the context menu. \uicontrol {Paste Keyframes} in the context menu.
\endlist
\section2 Deleting Keyframes \section2 Deleting Keyframes
To delete the selected keyframe, select \uicontrol {Delete Keyframe} in the To delete a keyframe, right-click it and select \uicontrol {Delete Keyframe}
context menu. in the context menu.
To delete all keyframes from the selected component, select To delete all keyframes from the selected component, right-click the
component name in \uicontrol {Timeline} and select
\uicontrol {Delete All Keyframes} in the context menu. \uicontrol {Delete All Keyframes} in the context menu.
\section1 Viewing the Animation \section1 Viewing the Animation
You can view the animation on the canvas by moving the playhead along the To preview your animation, do one of the following in the
timeline. \uicontrol{Timeline} view:
\list
\li Drag the playhead along the timeline.
\li Select \inlineimage icons/start_playback.png
button or press \key Space.
\endlist
To preview the animation, select the \uicontrol {Play (Space)} To preview the whole UI, select the
button or press \key Space. To preview the whole UI, select the
\inlineimage icons/live_preview.png \inlineimage icons/live_preview.png
(\uicontrol {Show Live Preview}) button on the canvas toolbar (\uicontrol {Show Live Preview}) button on the canvas toolbar
or press \key {Alt+P}. or press \key {Alt+P}.

View File

@@ -0,0 +1,266 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Creator documentation.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Free Documentation License Usage
** Alternatively, this file may be used under the terms of the GNU Free
** Documentation License version 1.3 as published by the Free Software
** Foundation and appearing in the file included in the packaging of
** this file. Please review the following information to ensure
** the GNU Free Documentation License version 1.3 requirements
** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
**
****************************************************************************/
/*!
\page studio-material-editor.html
\previouspage studio-3d-editor.html
\nextpage quick-components-view.html
\title Material Editor and Browser
In the \uicontrol {Material Editor} and \uicontrol {Material Browser} views,
you create and manage materials.
\image material-editor-browser.webp "Material Editor and Browser"
\section1 Creating a Material
To create a new material, do one of the following:
\list
\li In \uicontrol {Material Browser}, select \inlineimage icons/plus.png
.
\li In \uicontrol {Material Editor}, select \inlineimage icons/plus.png
.
\endlist
\section1 Editing a Material
To edit a material, select it in \uicontrol{Material Browser} and edit its
properties in \uicontrol{Material Editor}. If \uicontrol {Material Editor}
is closed, open it in one of the following ways:
\list
\li In \uicontrol{Navigator}, right-click an object that has the material
assigned to it and select \uicontrol {Edit Material}.
\li In \uicontrol{Material Browser}, double-click a material.
\endlist
\section1 Assigning a Material to an Object
To assign a material to a 3D object in your project, first select the object
in \uicontrol Navigator or \uicontrol {3D Editor}. Then, do one of the
following:
\list
\li In \uicontrol {Material Browser}, right-click the material and select
\uicontrol {Apply to Selected}. If there already is any material assigned
to the object, you can select whether to replace the material or to add
another material to the object.
\li In \uicontrol {Material Editor}, select
\inlineimage icons/apply-material.png
. This replaces any material already assigned to the object.
\endlist
\section1 Removing a Material from an Object
To remove an assigned material from an object:
\list 1
\li In \uicontrol{Navigator}, select the object.
\li In \uicontrol{Properties}, select
\inlineimage icons/close.png
next to the material.
\image materials-remove-material.png
\endlist
\section1 Using Texture Maps
In \QDS you can add many different texture maps to your material.
To add a texture map to a material:
\list 1
\li Select the material in \uicontrol{Material Browser}.
\li From \uicontrol {Assets}, drag an image to the correct map field
in \uicontrol {Material Editor}. For example, to add a diffuse map, drag
the image to \uicontrol{Diffuse Map} in \uicontrol{Material Editor}.
\endlist
\section2 Using a Reflection Map for Environmental Mapping
To use a texture for environmental mapping, you need to set the mapping
mode to \e {environment}.
To add a reflection map for environmental mapping to a material:
\list 1
\li Select the material in \uicontrol {Material Browser}.
\li From \uicontrol{Assets}, drag an image to
\uicontrol{Reflection Map}.
\li In \uicontrol {Navigator}, select
\inlineimage icons/filtericon.png
and then clear \uicontrol {Show Only Visible Components}. Now the
texture you just added to the material is visible in
\uicontrol {Navigator}.
\image navigator-material-texture.png
\li In \uicontrol {Navigator}, select the texture.
\li In \uicontrol {Properties}, set \uicontrol {Texture Mapping} to
\uicontrol {Environment}.
\endlist
\section1 Blending Colors
To determine how the colors of a model blend with the colors of the models
behind it, set the \uicontrol {Blend mode} property. To make opaque objects
occlude the objects behind them, select \uicontrol {SourceOver}.
For a lighter result, select \uicontrol Screen to blend colors using an
inverted multiply or \uicontrol ColorDodge to blend them by inverted
division. Color dodge produces an even lighter result than screen.
For a darker result, select \uicontrol Multiply to blend colors using a
multiply or \uicontrol ColorBurn to blend them by inverted division, where
the result also is inverted. Color burn produces an even darker result than
multiply.
The screen and multiply modes are order-independent, so select them to
avoid \e popping, which can happen when using semi-opaque objects and
sorting the back and front faces or models.
For a result with higher contrast, select \uicontrol Overlay, which is a mix
of the multiply and screen modes.
\section1 Lighting Materials
To set the lighting method for generating a material, use the
\uicontrol Lighting property. Select \uicontrol {Fragment lighting} to
calculate diffuse and specular lighting for each rendered pixel. Some
effects, such as Fresnel or a bump map, require fragment lighting.
To skip lighting calculation, select \uicontrol {No lighting}. This is very
fast and quite effective when using image maps that do not need to be shaded
by lighting.
To set the base color for the material, use the \uicontrol {Diffuse Color}
property. You can either use the color picker or specify a RBG value. Set
the diffuse color to black to create purely-specular materials, such as
metals or mirrors. To apply a texture to a material, set it as the value of
the \uicontrol {Diffuse map} property. Using a texture with transparency
also applies the alpha channel as an \uicontrol {Opacity map}. You can set
the opacity of the material independently of the model as the value of the
\uicontrol Opacity property.
\section1 Self-Illuminating Materials
To set the color and amount of self-illumination for a material, use the
\uicontrol {Emissive color} and \uicontrol {Emissive factor} properties. In
a scene with black ambient lighting, a material with an emissive factor of 0
is black where the light does not shine on it. Setting the emissive factor
to 1 shows the material in its diffuse color instead.
To use a Texture for specifying the emissive factor for different parts of
the material, set the \uicontrol {Emissive map} property. Using a grayscale
image does not affect the color of the result, while using a color image
produces glowing regions with the color affected by the emissive map.
\section1 Using Highlights and Reflections
You can control the highlights and reflections on a material by setting the
properties in the \uicontrol Specular group. You can use the color picker
or set a RGB value to specify the color used to adjust specular reflections.
Use white for no effect.
To use a color texture to modulate the amount and the color of specularity
across the surface of a material, set the \uicontrol {Specular map}
property. Set the \uicontrol {Specular amount} property to specify the
strength of specularity. This property does not affect the specular
reflection map, but it does affect the amount of reflections from a scene's
light probe.
\note Unless your mesh is high-resolution, you may need to use fragment
lighting to get good specular highlights from scene lights.
To determine how to calculate specular highlights for lights in the scene,
set the \uicontrol {Specular model}. In addition to the default mode, you
can use the GGX or Ward lighting model.
To use a Texture for specular highlighting on a material, set the
\uicontrol {Reflection map} property. When the texture is applied using
environmental mapping (not UV mapping), the map appears to be reflecting
from the environment as you rotate the model. Specular reflection maps are
an easy way to add a high-quality look at a relatively low cost.
To specify an image to use as the specular reflection map, set the
\uicontrol {Light probe} property.
Crisp images cause your material to look very glossy. The more you
blur your image, the softer your material appears.
To decrease head-on reflections (looking directly at the surface)
while maintaining reflections seen at grazing angles, set the
\uicontrol {Fresnel power} property. To select the angles to control,
set the \uicontrol {Index of refraction} property.
To control the size of the specular highlights generated from lights and the
clarity of reflections in general, set the \uicontrol {Specular roughness}
property. Larger values increase the roughness, while softening specular
highlights and blurring reflections. To control the specular roughness of
the material using a Texture, set the \uicontrol {Roughness map property}.
\section1 Simulating Geometry Displacement
Specify the properties in the \uicontrol {Bump/Normal} group to simulate
fine geometry displacement across the surface of the material. Set the
\uicontrol {Bump map} property to use a grayscale texture for the
simulation. Brighter pixels indicate raised regions.
To use an image for simulation, set the \uicontrol {Normal map} property.
The RGB channels indicate XYZ normal deviations.
The amount of displacement is controlled by the \uicontrol {Bump amount}
property.
Bump and normal maps do not affect the silhouette of a model. To affect the
silhouette, set the \uicontrol {Displacement map} property. It specifies a
grayscale image used to offset the vertices of geometry across the surface
of the material. The \uicontrol {Displacement amount} property specifies the
offset amount.
\section1 Specifying Material Translucency
Set the properties in the \uicontrol Translucency group to control how much
light can pass through the material from behind. To use a grayscale texture,
specify it as the value of the \uicontrol {Translucency map} property.
To specify the amount of light wrap for the translucency map, set the
\uicontrol {Diffuse light wrap} property. A value of 0 does not wrap the
light at all, while a value of 1 wraps the light all around the object.
To specify the amount of falloff for the translucency based on
the angle of the normals of the object to the light source, set
the \uicontrol {Translucency falloff} property.
\section1 Culling Faces
Set the \uicontrol {Culling mode} property to determine whether the front
and back faces of a model are rendered. Culling modes check whether the
points in the polygon appear in clockwise or counter-clockwise order when
projected onto the screen. If front-facing polygons have a clockwise
winding, but the polygon projected on the screen has a counter-clockwise
winding, the projected polygon is rotated to face away from the camera and
is not rendered. Culling makes rendering objects quicker and more efficient
by reducing the number of polygons to draw.
*/

View File

@@ -61,7 +61,8 @@ public:
QSize captureImageMinimumSize, QSize captureImageMinimumSize,
QSize captureImageMaximumSize, QSize captureImageMaximumSize,
qint32 stateInstanceId, qint32 stateInstanceId,
const QList<QColor> &edit3dBackgroundColor) const QList<QColor> &edit3dBackgroundColor,
const QColor &edit3dGridColor)
: instances(instanceContainer) : instances(instanceContainer)
, reparentInstances(reparentContainer) , reparentInstances(reparentContainer)
, ids(idVector) , ids(idVector)
@@ -78,6 +79,7 @@ public:
, captureImageMaximumSize(captureImageMaximumSize) , captureImageMaximumSize(captureImageMaximumSize)
, stateInstanceId{stateInstanceId} , stateInstanceId{stateInstanceId}
, edit3dBackgroundColor{edit3dBackgroundColor} , edit3dBackgroundColor{edit3dBackgroundColor}
, edit3dGridColor{edit3dGridColor}
{} {}
friend QDataStream &operator<<(QDataStream &out, const CreateSceneCommand &command) friend QDataStream &operator<<(QDataStream &out, const CreateSceneCommand &command)
@@ -98,6 +100,7 @@ public:
out << command.captureImageMinimumSize; out << command.captureImageMinimumSize;
out << command.captureImageMaximumSize; out << command.captureImageMaximumSize;
out << command.edit3dBackgroundColor; out << command.edit3dBackgroundColor;
out << command.edit3dGridColor;
return out; return out;
} }
@@ -120,6 +123,7 @@ public:
in >> command.captureImageMinimumSize; in >> command.captureImageMinimumSize;
in >> command.captureImageMaximumSize; in >> command.captureImageMaximumSize;
in >> command.edit3dBackgroundColor; in >> command.edit3dBackgroundColor;
in >> command.edit3dGridColor;
return in; return in;
} }
@@ -141,6 +145,7 @@ public:
QSize captureImageMaximumSize; QSize captureImageMaximumSize;
qint32 stateInstanceId = 0; qint32 stateInstanceId = 0;
QList<QColor> edit3dBackgroundColor; QList<QColor> edit3dBackgroundColor;
QColor edit3dGridColor;
}; };
QDebug operator<<(QDebug debug, const CreateSceneCommand &command); QDebug operator<<(QDebug debug, const CreateSceneCommand &command);

View File

@@ -57,6 +57,7 @@ public:
ParticlesRestart, ParticlesRestart,
ParticlesSeek, ParticlesSeek,
SelectBackgroundColor, SelectBackgroundColor,
SelectGridColor,
ResetBackgroundColor, ResetBackgroundColor,
}; };

View File

@@ -13,7 +13,7 @@
<file>mockfiles/images/directional@2x.png</file> <file>mockfiles/images/directional@2x.png</file>
<file>mockfiles/images/point.png</file> <file>mockfiles/images/point.png</file>
<file>mockfiles/images/point@2x.png</file> <file>mockfiles/images/point@2x.png</file>
<file>mockfiles/images/floor_tex.png</file> <file>mockfiles/images/static_floor.png</file>
<file>mockfiles/images/spot.png</file> <file>mockfiles/images/spot.png</file>
<file>mockfiles/images/spot@2x.png</file> <file>mockfiles/images/spot@2x.png</file>
<file>mockfiles/qt5/AdjustableArrow.qml</file> <file>mockfiles/qt5/AdjustableArrow.qml</file>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -47,6 +47,7 @@ Item {
property alias contentItem: contentItem property alias contentItem: contentItem
property color backgroundGradientColorStart: "#222222" property color backgroundGradientColorStart: "#222222"
property color backgroundGradientColorEnd: "#999999" property color backgroundGradientColorEnd: "#999999"
property color gridColor: "#aaaaaa"
enum SelectionMode { Item, Group } enum SelectionMode { Item, Group }
enum TransformMode { Move, Rotate, Scale } enum TransformMode { Move, Rotate, Scale }
@@ -96,12 +97,14 @@ Item {
{"usePerspective": usePerspective, {"usePerspective": usePerspective,
"showSceneLight": showEditLight, "showSceneLight": showEditLight,
"showGrid": showGrid, "showGrid": showGrid,
"gridColor": gridColor,
"importScene": activeScene, "importScene": activeScene,
"cameraZoomFactor": cameraControl._zoomFactor, "cameraZoomFactor": cameraControl._zoomFactor,
"z": 1}); "z": 1});
editView.usePerspective = Qt.binding(function() {return usePerspective;}); editView.usePerspective = Qt.binding(function() {return usePerspective;});
editView.showSceneLight = Qt.binding(function() {return showEditLight;}); editView.showSceneLight = Qt.binding(function() {return showEditLight;});
editView.showGrid = Qt.binding(function() {return showGrid;}); editView.showGrid = Qt.binding(function() {return showGrid;});
editView.gridColor = Qt.binding(function() {return gridColor;});
editView.cameraZoomFactor = Qt.binding(function() {return cameraControl._zoomFactor;}); editView.cameraZoomFactor = Qt.binding(function() {return cameraControl._zoomFactor;});
selectionBoxes.length = 0; selectionBoxes.length = 0;
@@ -217,12 +220,21 @@ Item {
function updateViewStates(viewStates) function updateViewStates(viewStates)
{ {
if ("selectBackgroundColor" in viewStates) { if ("selectBackgroundColor" in viewStates) {
if (Array.isArray(viewStates.selectBackgroundColor)) {
var colors = viewStates.selectBackgroundColor
backgroundGradientColorStart = colors[0];
backgroundGradientColorEnd = colors[1];
} else {
var color = viewStates.selectBackgroundColor var color = viewStates.selectBackgroundColor
backgroundGradientColorStart = color[0]; backgroundGradientColorStart = color;
backgroundGradientColorEnd = color[1]; backgroundGradientColorEnd = color;
} }
} }
if ("selectGridColor" in viewStates)
viewRoot.gridColor = viewStates.selectGridColor
}
// If resetToDefault is true, tool states not specifically set to anything will be reset to // If resetToDefault is true, tool states not specifically set to anything will be reset to
// their default state. // their default state.
function updateToolStates(toolStates, resetToDefault) function updateToolStates(toolStates, resetToDefault)

View File

@@ -33,6 +33,7 @@ Node {
property alias lines: gridGeometry.lines property alias lines: gridGeometry.lines
property alias step: gridGeometry.step property alias step: gridGeometry.step
property alias subdivAlpha: subGridMaterial.opacity property alias subdivAlpha: subGridMaterial.opacity
property alias gridColor: mainGridMaterial.diffuseColor
eulerRotation.x: 90 eulerRotation.x: 90

View File

@@ -45,10 +45,6 @@ View3D {
Node { Node {
DirectionalLight { DirectionalLight {
shadowMapQuality: Light.ShadowMapQualityMedium
shadowFilter: 20
shadowFactor: 21
castsShadow: true
eulerRotation.x: -26 eulerRotation.x: -26
eulerRotation.y: -57 eulerRotation.y: -57
} }
@@ -68,25 +64,5 @@ View3D {
source: "#Sphere" source: "#Sphere"
materials: previewMaterial materials: previewMaterial
} }
Model {
id: floorModel
source: "#Rectangle"
scale.y: 8
scale.x: 8
eulerRotation.x: -90
materials: floorMaterial
DefaultMaterial {
id: floorMaterial
diffuseMap: floorTex
Texture {
id: floorTex
source: "../images/floor_tex.png"
scaleU: floorModel.scale.x
scaleV: floorModel.scale.y
}
}
}
} }
} }

View File

@@ -123,14 +123,13 @@ Item {
anchors.fill: parent anchors.fill: parent
} }
Rectangle { // We can use static image in Qt5 as only small previews will be generated
Image {
id: backgroundRect id: backgroundRect
anchors.fill: parent anchors.fill: parent
z: -1 z: -1
gradient: Gradient { source: "../images/static_floor.png"
GradientStop { position: 1.0; color: "#222222" } fillMode: Image.Stretch
GradientStop { position: 0.0; color: "#999999" }
}
} }
} }
} }

View File

@@ -37,7 +37,7 @@ View3D {
function fitToViewPort(closeUp) function fitToViewPort(closeUp)
{ {
// The magic number is the distance from camera default pos to origin // The magic number is the distance from camera default pos to origin
_generalHelper.calculateNodeBoundsAndFocusCamera(theCamera, sourceModel, root, _generalHelper.calculateNodeBoundsAndFocusCamera(theCamera, model, root,
1040, closeUp); 1040, closeUp);
} }
@@ -70,7 +70,7 @@ View3D {
materials: [ materials: [
DefaultMaterial { DefaultMaterial {
diffuseColor: "#4aee45" diffuseColor: "#999999"
} }
] ]
} }

View File

@@ -32,6 +32,7 @@ View3D {
property bool usePerspective: false property bool usePerspective: false
property alias showSceneLight: sceneLight.visible property alias showSceneLight: sceneLight.visible
property alias showGrid: helperGrid.visible property alias showGrid: helperGrid.visible
property alias gridColor: helperGrid.gridColor
property alias sceneHelpers: sceneHelpers property alias sceneHelpers: sceneHelpers
property alias perspectiveCamera: scenePerspectiveCamera property alias perspectiveCamera: scenePerspectiveCamera
property alias orthoCamera: sceneOrthoCamera property alias orthoCamera: sceneOrthoCamera

View File

@@ -48,6 +48,7 @@ Item {
property alias contentItem: contentItem property alias contentItem: contentItem
property color backgroundGradientColorStart: "#222222" property color backgroundGradientColorStart: "#222222"
property color backgroundGradientColorEnd: "#999999" property color backgroundGradientColorEnd: "#999999"
property color gridColor: "#aaaaaa"
enum SelectionMode { Item, Group } enum SelectionMode { Item, Group }
enum TransformMode { Move, Rotate, Scale } enum TransformMode { Move, Rotate, Scale }
@@ -100,12 +101,14 @@ Item {
{"usePerspective": usePerspective, {"usePerspective": usePerspective,
"showSceneLight": showEditLight, "showSceneLight": showEditLight,
"showGrid": showGrid, "showGrid": showGrid,
"gridColor": gridColor,
"importScene": activeScene, "importScene": activeScene,
"cameraZoomFactor": cameraControl._zoomFactor, "cameraZoomFactor": cameraControl._zoomFactor,
"z": 1}); "z": 1});
editView.usePerspective = Qt.binding(function() {return usePerspective;}); editView.usePerspective = Qt.binding(function() {return usePerspective;});
editView.showSceneLight = Qt.binding(function() {return showEditLight;}); editView.showSceneLight = Qt.binding(function() {return showEditLight;});
editView.showGrid = Qt.binding(function() {return showGrid;}); editView.showGrid = Qt.binding(function() {return showGrid;});
editView.gridColor = Qt.binding(function() {return gridColor;});
editView.cameraZoomFactor = Qt.binding(function() {return cameraControl._zoomFactor;}); editView.cameraZoomFactor = Qt.binding(function() {return cameraControl._zoomFactor;});
selectionBoxes.length = 0; selectionBoxes.length = 0;
@@ -211,12 +214,21 @@ Item {
function updateViewStates(viewStates) function updateViewStates(viewStates)
{ {
if ("selectBackgroundColor" in viewStates) { if ("selectBackgroundColor" in viewStates) {
if (Array.isArray(viewStates.selectBackgroundColor)) {
var colors = viewStates.selectBackgroundColor
backgroundGradientColorStart = colors[0];
backgroundGradientColorEnd = colors[1];
} else {
var color = viewStates.selectBackgroundColor var color = viewStates.selectBackgroundColor
backgroundGradientColorStart = color[0]; backgroundGradientColorStart = color;
backgroundGradientColorEnd = color[1]; backgroundGradientColorEnd = color;
} }
} }
if ("selectGridColor" in viewStates)
viewRoot.gridColor = viewStates.selectGridColor
}
// If resetToDefault is true, tool states not specifically set to anything will be reset to // If resetToDefault is true, tool states not specifically set to anything will be reset to
// their default state. // their default state.
function updateToolStates(toolStates, resetToDefault) function updateToolStates(toolStates, resetToDefault)

View File

@@ -33,6 +33,7 @@ Node {
property alias lines: gridGeometry.lines property alias lines: gridGeometry.lines
property alias step: gridGeometry.step property alias step: gridGeometry.step
property alias subdivAlpha: subGridMaterial.opacity property alias subdivAlpha: subGridMaterial.opacity
property alias gridColor: mainGridMaterial.diffuseColor
eulerRotation.x: 90 eulerRotation.x: 90

View File

@@ -45,10 +45,6 @@ View3D {
Node { Node {
DirectionalLight { DirectionalLight {
shadowMapQuality: Light.ShadowMapQualityMedium
shadowFilter: 20
shadowFactor: 21
castsShadow: true
eulerRotation.x: -26 eulerRotation.x: -26
eulerRotation.y: -57 eulerRotation.y: -57
} }
@@ -70,24 +66,5 @@ View3D {
materials: previewMaterial materials: previewMaterial
} }
Model {
id: floorModel
source: "#Rectangle"
scale.y: 8
scale.x: 8
eulerRotation.x: -90
materials: floorMaterial
DefaultMaterial {
id: floorMaterial
diffuseMap: floorTex
Texture {
id: floorTex
source: "../images/floor_tex.png"
scaleU: floorModel.scale.x
scaleV: floorModel.scale.y
}
}
}
} }
} }

View File

@@ -126,6 +126,50 @@ Item {
GradientStop { position: 1.0; color: "#222222" } GradientStop { position: 1.0; color: "#222222" }
GradientStop { position: 0.0; color: "#999999" } GradientStop { position: 0.0; color: "#999999" }
} }
// Use View3D instead of static image to make background look good on all resolutions
View3D {
anchors.fill: parent
environment: sceneEnv
SceneEnvironment {
id: sceneEnv
antialiasingMode: SceneEnvironment.MSAA
antialiasingQuality: SceneEnvironment.High
}
DirectionalLight {
eulerRotation.x: -26
eulerRotation.y: -57
}
PerspectiveCamera {
y: 125
z: 120
eulerRotation.x: -31
clipNear: 1
clipFar: 1000
}
Model {
id: floorModel
source: "#Rectangle"
scale.y: 8
scale.x: 8
eulerRotation.x: -90
materials: floorMaterial
DefaultMaterial {
id: floorMaterial
diffuseMap: floorTex
Texture {
id: floorTex
source: "../images/floor_tex.png"
scaleU: floorModel.scale.x
scaleV: floorModel.scale.y
}
}
}
}
} }
} }
} }

View File

@@ -37,7 +37,7 @@ View3D {
function fitToViewPort(closeUp) function fitToViewPort(closeUp)
{ {
// The magic number is the distance from camera default pos to origin // The magic number is the distance from camera default pos to origin
_generalHelper.calculateNodeBoundsAndFocusCamera(theCamera, sourceModel, root, _generalHelper.calculateNodeBoundsAndFocusCamera(theCamera, model, root,
1040, closeUp); 1040, closeUp);
} }
@@ -70,7 +70,7 @@ View3D {
materials: [ materials: [
DefaultMaterial { DefaultMaterial {
diffuseColor: "#4aee45" diffuseColor: "#999999"
} }
] ]
} }

View File

@@ -32,6 +32,7 @@ View3D {
property bool usePerspective: false property bool usePerspective: false
property alias showSceneLight: sceneLight.visible property alias showSceneLight: sceneLight.visible
property alias showGrid: helperGrid.visible property alias showGrid: helperGrid.visible
property alias gridColor: helperGrid.gridColor
property alias sceneHelpers: sceneHelpers property alias sceneHelpers: sceneHelpers
property alias perspectiveCamera: scenePerspectiveCamera property alias perspectiveCamera: scenePerspectiveCamera
property alias orthoCamera: sceneOrthoCamera property alias orthoCamera: sceneOrthoCamera

View File

@@ -812,7 +812,7 @@ QVector3D GeneralHelper::pivotScenePosition(QQuick3DNode *node) const
// Calculate bounds for given node, including all child nodes. // Calculate bounds for given node, including all child nodes.
// Returns true if the tree contains at least one Model node. // Returns true if the tree contains at least one Model node.
bool GeneralHelper::getBounds(QQuick3DViewport *view3D, QQuick3DNode *node, QVector3D &minBounds, bool GeneralHelper::getBounds(QQuick3DViewport *view3D, QQuick3DNode *node, QVector3D &minBounds,
QVector3D &maxBounds, bool recursive) QVector3D &maxBounds)
{ {
if (!node) { if (!node) {
const float halfExtent = 100.f; const float halfExtent = 100.f;
@@ -825,7 +825,7 @@ bool GeneralHelper::getBounds(QQuick3DViewport *view3D, QQuick3DNode *node, QVec
auto nodePriv = QQuick3DObjectPrivate::get(node); auto nodePriv = QQuick3DObjectPrivate::get(node);
auto renderNode = static_cast<QSSGRenderNode *>(nodePriv->spatialNode); auto renderNode = static_cast<QSSGRenderNode *>(nodePriv->spatialNode);
if (recursive && renderNode) { if (renderNode) {
#if QT_VERSION < QT_VERSION_CHECK(6, 4, 0) #if QT_VERSION < QT_VERSION_CHECK(6, 4, 0)
if (renderNode->flags.testFlag(QSSGRenderNode::Flag::TransformDirty)) if (renderNode->flags.testFlag(QSSGRenderNode::Flag::TransformDirty))
renderNode->calculateLocalTransform(); renderNode->calculateLocalTransform();
@@ -850,7 +850,7 @@ bool GeneralHelper::getBounds(QQuick3DViewport *view3D, QQuick3DNode *node, QVec
if (auto childNode = qobject_cast<QQuick3DNode *>(child)) { if (auto childNode = qobject_cast<QQuick3DNode *>(child)) {
QVector3D newMinBounds = minBounds; QVector3D newMinBounds = minBounds;
QVector3D newMaxBounds = maxBounds; QVector3D newMaxBounds = maxBounds;
bool childHasModel = getBounds(view3D, childNode, newMinBounds, newMaxBounds, true); bool childHasModel = getBounds(view3D, childNode, newMinBounds, newMaxBounds);
// Ignore any subtrees that do not have Model in them as we don't need those // Ignore any subtrees that do not have Model in them as we don't need those
// for visual bounds calculations // for visual bounds calculations
if (childHasModel) { if (childHasModel) {

View File

@@ -130,7 +130,7 @@ private:
void handlePendingToolStateUpdate(); void handlePendingToolStateUpdate();
QVector3D pivotScenePosition(QQuick3DNode *node) const; QVector3D pivotScenePosition(QQuick3DNode *node) const;
bool getBounds(QQuick3DViewport *view3D, QQuick3DNode *node, QVector3D &minBounds, bool getBounds(QQuick3DViewport *view3D, QQuick3DNode *node, QVector3D &minBounds,
QVector3D &maxBounds, bool recursive = false); QVector3D &maxBounds);
QTimer m_overlayUpdateTimer; QTimer m_overlayUpdateTimer;
QTimer m_toolStateUpdateTimer; QTimer m_toolStateUpdateTimer;

View File

@@ -198,8 +198,7 @@ void IconRenderer::finishCreateIcon()
render(saveFile); render(saveFile);
// Allow little time for file operations to finish QTimer::singleShot(0, qGuiApp, &QGuiApplication::quit);
QTimer::singleShot(1000, qGuiApp, &QGuiApplication::quit);
} }
void IconRenderer::render(const QString &fileName) void IconRenderer::render(const QString &fileName)

View File

@@ -848,6 +848,18 @@ void Qt5InformationNodeInstanceServer::updateActiveSceneToEditView3D(bool timerC
m_activeSceneIdUpdateTimer.stop(); m_activeSceneIdUpdateTimer.stop();
} }
// We may have to substitute another scene to work around QTBUG-103316
// The worked around issue is that if a material is used in multiple scenes, there is some
// kind of ownership for it in the first View3D that uses it, so if that view is not rendered
// first, the material will not be properly initialized for other views using it.
// To make materials work properly, we ensure that views are rendered at least once in the
// order they appear in the scene.
if (!m_priorityView3DsToRender.isEmpty()) {
QObject *sceneRoot = find3DSceneRoot(m_priorityView3DsToRender.first());
if (sceneRoot)
activeSceneVar = objectToVariant(sceneRoot);
}
QMetaObject::invokeMethod(m_editView3DData.rootItem, "setActiveScene", Qt::QueuedConnection, QMetaObject::invokeMethod(m_editView3DData.rootItem, "setActiveScene", Qt::QueuedConnection,
Q_ARG(QVariant, activeSceneVar), Q_ARG(QVariant, activeSceneVar),
Q_ARG(QVariant, QVariant::fromValue(sceneId))); Q_ARG(QVariant, QVariant::fromValue(sceneId)));
@@ -1012,7 +1024,7 @@ void Qt5InformationNodeInstanceServer::doRender3DEditView()
// If we have only one or no render queued, send the result to the creator side. // If we have only one or no render queued, send the result to the creator side.
// Otherwise, we'll hold on that until we have rendered all pending frames to ensure sent // Otherwise, we'll hold on that until we have rendered all pending frames to ensure sent
// results are correct. // results are correct.
if (m_need3DEditViewRender <= 1) { if (m_priorityView3DsToRender.isEmpty() && m_need3DEditViewRender <= 1) {
nodeInstanceClient()->handlePuppetToCreatorCommand({PuppetToCreatorCommand::Render3DView, nodeInstanceClient()->handlePuppetToCreatorCommand({PuppetToCreatorCommand::Render3DView,
QVariant::fromValue(imgContainer)}); QVariant::fromValue(imgContainer)});
#ifdef QUICK3D_PARTICLES_MODULE #ifdef QUICK3D_PARTICLES_MODULE
@@ -1023,6 +1035,25 @@ void Qt5InformationNodeInstanceServer::doRender3DEditView()
#endif #endif
} }
if (!m_priorityView3DsToRender.isEmpty()) {
static int tryCounter = 0;
QObject *sceneRoot = find3DSceneRoot(m_priorityView3DsToRender.first());
bool needAnotherRender = false;
if (sceneRoot) {
// Active scene is updated asynchronously, so verify we are actually rendering
// the correct priority scene
QObject *activeScene = QQmlProperty::read(m_editView3DData.rootItem, "activeScene").value<QObject *>();
needAnotherRender = activeScene != sceneRoot;
}
if (!needAnotherRender || ++tryCounter > 10) {
m_priorityView3DsToRender.removeFirst();
updateActiveSceneToEditView3D();
tryCounter = 0;
}
++m_need3DEditViewRender;
}
if (m_need3DEditViewRender > 0) { if (m_need3DEditViewRender > 0) {
// We queue another render even if the requested render count was one, because another // We queue another render even if the requested render count was one, because another
// render is needed to ensure gizmo geometries are properly updated. // render is needed to ensure gizmo geometries are properly updated.
@@ -1059,6 +1090,13 @@ void Qt5InformationNodeInstanceServer::doRenderModelNodeImageView()
{ {
// This crashes on Qt 6.0.x due to QtQuick3D issue, so the preview generation is disabled // This crashes on Qt 6.0.x due to QtQuick3D issue, so the preview generation is disabled
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) || QT_VERSION >= QT_VERSION_CHECK(6, 1, 0) #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) || QT_VERSION >= QT_VERSION_CHECK(6, 1, 0)
if (!m_priorityView3DsToRender.isEmpty()) {
// Postpone any preview renders until we have rendered the priority views to ensure
// materials in material library are properly initialized
m_renderModelNodeImageViewTimer.start(17);
return;
}
RequestModelNodePreviewImageCommand cmd = *m_modelNodePreviewImageCommands.begin(); RequestModelNodePreviewImageCommand cmd = *m_modelNodePreviewImageCommands.begin();
ServerNodeInstance instance; ServerNodeInstance instance;
if (cmd.renderItemId() >= 0) if (cmd.renderItemId() >= 0)
@@ -1578,6 +1616,8 @@ void Qt5InformationNodeInstanceServer::add3DViewPorts(const QList<ServerNodeInst
for (const ServerNodeInstance &instance : instanceList) { for (const ServerNodeInstance &instance : instanceList) {
if (instance.isSubclassOf("QQuick3DViewport")) { if (instance.isSubclassOf("QQuick3DViewport")) {
QObject *obj = instance.internalObject(); QObject *obj = instance.internalObject();
if (!m_editView3DSetupDone)
m_priorityView3DsToRender.append(obj); // Workaround for quick3d bug QTBUG-103316
if (!m_view3Ds.contains(obj)) { if (!m_view3Ds.contains(obj)) {
m_view3Ds << obj; m_view3Ds << obj;
QObject::connect(obj, SIGNAL(widthChanged()), this, SLOT(handleView3DSizeChange())); QObject::connect(obj, SIGNAL(widthChanged()), this, SLOT(handleView3DSizeChange()));
@@ -1746,7 +1786,7 @@ QObject *Qt5InformationNodeInstanceServer::find3DSceneRoot(QObject *obj) const
} }
void Qt5InformationNodeInstanceServer::setup3DEditView(const QList<ServerNodeInstance> &instanceList, void Qt5InformationNodeInstanceServer::setup3DEditView(const QList<ServerNodeInstance> &instanceList,
const QHash<QString, QVariantMap> &toolStates) const CreateSceneCommand &command)
{ {
#ifdef QUICK3D_MODULE #ifdef QUICK3D_MODULE
if (!m_editView3DData.rootItem) if (!m_editView3DData.rootItem)
@@ -1779,6 +1819,7 @@ void Qt5InformationNodeInstanceServer::setup3DEditView(const QList<ServerNodeIns
Qt5InformationNodeInstanceServer::updateActiveSceneToEditView3D(true); Qt5InformationNodeInstanceServer::updateActiveSceneToEditView3D(true);
}); });
const QHash<QString, QVariantMap> &toolStates = command.edit3dToolStates;
QString lastSceneId; QString lastSceneId;
auto helper = qobject_cast<QmlDesigner::Internal::GeneralHelper *>(m_3dHelper); auto helper = qobject_cast<QmlDesigner::Internal::GeneralHelper *>(m_3dHelper);
if (helper) { if (helper) {
@@ -1832,11 +1873,23 @@ void Qt5InformationNodeInstanceServer::setup3DEditView(const QList<ServerNodeIns
createCameraAndLightGizmos(instanceList); createCameraAndLightGizmos(instanceList);
if (!command.edit3dBackgroundColor.isEmpty()) {
View3DActionCommand backgroundColorCommand(View3DActionCommand::SelectBackgroundColor,
QVariant::fromValue(command.edit3dBackgroundColor));
view3DAction(backgroundColorCommand);
}
if (command.edit3dGridColor.isValid()) {
View3DActionCommand backgroundColorCommand(View3DActionCommand::SelectGridColor,
QVariant::fromValue(command.edit3dGridColor));
view3DAction(backgroundColorCommand);
}
// Queue two renders to make sure icon gizmos update properly // Queue two renders to make sure icon gizmos update properly
render3DEditView(2); render3DEditView(2);
#else #else
Q_UNUSED(instanceList) Q_UNUSED(instanceList)
Q_UNUSED(toolStates) Q_UNUSED(command)
#endif #endif
} }
@@ -1958,7 +2011,7 @@ void Qt5InformationNodeInstanceServer::createScene(const CreateSceneCommand &com
nodeInstanceClient()->componentCompleted(createComponentCompletedCommand(instanceList)); nodeInstanceClient()->componentCompleted(createComponentCompletedCommand(instanceList));
if (ViewConfig::isQuick3DMode()) { if (ViewConfig::isQuick3DMode()) {
setup3DEditView(instanceList, command.edit3dToolStates); setup3DEditView(instanceList, command);
updateRotationBlocks(command.auxiliaryChanges); updateRotationBlocks(command.auxiliaryChanges);
} }
@@ -1967,12 +2020,6 @@ void Qt5InformationNodeInstanceServer::createScene(const CreateSceneCommand &com
#ifdef IMPORT_QUICK3D_ASSETS #ifdef IMPORT_QUICK3D_ASSETS
QTimer::singleShot(0, this, &Qt5InformationNodeInstanceServer::resolveImportSupport); QTimer::singleShot(0, this, &Qt5InformationNodeInstanceServer::resolveImportSupport);
#endif #endif
if (!command.edit3dBackgroundColor.isEmpty()) {
View3DActionCommand backgroundColorCommand(View3DActionCommand::SelectBackgroundColor,
QVariant::fromValue(command.edit3dBackgroundColor));
view3DAction(backgroundColorCommand);
}
} }
void Qt5InformationNodeInstanceServer::sendChildrenChangedCommand(const QList<ServerNodeInstance> &childList) void Qt5InformationNodeInstanceServer::sendChildrenChangedCommand(const QList<ServerNodeInstance> &childList)
@@ -2237,9 +2284,12 @@ void Qt5InformationNodeInstanceServer::view3DAction(const View3DActionCommand &c
case View3DActionCommand::ShowCameraFrustum: case View3DActionCommand::ShowCameraFrustum:
updatedToolState.insert("showCameraFrustum", command.isEnabled()); updatedToolState.insert("showCameraFrustum", command.isEnabled());
break; break;
case View3DActionCommand::SelectBackgroundColor: { case View3DActionCommand::SelectBackgroundColor:
updatedViewState.insert("selectBackgroundColor", command.value()); updatedViewState.insert("selectBackgroundColor", command.value());
break; break;
case View3DActionCommand::SelectGridColor: {
updatedViewState.insert("selectGridColor", command.value());
break;
} }
#ifdef QUICK3D_PARTICLES_MODULE #ifdef QUICK3D_PARTICLES_MODULE
case View3DActionCommand::ShowParticleEmitter: case View3DActionCommand::ShowParticleEmitter:

View File

@@ -114,7 +114,7 @@ private:
void createEditView3D(); void createEditView3D();
void create3DPreviewView(); void create3DPreviewView();
void setup3DEditView(const QList<ServerNodeInstance> &instanceList, void setup3DEditView(const QList<ServerNodeInstance> &instanceList,
const QHash<QString, QVariantMap> &toolStates); const CreateSceneCommand &command);
void createCameraAndLightGizmos(const QList<ServerNodeInstance> &instanceList) const; void createCameraAndLightGizmos(const QList<ServerNodeInstance> &instanceList) const;
void add3DViewPorts(const QList<ServerNodeInstance> &instanceList); void add3DViewPorts(const QList<ServerNodeInstance> &instanceList);
void add3DScenes(const QList<ServerNodeInstance> &instanceList); void add3DScenes(const QList<ServerNodeInstance> &instanceList);
@@ -165,6 +165,7 @@ private:
QSet<QObject *> m_view3Ds; QSet<QObject *> m_view3Ds;
QMultiHash<QObject *, QObject *> m_3DSceneMap; // key: scene root, value: node QMultiHash<QObject *, QObject *> m_3DSceneMap; // key: scene root, value: node
QObject *m_active3DView = nullptr; QObject *m_active3DView = nullptr;
QList<QObject *> m_priorityView3DsToRender;
QObject *m_active3DScene = nullptr; QObject *m_active3DScene = nullptr;
QSet<ServerNodeInstance> m_parentChangedSet; QSet<ServerNodeInstance> m_parentChangedSet;
QList<ServerNodeInstance> m_completedComponentList; QList<ServerNodeInstance> m_completedComponentList;

View File

@@ -405,6 +405,7 @@ QImage Qt5NodeInstanceServer::grabItem(QQuickItem *item)
QQuickItemPrivate *pItem = QQuickItemPrivate::get(item); QQuickItemPrivate *pItem = QQuickItemPrivate::get(item);
const bool renderEffects = qEnvironmentVariableIsSet("QMLPUPPET_RENDER_EFFECTS"); const bool renderEffects = qEnvironmentVariableIsSet("QMLPUPPET_RENDER_EFFECTS");
const bool smoothRendering = qEnvironmentVariableIsSet("QMLPUPPET_SMOOTH_RENDERING");
if (renderEffects) { if (renderEffects) {
if (parentEffectItem(item)) if (parentEffectItem(item))
@@ -427,17 +428,20 @@ QImage Qt5NodeInstanceServer::grabItem(QQuickItem *item)
ServerNodeInstance instance = instanceForObject(item); ServerNodeInstance instance = instanceForObject(item);
const bool rootIs3DObject = rootIsRenderable3DObject();
// Setting layer enabled to false messes up the bounding rect. // Setting layer enabled to false messes up the bounding rect.
// Therefore we calculate it upfront. // Therefore we calculate it upfront.
QRectF renderBoundingRect; QRectF renderBoundingRect;
if (instance.isValid()) if (instance.isValid())
renderBoundingRect = instance.boundingRect(); renderBoundingRect = instance.boundingRect();
else if (rootIs3DObject)
else if (rootIsRenderable3DObject())
renderBoundingRect = item->boundingRect(); renderBoundingRect = item->boundingRect();
else else
renderBoundingRect = ServerNodeInstance::effectAdjustedBoundingRect(item); renderBoundingRect = ServerNodeInstance::effectAdjustedBoundingRect(item);
const int scaleFactor = (smoothRendering && !rootIs3DObject) ? 2 : 1;
// Hide immediate children that have instances and are QQuickItems so we get only // Hide immediate children that have instances and are QQuickItems so we get only
// the parent item's content, as compositing is handled on creator side. // the parent item's content, as compositing is handled on creator side.
QSet<QQuickItem *> layerChildren; QSet<QQuickItem *> layerChildren;
@@ -470,6 +474,8 @@ QImage Qt5NodeInstanceServer::grabItem(QQuickItem *item)
// us to render it to a texture that we can grab to an image. // us to render it to a texture that we can grab to an image.
QSGRenderContext *rc = QQuickWindowPrivate::get(m_viewData.window.data())->context; QSGRenderContext *rc = QQuickWindowPrivate::get(m_viewData.window.data())->context;
QSGLayer *layer = rc->sceneGraphContext()->createLayer(rc); QSGLayer *layer = rc->sceneGraphContext()->createLayer(rc);
if (smoothRendering)
layer->setSamples(4);
layer->setItem(pItem->itemNode()); layer->setItem(pItem->itemNode());
layer->setRect(QRectF(renderBoundingRect.x(), layer->setRect(QRectF(renderBoundingRect.x(),
@@ -478,8 +484,8 @@ QImage Qt5NodeInstanceServer::grabItem(QQuickItem *item)
-renderBoundingRect.height())); -renderBoundingRect.height()));
const QSize minSize = rc->sceneGraphContext()->minimumFBOSize(); const QSize minSize = rc->sceneGraphContext()->minimumFBOSize();
layer->setSize(QSize(qMax(minSize.width(), int(renderBoundingRect.width())), layer->setSize(QSize(qMax(minSize.width(), int(renderBoundingRect.width() * scaleFactor)),
qMax(minSize.height(), int(renderBoundingRect.height())))); qMax(minSize.height(), int(renderBoundingRect.height() * scaleFactor))));
layer->scheduleUpdate(); layer->scheduleUpdate();
if (layer->updateTexture()) if (layer->updateTexture())
@@ -489,6 +495,8 @@ QImage Qt5NodeInstanceServer::grabItem(QQuickItem *item)
delete layer; delete layer;
layer = nullptr; layer = nullptr;
renderImage.setDevicePixelRatio(scaleFactor);
}); });
m_viewData.renderControl->render(); m_viewData.renderControl->render();
@@ -514,7 +522,6 @@ QImage Qt5NodeInstanceServer::grabItem(QQuickItem *item)
if (!isLayerEnabled(pItem)) if (!isLayerEnabled(pItem))
pItem->derefFromEffectItem(false); pItem->derefFromEffectItem(false);
#else #else
Q_UNUSED(item) Q_UNUSED(item)
#endif #endif

View File

@@ -111,8 +111,6 @@ void Qt5RenderNodeInstanceServer::collectItemChangesAndSendChangeCommands()
} }
} }
clearChangedPropertyList();
if (Internal::QuickItemNodeInstance::unifiedRenderPath()) { if (Internal::QuickItemNodeInstance::unifiedRenderPath()) {
if (windowDirty) if (windowDirty)
nodeInstanceClient()->pixmapChanged(createPixmapChangedCommand({rootNodeInstance()})); nodeInstanceClient()->pixmapChanged(createPixmapChangedCommand({rootNodeInstance()}));
@@ -134,13 +132,15 @@ void Qt5RenderNodeInstanceServer::collectItemChangesAndSendChangeCommands()
} }
if (rootIsRenderable3DObject() && rootNodeInstance().contentItem() if (rootIsRenderable3DObject() && rootNodeInstance().contentItem()
&& DesignerSupport::isDirty(rootNodeInstance().contentItem(), && !changedPropertyList().isEmpty()
DesignerSupport::AllMask)
&& nodeInstanceClient()->bytesToWrite() < 10000) { && nodeInstanceClient()->bytesToWrite() < 10000) {
Internal::QuickItemNodeInstance::updateDirtyNode(rootNodeInstance().contentItem()); Internal::QuickItemNodeInstance::updateDirtyNode(rootNodeInstance().contentItem());
nodeInstanceClient()->pixmapChanged(createPixmapChangedCommand({rootNodeInstance()})); nodeInstanceClient()->pixmapChanged(createPixmapChangedCommand({rootNodeInstance()}));
} }
clearChangedPropertyList();
inFunction = false; inFunction = false;
} }
} }

View File

@@ -504,12 +504,12 @@ QImage QuickItemNodeInstance::renderImage() const
if (s_unifiedRenderPath) { if (s_unifiedRenderPath) {
renderImage = nodeInstanceServer()->grabWindow(); renderImage = nodeInstanceServer()->grabWindow();
renderImage = renderImage.copy(renderBoundingRect.toRect()); renderImage = renderImage.copy(renderBoundingRect.toRect());
/* When grabbing an offscren window the device pixel ratio is 1 */
renderImage.setDevicePixelRatio(1);
} else { } else {
renderImage = nodeInstanceServer()->grabItem(quickItem()); renderImage = nodeInstanceServer()->grabItem(quickItem());
} }
/* When grabbing an offscren window the device pixel ratio is 1 */
renderImage.setDevicePixelRatio(1);
#endif #endif
return renderImage; return renderImage;

View File

@@ -128,7 +128,7 @@ Item {
StudioControls.MenuSeparator {} StudioControls.MenuSeparator {}
StudioControls.MenuItem { StudioControls.MenuItem {
text: qsTr("New Material") text: qsTr("Create New Material")
onTriggered: materialBrowserModel.addNewMaterial() onTriggered: materialBrowserModel.addNewMaterial()
} }
@@ -169,7 +169,8 @@ Item {
} }
Text { Text {
text: qsTr("No materials yet.\nClick '+' above to start.") text: qsTr("There are no materials in this project.<br>Select '<b>+</b>' to create one.")
textFormat: Text.RichText
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
font.pixelSize: StudioTheme.Values.mediumFontSize font.pixelSize: StudioTheme.Values.mediumFontSize
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
@@ -179,7 +180,8 @@ Item {
} }
Text { Text {
text: qsTr("Add QtQuick3D module using the Components view to enable the Material Browser."); text: qsTr("To use <b>Material Browser</b>, first add the QtQuick3D module in the <b>Components</b> view.");
textFormat: Text.RichText
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
font.pixelSize: StudioTheme.Values.mediumFontSize font.pixelSize: StudioTheme.Values.mediumFontSize
topPadding: 30 topPadding: 30

View File

@@ -72,9 +72,12 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: (mouse) => { onPressed: (mouse) => {
materialBrowserModel.selectMaterial(index) materialBrowserModel.selectMaterial(index)
if (mouse.button === Qt.RightButton)
if (mouse.button === Qt.LeftButton)
rootView.startDragMaterial(index, mapToGlobal(mouse.x, mouse.y))
else if (mouse.button === Qt.RightButton)
root.showContextMenu() root.showContextMenu()
} }

View File

@@ -48,8 +48,9 @@ PropertyEditorPane {
height: 150 height: 150
Text { Text {
text: hasQuick3DImport ? qsTr("No materials yet.\nClick '+' above to start.") text: hasQuick3DImport ? qsTr("There are no materials in this project.<br>Select '<b>+</b>' to create one.")
: qsTr("Add QtQuick3D module using the Components view to enable the Material Editor.") : qsTr("To use <b>Material Editor</b>, first add the QtQuick3D module in the <b>Components</b> view.")
textFormat: Text.RichText
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
font.pixelSize: StudioTheme.Values.mediumFontSize font.pixelSize: StudioTheme.Values.mediumFontSize
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter

View File

@@ -63,7 +63,7 @@ Rectangle {
buttonSize: root.height buttonSize: root.height
enabled: hasQuick3DImport enabled: hasQuick3DImport
onClicked: root.toolBarAction(ToolBarAction.AddNewMaterial) onClicked: root.toolBarAction(ToolBarAction.AddNewMaterial)
tooltip: qsTr("Add a new material.") tooltip: qsTr("Create new material.")
} }
IconButton { IconButton {

View File

@@ -33,7 +33,7 @@ PropertyEditorPane {
id: itemPane id: itemPane
ComponentSection { ComponentSection {
showState: majorVersion >= 6 showState: majorQtQuickVersion >= 6
} }
Column { Column {

View File

@@ -1,6 +1,6 @@
/**************************************************************************** /****************************************************************************
** **
** Copyright (C) 2021 The Qt Company Ltd. ** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/ ** Contact: https://www.qt.io/licensing/
** **
** This file is part of Qt Creator. ** This file is part of Qt Creator.
@@ -36,6 +36,7 @@ StudioControls.ComboBox {
property string fontFilter: "*.ttf *.otf" property string fontFilter: "*.ttf *.otf"
property bool showExtendedFunctionButton: true property bool showExtendedFunctionButton: true
hasActiveDrag: activeDragSuffix !== "" && root.fontFilter.includes(activeDragSuffix)
labelColor: colorLogic.textColor labelColor: colorLogic.textColor
editable: true editable: true
@@ -47,16 +48,45 @@ StudioControls.ComboBox {
filter: root.fontFilter filter: root.fontFilter
} }
DropArea {
id: dropArea
anchors.fill: parent
property string assetPath: ""
onEntered: function(drag) {
dropArea.assetPath = drag.getDataAsString(drag.keys[0]).split(",")[0]
drag.accepted = root.hasActiveDrag
root.hasActiveHoverDrag = drag.accepted
}
onExited: root.hasActiveHoverDrag = false
onDropped: function(drop) {
drop.accepted = root.hasActiveHoverDrag
var fontLoader = root.createFontLoader("file:" + dropArea.assetPath)
if (fontLoader.status === FontLoader.Ready) {
root.backendValue.value = fontLoader.name
root.currentIndex = root.find(root.backendValue.value)
}
root.hasActiveHoverDrag = false
root.backendValue.commitDrop(dropArea.assetPath)
}
}
function createFontLoader(fontUrl) { function createFontLoader(fontUrl) {
return Qt.createQmlObject('import QtQuick 2.0; FontLoader { source: "' + fontUrl + '"; }', return Qt.createQmlObject('import QtQuick 2.0; FontLoader { source: "' + fontUrl + '"; }',
root, "dynamicFontLoader") root, "dynamicFontLoader")
} }
function setupModel() { function setupModel() {
var familyNames = ["Arial", "Times New Roman", "Courier", "Verdana", "Tahoma"] // default fonts // default fonts
var familyNames = ["Arial", "Times New Roman", "Courier", "Verdana", "Tahoma"]
for (var i = 0; i < fileModel.fullPathModel.length; ++i) { // add custom fonts for (var i = 0; i < fileModel.model.length; ++i) { // add custom fonts
var fontLoader = createFontLoader(fileModel.docPath + "/" + fileModel.fullPathModel[i]) var fontLoader = root.createFontLoader(fileModel.docPath + "/"
+ fileModel.model[i].relativeFilePath)
familyNames.push(fontLoader.name) familyNames.push(fontLoader.name)
} }

View File

@@ -34,10 +34,10 @@ Rectangle {
signal clicked() signal clicked()
property alias icon: icon.text property alias icon: icon.text
property alias enabled: mouseArea.enabled
property alias tooltip: toolTip.text property alias tooltip: toolTip.text
property alias iconSize: icon.font.pixelSize property alias iconSize: icon.font.pixelSize
property bool enabled: true
property int buttonSize: StudioTheme.Values.height property int buttonSize: StudioTheme.Values.height
property color normalColor: StudioTheme.Values.themeControlBackground property color normalColor: StudioTheme.Values.themeControlBackground
property color hoverColor: StudioTheme.Values.themeControlBackgroundHover property color hoverColor: StudioTheme.Values.themeControlBackgroundHover
@@ -46,7 +46,8 @@ Rectangle {
width: buttonSize width: buttonSize
height: buttonSize height: buttonSize
color: mouseArea.pressed ? pressColor color: !enabled ? normalColor
: mouseArea.pressed ? pressColor
: mouseArea.containsMouse ? hoverColor : mouseArea.containsMouse ? hoverColor
: normalColor : normalColor
@@ -71,7 +72,11 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
onClicked: root.clicked() onClicked: {
// We need to keep mouse area enabled even when button is disabled to make tooltip work
if (root.enabled)
root.clicked()
}
} }
ToolTip { ToolTip {

View File

@@ -1,6 +1,6 @@
/**************************************************************************** /****************************************************************************
** **
** Copyright (C) 2021 The Qt Company Ltd. ** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/ ** Contact: https://www.qt.io/licensing/
** **
** This file is part of Qt Creator. ** This file is part of Qt Creator.
@@ -44,6 +44,9 @@ Row {
// by QtQuick3D to add built-in primitives to the model. // by QtQuick3D to add built-in primitives to the model.
property var defaultItems property var defaultItems
// Current item
property string absoluteFilePath: ""
FileResourcesModel { FileResourcesModel {
id: fileModel id: fileModel
modelNodeBackendProperty: modelNodeBackend modelNodeBackendProperty: modelNodeBackend
@@ -60,6 +63,7 @@ Row {
property ListModel listModel: ListModel {} property ListModel listModel: ListModel {}
hasActiveDrag: activeDragSuffix !== "" && root.filter.includes(activeDragSuffix)
implicitWidth: StudioTheme.Values.singleControlColumnWidth implicitWidth: StudioTheme.Values.singleControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth + StudioTheme.Values.actionIndicatorWidth
width: implicitWidth width: implicitWidth
@@ -69,26 +73,99 @@ Row {
// when the combobox is closed by focusing on some other control. // when the combobox is closed by focusing on some other control.
property int hoverIndex: -1 property int hoverIndex: -1
DropArea {
id: dropArea
anchors.fill: parent
property string assetPath: ""
onEntered: function(drag) {
dropArea.assetPath = drag.getDataAsString(drag.keys[0]).split(",")[0]
drag.accepted = comboBox.hasActiveDrag
comboBox.hasActiveHoverDrag = drag.accepted
}
onExited: comboBox.hasActiveHoverDrag = false
onDropped: function(drop) {
drop.accepted = comboBox.hasActiveHoverDrag
comboBox.editText = dropArea.assetPath
comboBox.accepted()
comboBox.hasActiveHoverDrag = false
root.backendValue.commitDrop(dropArea.assetPath)
}
}
ToolTip { ToolTip {
id: toolTip id: toolTip
visible: comboBox.hover && toolTip.text !== "" visible: comboBox.hover && toolTip.text !== ""
text: root.backendValue.valueToString text: root.backendValue.valueToString
delay: StudioTheme.Values.toolTipDelay delay: StudioTheme.Values.toolTipDelay
height: StudioTheme.Values.toolTipHeight
background: Rectangle { background: Rectangle {
color: StudioTheme.Values.themeToolTipBackground color: StudioTheme.Values.themeToolTipBackground
border.color: StudioTheme.Values.themeToolTipOutline border.color: StudioTheme.Values.themeToolTipOutline
border.width: StudioTheme.Values.border border.width: StudioTheme.Values.border
} }
contentItem: Text {
contentItem: RowLayout {
spacing: 10
Item {
visible: thumbnail.status === Image.Ready
Layout.preferredWidth: 100
Layout.preferredHeight: 100
Image {
id: checker
visible: !root.isMesh(root.absoluteFilePath)
anchors.fill: parent
fillMode: Image.Tile
source: "images/checkers.png"
}
Image {
id: thumbnail
asynchronous: true
anchors.fill: parent
fillMode: Image.PreserveAspectFit
source: {
if (root.isBuiltInPrimitive(root.absoluteFilePath))
return "image://qmldesigner_thumbnails/"
+ root.absoluteFilePath.substring(1, root.absoluteFilePath.length)
+ ".builtin"
if (fileModel.isLocal(root.absoluteFilePath))
return "image://qmldesigner_thumbnails/" + root.absoluteFilePath
return root.absoluteFilePath
}
}
}
ColumnLayout {
Text {
text: root.fileName(toolTip.text)
color: StudioTheme.Values.themeToolTipText color: StudioTheme.Values.themeToolTipText
text: toolTip.text font: toolTip.font
verticalAlignment: Text.AlignVCenter }
Text {
Layout.fillWidth: true
text: root.isBuiltInPrimitive(toolTip.text) ? qsTr("Built-in primitive")
: toolTip.text
font: toolTip.font
color: StudioTheme.Values.themeToolTipText
wrapMode: Text.WordWrap
}
}
} }
} }
delegate: ItemDelegate { delegate: ItemDelegate {
required property string fullPath required property string absoluteFilePath
required property string relativeFilePath
required property string name required property string name
required property int group required property int group
required property int index required property int index
@@ -150,20 +227,66 @@ Row {
} }
ToolTip { ToolTip {
id: itemToolTip id: delegateToolTip
visible: delegateRoot.hovered && comboBox.highlightedIndex === index visible: delegateRoot.hovered
text: fullPath text: delegateRoot.relativeFilePath
delay: StudioTheme.Values.toolTipDelay delay: StudioTheme.Values.toolTipDelay
height: StudioTheme.Values.toolTipHeight
background: Rectangle { background: Rectangle {
color: StudioTheme.Values.themeToolTipBackground color: StudioTheme.Values.themeToolTipBackground
border.color: StudioTheme.Values.themeToolTipOutline border.color: StudioTheme.Values.themeToolTipOutline
border.width: StudioTheme.Values.border border.width: StudioTheme.Values.border
} }
contentItem: Text {
contentItem: RowLayout {
spacing: 10
Item {
visible: delegateThumbnail.status === Image.Ready
Layout.preferredWidth: 100
Layout.preferredHeight: 100
Image {
id: delegateChecker
visible: !root.isMesh(delegateRoot.absoluteFilePath)
anchors.fill: parent
fillMode: Image.Tile
source: "images/checkers.png"
}
Image {
id: delegateThumbnail
asynchronous: true
anchors.fill: parent
fillMode: Image.PreserveAspectFit
source: {
if (root.isBuiltInPrimitive(delegateRoot.name))
return "image://qmldesigner_thumbnails/"
+ delegateRoot.name.substring(1, delegateRoot.name.length)
+ ".builtin"
return "image://qmldesigner_thumbnails/" + delegateRoot.absoluteFilePath
}
}
}
ColumnLayout {
Text {
text: delegateRoot.name
color: StudioTheme.Values.themeToolTipText color: StudioTheme.Values.themeToolTipText
text: itemToolTip.text font: delegateToolTip.font
verticalAlignment: Text.AlignVCenter }
Text {
Layout.fillWidth: true
text: root.isBuiltInPrimitive(delegateToolTip.text)
? qsTr("Built-in primitive")
: delegateToolTip.text
font: delegateToolTip.font
color: StudioTheme.Values.themeToolTipText
wrapMode: Text.WordWrap
}
}
} }
} }
} }
@@ -197,8 +320,9 @@ Row {
if (root.backendValue.isBound) { if (root.backendValue.isBound) {
comboBox.textValue = root.backendValue.expression comboBox.textValue = root.backendValue.expression
} else { } else {
var fullPath = root.backendValue.valueToString // Can be absolute or relative file path
comboBox.textValue = fullPath.substr(fullPath.lastIndexOf('/') + 1) var filePath = root.backendValue.valueToString
comboBox.textValue = filePath.substr(filePath.lastIndexOf('/') + 1)
} }
comboBox.setCurrentText(comboBox.textValue) comboBox.setCurrentText(comboBox.textValue)
@@ -230,9 +354,13 @@ Row {
// Check if value set by user matches with a name in the model then pick the full path // Check if value set by user matches with a name in the model then pick the full path
let index = comboBox.find(inputValue) let index = comboBox.find(inputValue)
if (index !== -1) if (index !== -1)
inputValue = comboBox.items.get(index).model.fullPath inputValue = comboBox.items.get(index).model.relativeFilePath
root.backendValue.value = inputValue root.backendValue.value = inputValue
if (!root.backendValue.isBound)
root.absoluteFilePath = fileModel.resolve(root.backendValue.value)
comboBox.dirty = false comboBox.dirty = false
} }
@@ -252,11 +380,14 @@ Row {
let inputValue = comboBox.editText let inputValue = comboBox.editText
if (index >= 0) if (index >= 0)
inputValue = comboBox.items.get(index).model.fullPath inputValue = comboBox.items.get(index).model.relativeFilePath
if (root.backendValue.value !== inputValue) if (root.backendValue.value !== inputValue)
root.backendValue.value = inputValue root.backendValue.value = inputValue
if (!root.backendValue.isBound)
root.absoluteFilePath = fileModel.resolve(root.backendValue.value)
comboBox.dirty = false comboBox.dirty = false
} }
@@ -273,6 +404,23 @@ Row {
} }
} }
function isBuiltInPrimitive(value) {
return value.startsWith('#')
}
function isMesh(value) {
return root.isBuiltInPrimitive(value)
|| root.hasFileExtension(root.fileName(value), "mesh")
}
function hasFileExtension(fileName, extension) {
return fileName.split('.').pop() === extension
}
function fileName(filePath) {
return filePath.substr(filePath.lastIndexOf('/') + 1)
}
function createModel() { function createModel() {
// Build the combobox model // Build the combobox model
comboBox.listModel.clear() comboBox.listModel.clear()
@@ -284,17 +432,22 @@ Row {
if (root.defaultItems !== undefined) { if (root.defaultItems !== undefined) {
for (var i = 0; i < root.defaultItems.length; ++i) { for (var i = 0; i < root.defaultItems.length; ++i) {
comboBox.listModel.append({ comboBox.listModel.append({
fullPath: root.defaultItems[i], absoluteFilePath: "",
relativeFilePath: root.defaultItems[i],
name: root.defaultItems[i], name: root.defaultItems[i],
group: 0 group: 0
}) })
} }
} }
for (var j = 0; j < fileModel.fullPathModel.length; ++j) { const myModel = fileModel.model
for (var j = 0; j < myModel.length; ++j) {
let item = myModel[j]
comboBox.listModel.append({ comboBox.listModel.append({
fullPath: fileModel.fullPathModel[j], absoluteFilePath: item.absoluteFilePath,
name: fileModel.fileNameModel[j], relativeFilePath: item.relativeFilePath,
name: item.fileName,
group: 1 group: 1
}) })
} }
@@ -304,7 +457,7 @@ Row {
Connections { Connections {
target: fileModel target: fileModel
function onFullPathModelChanged() { function onModelChanged() {
root.createModel() root.createModel()
comboBox.setCurrentText(comboBox.textValue) comboBox.setCurrentText(comboBox.textValue)
} }
@@ -315,6 +468,9 @@ Row {
Component.onCompleted: { Component.onCompleted: {
root.createModel() root.createModel()
comboBox.updateTextValue() comboBox.updateTextValue()
if (!root.backendValue.isBound)
root.absoluteFilePath = fileModel.resolve(root.backendValue.value)
} }
function indexOf(model, criteria) { function indexOf(model, criteria) {
@@ -333,7 +489,7 @@ Row {
if (comboBox.popup.opened && !root.backendValue.isBound) { if (comboBox.popup.opened && !root.backendValue.isBound) {
var index = root.indexOf(comboBox.items, var index = root.indexOf(comboBox.items,
function(item) { function(item) {
return item.fullPath === root.backendValue.value return item.relativeFilePath === root.backendValue.value
}) })
if (index !== -1) { if (index !== -1) {
@@ -352,8 +508,10 @@ Row {
iconColor: root.textColor iconColor: root.textColor
onClicked: { onClicked: {
fileModel.openFileDialog() fileModel.openFileDialog()
if (fileModel.fileName !== "") if (fileModel.fileName !== "") {
root.backendValue.value = fileModel.fileName root.backendValue.value = fileModel.fileName
root.absoluteFilePath = fileModel.resolve(root.backendValue.value)
}
} }
} }
} }

View File

@@ -136,7 +136,7 @@ T.AbstractButton {
when: myButton.globalHover && !myButton.hover && !myButton.pressed && myButton.enabled when: myButton.globalHover && !myButton.hover && !myButton.pressed && myButton.enabled
PropertyChanges { PropertyChanges {
target: buttonBackground target: buttonBackground
color: StudioTheme.Values.themeControlBackgroundGlobalHover color: StudioTheme.Values.themeControlBackground
} }
}, },
State { State {

View File

@@ -37,6 +37,9 @@ Rectangle {
property bool pressed: checkIndicatorMouseArea.containsPress property bool pressed: checkIndicatorMouseArea.containsPress
property bool checked: false property bool checked: false
property bool hasActiveDrag: myControl.hasActiveDrag ?? false
property bool hasActiveHoverDrag: myControl.hasActiveHoverDrag ?? false
color: StudioTheme.Values.themeControlBackground color: StudioTheme.Values.themeControlBackground
border.width: 0 border.width: 0
@@ -79,12 +82,20 @@ Rectangle {
name: "default" name: "default"
when: myControl.enabled && checkIndicator.enabled && !myControl.edit when: myControl.enabled && checkIndicator.enabled && !myControl.edit
&& !checkIndicator.hover && !myControl.hover && !myControl.drag && !checkIndicator.hover && !myControl.hover && !myControl.drag
&& !checkIndicator.checked && !checkIndicator.checked && !checkIndicator.hasActiveDrag
PropertyChanges { PropertyChanges {
target: checkIndicator target: checkIndicator
color: StudioTheme.Values.themeControlBackground color: StudioTheme.Values.themeControlBackground
} }
}, },
State {
name: "dragHover"
when: myControl.enabled && checkIndicator.hasActiveHoverDrag
PropertyChanges {
target: checkIndicator
color: StudioTheme.Values.themeControlBackgroundInteraction
}
},
State { State {
name: "globalHover" name: "globalHover"
when: myControl.enabled && checkIndicator.enabled && !myControl.drag when: myControl.enabled && checkIndicator.enabled && !myControl.drag

View File

@@ -50,9 +50,6 @@ T.ComboBox {
property alias textInput: comboBoxInput property alias textInput: comboBoxInput
property int borderWidth: myComboBox.hasActiveHoverDrag ? StudioTheme.Values.borderHover
: StudioTheme.Values.border
signal compressedActivated(int index, int reason) signal compressedActivated(int index, int reason)
enum ActivatedReason { EditingFinished, Other } enum ActivatedReason { EditingFinished, Other }
@@ -61,7 +58,7 @@ T.ComboBox {
height: StudioTheme.Values.defaultControlHeight height: StudioTheme.Values.defaultControlHeight
leftPadding: actionIndicator.width leftPadding: actionIndicator.width
rightPadding: popupIndicator.width + myComboBox.borderWidth rightPadding: popupIndicator.width + StudioTheme.Values.border
font.pixelSize: StudioTheme.Values.myFontSize font.pixelSize: StudioTheme.Values.myFontSize
wheelEnabled: false wheelEnabled: false
@@ -91,7 +88,6 @@ T.ComboBox {
myControl: myComboBox myControl: myComboBox
text: myComboBox.editText text: myComboBox.editText
borderWidth: myComboBox.borderWidth
onEditingFinished: { onEditingFinished: {
comboBoxInput.deselect() comboBoxInput.deselect()
@@ -113,16 +109,16 @@ T.ComboBox {
myControl: myComboBox myControl: myComboBox
myPopup: myComboBox.popup myPopup: myComboBox.popup
x: comboBoxInput.x + comboBoxInput.width x: comboBoxInput.x + comboBoxInput.width
y: myComboBox.borderWidth y: StudioTheme.Values.border
width: StudioTheme.Values.checkIndicatorWidth - myComboBox.borderWidth width: StudioTheme.Values.checkIndicatorWidth - StudioTheme.Values.border
height: StudioTheme.Values.checkIndicatorHeight - myComboBox.borderWidth * 2 height: StudioTheme.Values.checkIndicatorHeight - StudioTheme.Values.border * 2
} }
background: Rectangle { background: Rectangle {
id: comboBoxBackground id: comboBoxBackground
color: StudioTheme.Values.themeControlBackground color: StudioTheme.Values.themeControlBackground
border.color: StudioTheme.Values.themeControlOutline border.color: StudioTheme.Values.themeControlOutline
border.width: myComboBox.borderWidth border.width: StudioTheme.Values.border
x: actionIndicator.width x: actionIndicator.width
width: myComboBox.width - actionIndicator.width width: myComboBox.width - actionIndicator.width
height: myComboBox.height height: myComboBox.height
@@ -149,7 +145,7 @@ T.ComboBox {
width: comboBoxPopup.width - comboBoxPopup.leftPadding - comboBoxPopup.rightPadding width: comboBoxPopup.width - comboBoxPopup.leftPadding - comboBoxPopup.rightPadding
- (comboBoxPopupScrollBar.visible ? comboBoxPopupScrollBar.contentItem.implicitWidth - (comboBoxPopupScrollBar.visible ? comboBoxPopupScrollBar.contentItem.implicitWidth
+ 2 : 0) // TODO Magic number + 2 : 0) // TODO Magic number
height: StudioTheme.Values.height - 2 * myComboBox.borderWidth height: StudioTheme.Values.height - 2 * StudioTheme.Values.border
padding: 0 padding: 0
enabled: model.enabled === undefined ? true : model.enabled enabled: model.enabled === undefined ? true : model.enabled
@@ -203,9 +199,9 @@ T.ComboBox {
popup: T.Popup { popup: T.Popup {
id: comboBoxPopup id: comboBoxPopup
x: actionIndicator.width + myComboBox.borderWidth x: actionIndicator.width + StudioTheme.Values.border
y: myComboBox.height y: myComboBox.height
width: myComboBox.width - actionIndicator.width - myComboBox.borderWidth * 2 width: myComboBox.width - actionIndicator.width - StudioTheme.Values.border * 2
// TODO Setting the height on the popup solved the problem with the popup of height 0, // TODO Setting the height on the popup solved the problem with the popup of height 0,
// but it has the problem that it sometimes extend over the border of the actual window // but it has the problem that it sometimes extend over the border of the actual window
// and is then cut off. // and is then cut off.
@@ -213,7 +209,7 @@ T.ComboBox {
+ comboBoxPopup.bottomPadding, + comboBoxPopup.bottomPadding,
myComboBox.Window.height - topMargin - bottomMargin, myComboBox.Window.height - topMargin - bottomMargin,
StudioTheme.Values.maxComboBoxPopupHeight) StudioTheme.Values.maxComboBoxPopupHeight)
padding: myComboBox.borderWidth padding: StudioTheme.Values.border
margins: 0 // If not defined margin will be -1 margins: 0 // If not defined margin will be -1
closePolicy: T.Popup.CloseOnPressOutside | T.Popup.CloseOnPressOutsideParent closePolicy: T.Popup.CloseOnPressOutside | T.Popup.CloseOnPressOutsideParent
| T.Popup.CloseOnEscape | T.Popup.CloseOnReleaseOutside | T.Popup.CloseOnEscape | T.Popup.CloseOnReleaseOutside
@@ -245,7 +241,7 @@ T.ComboBox {
State { State {
name: "default" name: "default"
when: myComboBox.enabled && !myComboBox.hover && !myComboBox.edit && !myComboBox.open when: myComboBox.enabled && !myComboBox.hover && !myComboBox.edit && !myComboBox.open
&& !myComboBox.activeFocus && !myComboBox.activeFocus && !myComboBox.hasActiveDrag
PropertyChanges { PropertyChanges {
target: myComboBox target: myComboBox
wheelEnabled: false wheelEnabled: false
@@ -257,9 +253,23 @@ T.ComboBox {
PropertyChanges { PropertyChanges {
target: comboBoxBackground target: comboBoxBackground
color: StudioTheme.Values.themeControlBackground color: StudioTheme.Values.themeControlBackground
border.color: myComboBox.hasActiveDrag ? StudioTheme.Values.themeInteraction }
: StudioTheme.Values.themeControlOutline },
border.width: myComboBox.borderWidth State {
name: "acceptsDrag"
when: myComboBox.enabled && myComboBox.hasActiveDrag && !myComboBox.hasActiveHoverDrag
PropertyChanges {
target: comboBoxBackground
border.color: StudioTheme.Values.themeControlOutlineInteraction
}
},
State {
name: "dragHover"
when: myComboBox.enabled && myComboBox.hasActiveHoverDrag
PropertyChanges {
target: comboBoxBackground
color: StudioTheme.Values.themeControlBackgroundInteraction
border.color: StudioTheme.Values.themeControlOutlineInteraction
} }
}, },
// This state is intended for ComboBoxes which aren't editable, but have focus e.g. via // This state is intended for ComboBoxes which aren't editable, but have focus e.g. via

View File

@@ -34,7 +34,6 @@ TextInput {
property bool edit: textInput.activeFocus property bool edit: textInput.activeFocus
property bool hover: mouseArea.containsMouse && textInput.enabled property bool hover: mouseArea.containsMouse && textInput.enabled
property int borderWidth: StudioTheme.Values.border
z: 2 z: 2
font: myControl.font font: myControl.font
@@ -56,11 +55,11 @@ TextInput {
Rectangle { Rectangle {
id: textInputBackground id: textInputBackground
x: textInput.borderWidth x: StudioTheme.Values.border
y: textInput.borderWidth y: StudioTheme.Values.border
z: -1 z: -1
width: textInput.width width: textInput.width
height: StudioTheme.Values.height - textInput.borderWidth * 2 height: StudioTheme.Values.height - StudioTheme.Values.border * 2
color: StudioTheme.Values.themeControlBackground color: StudioTheme.Values.themeControlBackground
border.width: 0 border.width: 0
} }
@@ -94,7 +93,7 @@ TextInput {
State { State {
name: "default" name: "default"
when: myControl.enabled && !textInput.edit && !textInput.hover && !myControl.hover when: myControl.enabled && !textInput.edit && !textInput.hover && !myControl.hover
&& !myControl.open && !myControl.open && !myControl.hasActiveDrag
PropertyChanges { PropertyChanges {
target: textInputBackground target: textInputBackground
color: StudioTheme.Values.themeControlBackground color: StudioTheme.Values.themeControlBackground
@@ -105,6 +104,14 @@ TextInput {
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
} }
}, },
State {
name: "dragHover"
when: myControl.enabled && myControl.hasActiveHoverDrag
PropertyChanges {
target: textInputBackground
color: StudioTheme.Values.themeControlBackgroundInteraction
}
},
State { State {
name: "globalHover" name: "globalHover"
when: myControl.hover && !textInput.hover && !textInput.edit && !myControl.open when: myControl.hover && !textInput.hover && !textInput.edit && !myControl.open

View File

@@ -88,6 +88,9 @@ Item {
property alias popupScrollBar: popupScrollBar property alias popupScrollBar: popupScrollBar
property alias popupMouseArea: popupMouseArea property alias popupMouseArea: popupMouseArea
property bool hasActiveDrag: false // an item that can be dropped here is being dragged
property bool hasActiveHoverDrag: false // an item that can be dropped her is being hovered on top
width: StudioTheme.Values.defaultControlWidth width: StudioTheme.Values.defaultControlWidth
height: StudioTheme.Values.defaultControlHeight height: StudioTheme.Values.defaultControlHeight
implicitHeight: StudioTheme.Values.defaultControlHeight implicitHeight: StudioTheme.Values.defaultControlHeight
@@ -469,9 +472,11 @@ Item {
State { State {
name: "default" name: "default"
when: root.enabled && !textInput.edit && !root.hover && !root.open when: root.enabled && !textInput.edit && !root.hover && !root.open
&& !root.hasActiveDrag
PropertyChanges { PropertyChanges {
target: textInputBackground target: textInputBackground
color: StudioTheme.Values.themeControlBackground color: StudioTheme.Values.themeControlBackground
border.color: StudioTheme.Values.themeControlOutline
} }
PropertyChanges { PropertyChanges {
target: textInputMouseArea target: textInputMouseArea
@@ -479,6 +484,23 @@ Item {
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
} }
}, },
State {
name: "acceptsDrag"
when: root.enabled && root.hasActiveDrag && !root.hasActiveHoverDrag
PropertyChanges {
target: textInputBackground
border.color: StudioTheme.Values.themeInteraction
}
},
State {
name: "dragHover"
when: root.enabled && root.hasActiveHoverDrag
PropertyChanges {
target: textInputBackground
color: StudioTheme.Values.themeControlBackgroundInteraction
border.color: StudioTheme.Values.themeInteraction
}
},
State { State {
name: "globalHover" name: "globalHover"
when: root.hover && !textInput.hover && !textInput.edit && !root.open when: root.hover && !textInput.hover && !textInput.edit && !root.open
@@ -587,12 +609,20 @@ Item {
name: "default" name: "default"
when: root.enabled && checkIndicator.enabled && !root.edit when: root.enabled && checkIndicator.enabled && !root.edit
&& !checkIndicator.hover && !root.hover && !checkIndicator.hover && !root.hover
&& !checkIndicator.checked && !checkIndicator.checked && !root.hasActiveHoverDrag
PropertyChanges { PropertyChanges {
target: checkIndicator target: checkIndicator
color: StudioTheme.Values.themeControlBackground color: StudioTheme.Values.themeControlBackground
} }
}, },
State {
name: "dragHover"
when: root.enabled && root.hasActiveHoverDrag
PropertyChanges {
target: checkIndicator
color: StudioTheme.Values.themeControlBackgroundInteraction
}
},
State { State {
name: "globalHover" name: "globalHover"
when: root.enabled && checkIndicator.enabled when: root.enabled && checkIndicator.enabled

View File

@@ -0,0 +1,5 @@
import QtQuick 2.15
%{FormClass} {
button.onClicked: console.log("Button Pressed")
}

View File

@@ -0,0 +1,33 @@
/*
This is a UI file (.ui.qml) that is intended to be edited in Qt Design Studio only.
It is supposed to be strictly declarative and only uses a subset of QML. If you edit
this file manually, you might introduce QML code that is not supported by Qt Design Studio.
Check out https://doc.qt.io/qtcreator/creator-quick-ui-forms.html for details on .ui.qml files.
*/
import QtQuick 2.15
@if %{UseQtQuickControls2}
import QtQuick.Controls 2.15
@endif
@if %{UseImport}
import %{ApplicationImport}
@endif
%{RootItem} {
@if %{UseImport}
width: Constants.width
height: Constants.height
@else
width: 1024
height: 768
@endif
property alias button: button
Button {
id: button
x: 64
y: 64
text: qsTr("Button")
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,132 @@
{
"version": 1,
"supportedProjectTypes": [ ],
"id": "Q.QtStudio.QmlUIForm.2",
"category": "B.StudioQtQuickFiles",
"trDescription": "Creates a UI file (.ui.qml) along with a matching QML file for implementation purposes.",
"trDisplayName": "QtQuick UI Form",
"trDisplayCategory": "Qt Quick Files",
"icon": "file_ui.png",
"platformIndependent": true,
"enabled": "%{JS: value('Plugins').indexOf('QmlJSEditor') >= 0}",
"options" : [
{ "key": "QmlFile", "value": "%{Class}.%{JS: Util.preferredSuffix('text/x-qml')}" },
{ "key": "UiFile", "value": "%{FormClass}.%{JS: Util.preferredSuffix('application/x-qt.ui+qml')}" },
{ "key": "ApplicationImport", "value": "%{QmlProjectName} 1.0" },
{ "key": "RootItem", "value": "%{JS: %{RootItemCB}.RootItem}" },
{ "key": "UseImportDefault", "value": "%{JS: false}" },
{ "key": "UseQtQuickControls2Default", "value": "%{JS: true}" }
],
"pages" :
[
{
"trDisplayName": "Define Class",
"trShortTitle": "Details",
"typeId": "Fields",
"data" :
[
{
"name": "Class",
"trDisplayName": "Component name:",
"mandatory": true,
"type": "LineEdit",
"data": {
"validator": "(?:[A-Z_][a-zA-Z_0-9]*|)",
"fixup": "%{JS: '%{INPUT}'.charAt(0).toUpperCase() + '%{INPUT}'.slice(1) }"
}
},
{
"name": "Sp1",
"type": "Spacer",
"data": { "factor": 2 }
},
{
"name": "FormClass",
"trDisplayName": "Component form name:",
"mandatory": true,
"type": "LineEdit",
"data": {
"validator": "(?:[A-Z_][a-zA-Z_0-9]*|)",
"fixup": "%{JS: '%{INPUT}'.charAt(0).toUpperCase() + '%{INPUT}'.slice(1) }",
"trText": "%{Class}Form"
}
},
{
"name": "TargetPath",
"type": "PathChooser",
"trDisplayName": "Path:",
"mandatory": true,
"data":
{
"kind": "directory",
"basePath": "%{InitialPath}",
"path": "%{InitialPath}"
}
},
{
"name": "RootItemCB",
"trDisplayName": "Root Item:",
"type": "ComboBox",
"data":
{
"index": 0,
"items":
[
{
"trKey": "Item",
"value":
"({
'RootItem': 'Item'
})"
},
{
"trKey": "Rectangle",
"value":
"({
'RootItem': 'Rectangle'
})"
}
]
}
},
{
"name": "UseImport",
"trDisplayName": "Use Application Import",
"type": "CheckBox",
"data":
{
"checked": "%{UseImportDefault}"
}
},
{
"name": "UseQtQuickControls2",
"trDisplayName": "Use QtQuick Controls 2",
"type": "CheckBox",
"data":
{
"checked": "%{UseQtQuickControls2Default}"
}
}
]
}
],
"generators" :
[
{
"typeId": "File",
"data": [
{
"source": "file.qml.tpl",
"target": "%{TargetPath}/%{QmlFile}",
"openInEditor": true
},
{
"source": "fileForm.ui.qml.tpl",
"target": "%{TargetPath}/%{UiFile}",
"openInEditor": true
}
]
}
]
}

View File

@@ -16,12 +16,6 @@ Rectangle {
color: Constants.backgroundColor color: Constants.backgroundColor
Text {
text: qsTr("Hello %{ProjectName}")
anchors.centerIn: parent
font.family: Constants.font.family
}
View3D { View3D {
id: view3D id: view3D
anchors.fill: parent anchors.fill: parent
@@ -63,4 +57,12 @@ Rectangle {
diffuseColor: "#4aee45" diffuseColor: "#4aee45"
} }
} }
Text {
text: qsTr("Hello %{ProjectName}")
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 100
font.family: Constants.font.family
}
} }

View File

@@ -90,7 +90,7 @@ Project {
/* Required for deployment */ /* Required for deployment */
targetDirectory: "/opt/%{ProjectName}" targetDirectory: "/opt/%{ProjectName}"
qdsVersion: "3.4" qdsVersion: "3.5"
quickVersion: "%{QtQuickVersion}" quickVersion: "%{QtQuickVersion}"

View File

@@ -93,12 +93,12 @@ void Database::activateLogging()
void Database::open(LockingMode lockingMode) void Database::open(LockingMode lockingMode)
{ {
m_databaseBackend.open(m_databaseFilePath, m_openMode); m_databaseBackend.open(m_databaseFilePath, m_openMode);
m_databaseBackend.setLockingMode(lockingMode);
m_databaseBackend.setJournalMode(m_journalMode);
if (m_busyTimeout > 0ms) if (m_busyTimeout > 0ms)
m_databaseBackend.setBusyTimeout(m_busyTimeout); m_databaseBackend.setBusyTimeout(m_busyTimeout);
else else
m_databaseBackend.registerBusyHandler(); m_databaseBackend.registerBusyHandler();
m_databaseBackend.setLockingMode(lockingMode);
m_databaseBackend.setJournalMode(m_journalMode);
registerTransactionStatements(); registerTransactionStatements();
m_isOpen = true; m_isOpen = true;
} }

View File

@@ -473,7 +473,7 @@ bool CppEditorDocument::save(QString *errorString, const FilePath &filePath, boo
if (!editedRanges.empty()) { if (!editedRanges.empty()) {
QTextCursor cursor(document()); QTextCursor cursor(document());
cursor.beginEditBlock(); cursor.joinPreviousEditBlock();
indenter()->format(editedRanges); indenter()->format(editedRanges);
cursor.endEditBlock(); cursor.endEditBlock();
} }

View File

@@ -161,6 +161,7 @@ extend_qtc_plugin(QmlDesigner
SOURCES_PREFIX components/edit3d SOURCES_PREFIX components/edit3d
SOURCES SOURCES
edit3dview.cpp edit3dview.h edit3dview.cpp edit3dview.h
edit3dviewconfig.h
edit3dwidget.cpp edit3dwidget.h edit3dwidget.cpp edit3dwidget.h
edit3dcanvas.cpp edit3dcanvas.h edit3dcanvas.cpp edit3dcanvas.h
edit3dactions.cpp edit3dactions.h edit3dactions.cpp edit3dactions.h
@@ -305,6 +306,7 @@ extend_qtc_plugin(QmlDesigner
gradientpresetitem.cpp gradientpresetitem.h gradientpresetitem.cpp gradientpresetitem.h
gradientpresetlistmodel.cpp gradientpresetlistmodel.h gradientpresetlistmodel.cpp gradientpresetlistmodel.h
propertyeditorcontextobject.cpp propertyeditorcontextobject.h propertyeditorcontextobject.cpp propertyeditorcontextobject.h
propertyeditorimageprovider.cpp propertyeditorimageprovider.h
propertyeditorqmlbackend.cpp propertyeditorqmlbackend.h propertyeditorqmlbackend.cpp propertyeditorqmlbackend.h
propertyeditortransaction.cpp propertyeditortransaction.h propertyeditortransaction.cpp propertyeditortransaction.h
propertyeditorvalue.cpp propertyeditorvalue.h propertyeditorvalue.cpp propertyeditorvalue.h
@@ -392,7 +394,8 @@ extend_qtc_plugin(QmlDesigner
SOURCES SOURCES
explicitimagecacheimageprovider.cpp explicitimagecacheimageprovider.cpp
explicitimagecacheimageprovider.h explicitimagecacheimageprovider.h
smallimagecacheprovider.cpp
smallimagecacheprovider.h
) )
extend_qtc_plugin(QmlDesigner extend_qtc_plugin(QmlDesigner

View File

@@ -81,7 +81,7 @@ bool AssetExporterView::saveQmlFile(QString *error) const
void AssetExporterView::modelAttached(Model *model) void AssetExporterView::modelAttached(Model *model)
{ {
if (model->rewriterView() && model->rewriterView()->inErrorState()) if (model->rewriterView() && !model->rewriterView()->errors().isEmpty())
setState(LoadState::QmlErrorState); setState(LoadState::QmlErrorState);
AbstractView::modelAttached(model); AbstractView::modelAttached(model);

View File

@@ -75,7 +75,7 @@ QPixmap AssetsLibraryIconProvider::requestPixmap(const QString &id, QSize *size,
pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/AssetsLibrary/images/assets_default.png"); pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/AssetsLibrary/images/assets_default.png");
if (requestedSize.isValid()) if (requestedSize.isValid())
return pixmap.scaled(requestedSize); return pixmap.scaled(requestedSize, Qt::KeepAspectRatio);
return pixmap; return pixmap;
} }

View File

@@ -91,10 +91,6 @@ bool AssetsLibraryWidget::eventFilter(QObject *obj, QEvent *event)
m_assetsToDrag.clear(); m_assetsToDrag.clear();
} }
} }
} else if (event->type() == QMouseEvent::MouseButtonRelease) {
m_assetsToDrag.clear();
if (m_model)
m_model->endDrag();
} }
return QObject::eventFilter(obj, event); return QObject::eventFilter(obj, event);

View File

@@ -513,7 +513,7 @@ public:
->currentModel(); ->currentModel();
if (currentModel->rewriterView() if (currentModel->rewriterView()
&& currentModel->rewriterView()->inErrorState()) { && !currentModel->rewriterView()->errors().isEmpty()) {
throw DocumentError{}; throw DocumentError{};
} }

View File

@@ -478,19 +478,16 @@ static void layoutHelperFunction(const SelectionContext &selectionContext,
const QmlItemNode qmlItemNode = QmlItemNode(selectionContext.firstSelectedModelNode()); const QmlItemNode qmlItemNode = QmlItemNode(selectionContext.firstSelectedModelNode());
if (qmlItemNode.hasInstanceParentItem()) { if (qmlItemNode.hasInstanceParentItem()) {
ModelNode layoutNode;
selectionContext.view()->executeInTransaction("DesignerActionManager|layoutHelperFunction1",[=, &layoutNode](){ selectionContext.view()->executeInTransaction("DesignerActionManager|layoutHelperFunction",[=](){
QmlItemNode parentNode = qmlItemNode.instanceParentItem(); QmlItemNode parentNode = qmlItemNode.instanceParentItem();
NodeMetaInfo metaInfo = selectionContext.view()->model()->metaInfo(layoutType); NodeMetaInfo metaInfo = selectionContext.view()->model()->metaInfo(layoutType);
layoutNode = selectionContext.view()->createModelNode(layoutType, metaInfo.majorVersion(), metaInfo.minorVersion()); const ModelNode layoutNode = selectionContext.view()->createModelNode(layoutType, metaInfo.majorVersion(), metaInfo.minorVersion());
reparentTo(layoutNode, parentNode); reparentTo(layoutNode, parentNode);
});
selectionContext.view()->executeInTransaction("DesignerActionManager|layoutHelperFunction2",[=](){
QList<ModelNode> sortedSelectedNodes = selectionContext.selectedModelNodes(); QList<ModelNode> sortedSelectedNodes = selectionContext.selectedModelNodes();
Utils::sort(sortedSelectedNodes, lessThan); Utils::sort(sortedSelectedNodes, lessThan);

View File

@@ -110,9 +110,11 @@ void DebugView::nodeCreated(const ModelNode &createdNode)
QString string; QString string;
message.setString(&string); message.setString(&string);
message << createdNode; message << createdNode;
message << createdNode.majorVersion() << "." << createdNode.minorVersion();
message << createdNode.nodeSource(); message << createdNode.nodeSource();
message << "MetaInfo " << createdNode.metaInfo().isValid(); message << "MetaInfo " << createdNode.metaInfo().isValid() << " ";
if (createdNode.metaInfo().isValid()) { if (createdNode.metaInfo().isValid()) {
message << createdNode.metaInfo().majorVersion() << "." << createdNode.metaInfo().minorVersion();
message << createdNode.metaInfo().componentFileName(); message << createdNode.metaInfo().componentFileName();
} }
log("::nodeCreated:", message.readAll()); log("::nodeCreated:", message.readAll());

View File

@@ -23,85 +23,56 @@
** **
****************************************************************************/ ****************************************************************************/
#include "backgroundcolorselection.h" #pragma once
#include <QColorDialog>
#include <nodeinstanceview.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <view3dactioncommand.h>
#include <qmldesignerplugin.h> #include "backgroundcolorselection.h"
#include "edit3dviewconfig.h"
using namespace QmlDesigner; using namespace QmlDesigner;
namespace { void BackgroundColorSelection::showBackgroundColorSelectionWidget(QWidget *parent, const QByteArray &key,
QList<QColor> readBackgroundColorConfiguration() View3DActionCommand::Type cmdType)
{
QVariant var = QmlDesigner::DesignerSettings::getValue(
QmlDesigner::DesignerSettingsKey::EDIT3DVIEW_BACKGROUND_COLOR);
if (!var.isValid())
return {};
auto colorNameList = var.value<QList<QString>>();
QTC_ASSERT(colorNameList.size() == 2, return {});
return {colorNameList[0], colorNameList[1]};
}
void setBackgroundColorConfiguration(const QList<QColor> &colorConfig)
{
auto view = QmlDesignerPlugin::instance()->viewManager().nodeInstanceView();
View3DActionCommand cmd(View3DActionCommand::SelectBackgroundColor,
QVariant::fromValue(colorConfig));
view->view3DAction(cmd);
}
void saveBackgroundColorConfiguration(const QList<QColor> &colorConfig)
{
QList<QString> colorsSaved = {colorConfig[0].name(), colorConfig[1].name()};
QmlDesigner::DesignerSettings::setValue(
QmlDesigner::DesignerSettingsKey::EDIT3DVIEW_BACKGROUND_COLOR,
QVariant::fromValue(colorsSaved));
}
} // namespace
QColorDialog *BackgroundColorSelection::createDialog(QWidget *parent)
{
auto dialog = new QColorDialog(parent);
dialog->setModal(true);
dialog->setAttribute(Qt::WA_DeleteOnClose);
const QList<QColor> oldColorConfig = readBackgroundColorConfiguration();
dialog->show();
QObject::connect(dialog, &QColorDialog::currentColorChanged, dialog, [](const QColor &color) {
setBackgroundColorConfiguration({color, color});
});
QObject::connect(dialog, &QColorDialog::colorSelected, dialog, [](const QColor &color) {
saveBackgroundColorConfiguration({color, color});
});
if (!oldColorConfig.isEmpty()) {
QObject::connect(dialog, &QColorDialog::rejected, dialog, [oldColorConfig]() {
setBackgroundColorConfiguration(oldColorConfig);
});
}
return dialog;
}
void BackgroundColorSelection::showBackgroundColorSelectionWidget(QWidget *parent)
{ {
if (m_dialog) if (m_dialog)
return; return;
m_dialog = BackgroundColorSelection::createDialog(parent); m_dialog = BackgroundColorSelection::createColorDialog(parent, key, cmdType);
QTC_ASSERT(m_dialog, return); QTC_ASSERT(m_dialog, return);
QObject::connect(m_dialog, &QWidget::destroyed, m_dialog, [&]() { QObject::connect(m_dialog, &QWidget::destroyed, m_dialog, [&]() {
m_dialog = nullptr; m_dialog = nullptr;
}); });
} }
QColorDialog *BackgroundColorSelection::createColorDialog(QWidget *parent, const QByteArray &key,
View3DActionCommand::Type cmdType)
{
auto dialog = new QColorDialog(parent);
dialog->setModal(true);
dialog->setAttribute(Qt::WA_DeleteOnClose);
QList<QColor> oldColorConfig = Edit3DViewConfig::load(key);
dialog->show();
QObject::connect(dialog, &QColorDialog::currentColorChanged, dialog, [cmdType](const QColor &color) {
Edit3DViewConfig::set(cmdType, color);
});
QObject::connect(dialog, &QColorDialog::colorSelected, dialog, [key](const QColor &color) {
Edit3DViewConfig::save(key, color);
});
if (Edit3DViewConfig::isValid(oldColorConfig)) {
QObject::connect(dialog, &QColorDialog::rejected, dialog, [cmdType, oldColorConfig]() {
Edit3DViewConfig::set(cmdType, oldColorConfig);
});
}
return dialog;
}

View File

@@ -25,9 +25,13 @@
#pragma once #pragma once
#include <QColorDialog> #include <QByteArray>
#include <view3dactioncommand.h>
QT_FORWARD_DECLARE_CLASS(QColorDialog)
namespace QmlDesigner { namespace QmlDesigner {
class BackgroundColorSelection : public QObject class BackgroundColorSelection : public QObject
{ {
Q_OBJECT Q_OBJECT
@@ -37,10 +41,13 @@ public:
: QObject{parent} : QObject{parent}
{} {}
static void showBackgroundColorSelectionWidget(QWidget *parent); static void showBackgroundColorSelectionWidget(QWidget *parent, const QByteArray &key,
View3DActionCommand::Type cmdType);
private: private:
static QColorDialog *createDialog(QWidget *parent); static QColorDialog *createColorDialog(QWidget *parent, const QByteArray &key,
View3DActionCommand::Type cmdType);
inline static QColorDialog *m_dialog = nullptr; inline static QColorDialog *m_dialog = nullptr;
}; };

View File

@@ -48,7 +48,8 @@ Edit3DActionTemplate::Edit3DActionTemplate(const QString &description,
void Edit3DActionTemplate::actionTriggered(bool b) void Edit3DActionTemplate::actionTriggered(bool b)
{ {
if (m_type != View3DActionCommand::Empty && m_type != View3DActionCommand::SelectBackgroundColor) { if (m_type != View3DActionCommand::Empty && m_type != View3DActionCommand::SelectBackgroundColor
&& m_type != View3DActionCommand::SelectGridColor) {
auto view = QmlDesignerPlugin::instance()->viewManager().nodeInstanceView(); auto view = QmlDesignerPlugin::instance()->viewManager().nodeInstanceView();
View3DActionCommand cmd(m_type, b); View3DActionCommand cmd(m_type, b);
view->view3DAction(cmd); view->view3DAction(cmd);

View File

@@ -186,6 +186,7 @@ void Edit3DCanvas::dragEnterEvent(QDragEnterEvent *e)
void Edit3DCanvas::dropEvent(QDropEvent *e) void Edit3DCanvas::dropEvent(QDropEvent *e)
{ {
m_parent->view()->executeInTransaction(__FUNCTION__, [&] {
auto modelNode = QmlVisualNode::createQml3DNode(m_parent->view(), m_itemLibraryEntry, m_activeScene).modelNode(); auto modelNode = QmlVisualNode::createQml3DNode(m_parent->view(), m_itemLibraryEntry, m_activeScene).modelNode();
QTC_ASSERT(modelNode.isValid(), return); QTC_ASSERT(modelNode.isValid(), return);
@@ -193,35 +194,9 @@ void Edit3DCanvas::dropEvent(QDropEvent *e)
m_parent->view()->setSelectedModelNode(modelNode); m_parent->view()->setSelectedModelNode(modelNode);
// if added node is a Model, assign it a material // if added node is a Model, assign it a material
if (modelNode.isSubclassOf("QtQuick3D.Model")) { if (modelNode.isSubclassOf("QtQuick3D.Model"))
ModelNode matLib = m_parent->view()->modelNodeForId(Constants::MATERIAL_LIB_ID); m_parent->view()->assignMaterialTo3dModel(modelNode);
QTC_ASSERT(matLib.isValid(), return); });
const QList<ModelNode> materials = matLib.directSubModelNodes();
ModelNode material;
if (materials.size() > 0) {
for (const ModelNode &mat : materials) {
if (mat.isSubclassOf("QtQuick3D.Material")) {
material = mat;
break;
}
}
}
// if no valid material, create a new default material
if (!material.isValid()) {
NodeMetaInfo metaInfo = m_parent->view()->model()->metaInfo("QtQuick3D.DefaultMaterial");
material = m_parent->view()->createModelNode("QtQuick3D.DefaultMaterial", metaInfo.majorVersion(),
metaInfo.minorVersion());
VariantProperty matNameProp = material.variantProperty("objectName");
matNameProp.setValue("New Material");
material.validId();
matLib.defaultNodeListProperty().reparentHere(material);
}
BindingProperty modelMatsProp = modelNode.bindingProperty("materials");
modelMatsProp.setExpression(material.id());
}
} }
void Edit3DCanvas::focusOutEvent(QFocusEvent *focusEvent) void Edit3DCanvas::focusOutEvent(QFocusEvent *focusEvent)

View File

@@ -28,6 +28,8 @@
#include "edit3dcanvas.h" #include "edit3dcanvas.h"
#include "edit3dview.h" #include "edit3dview.h"
#include "edit3dwidget.h" #include "edit3dwidget.h"
#include "edit3dviewconfig.h"
#include "backgroundcolorselection.h"
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <coreplugin/messagebox.h> #include <coreplugin/messagebox.h>
@@ -42,8 +44,6 @@
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/utilsicons.h> #include <utils/utilsicons.h>
#include <backgroundcolorselection.h>
#include <QDebug> #include <QDebug>
#include <QToolButton> #include <QToolButton>
@@ -266,6 +266,70 @@ void Edit3DView::setSeeker(SeekerSlider *slider)
m_seeker = slider; m_seeker = slider;
} }
Edit3DAction *Edit3DView::createSelectBackgrounColorAction()
{
QString description = QCoreApplication::translate("SelectBackgroundColorAction",
"Select Background Color");
QString tooltip = QCoreApplication::translate("SelectBackgroundColorAction",
"Select a color for the background of the 3D Editor.");
auto operation = [this](const SelectionContext &) {
BackgroundColorSelection::showBackgroundColorSelectionWidget(
edit3DWidget(),
DesignerSettingsKey::EDIT3DVIEW_BACKGROUND_COLOR,
View3DActionCommand::SelectBackgroundColor);
};
return new Edit3DAction(
Constants::EDIT3D_EDIT_SELECT_BACKGROUND_COLOR, View3DActionCommand::SelectBackgroundColor,
description,
{}, false, false, {}, {}, operation,
tooltip);
}
Edit3DAction *Edit3DView::createGridColorSelectionAction()
{
QString description = QCoreApplication::translate("SelectGridColorAction", "Select Grid Color");
QString tooltip = QCoreApplication::translate("SelectGridColorAction",
"Select a color for the grid lines of the 3D Editor.");
auto operation = [this](const SelectionContext &) {
BackgroundColorSelection::showBackgroundColorSelectionWidget(
edit3DWidget(),
DesignerSettingsKey::EDIT3DVIEW_GRID_COLOR,
View3DActionCommand::SelectGridColor);
};
return new Edit3DAction(
Constants::EDIT3D_EDIT_SELECT_GRID_COLOR, View3DActionCommand::SelectGridColor,
description, {}, false, false, {}, {}, operation,
tooltip);
}
Edit3DAction *Edit3DView::createResetColorAction()
{
QString description = QCoreApplication::translate("ResetEdit3DColorsAction", "Reset Colors");
QString tooltip = QCoreApplication::translate("ResetEdit3DColorsAction",
"Reset the background color and the color of the "
"grid lines of the 3D Editor to the default valus.");
auto operation = [](const SelectionContext &) {
QList<QColor> bgColors = {QRgb(0x222222), QRgb(0x999999)};
Edit3DViewConfig::set(View3DActionCommand::SelectBackgroundColor, bgColors);
Edit3DViewConfig::save(DesignerSettingsKey::EDIT3DVIEW_BACKGROUND_COLOR, bgColors);
QColor gridColor{0xaaaaaa};
Edit3DViewConfig::set(View3DActionCommand::SelectGridColor, gridColor);
Edit3DViewConfig::save(DesignerSettingsKey::EDIT3DVIEW_GRID_COLOR, gridColor);
};
return new Edit3DAction(
QmlDesigner::Constants::EDIT3D_EDIT_RESET_BACKGROUND_COLOR, View3DActionCommand::ResetBackgroundColor,
description,
{}, false, false, {}, {}, operation,
tooltip);
}
void Edit3DView::createEdit3DActions() void Edit3DView::createEdit3DActions()
{ {
m_selectionModeAction m_selectionModeAction
@@ -338,32 +402,6 @@ void Edit3DView::createEdit3DActions()
QKeySequence(Qt::Key_G), true, true, {}, {}, nullptr, QKeySequence(Qt::Key_G), true, true, {}, {}, nullptr,
QCoreApplication::translate("ShowGridAction", "Toggle the visibility of the helper grid.")); QCoreApplication::translate("ShowGridAction", "Toggle the visibility of the helper grid."));
SelectionContextOperation showBackgroundColorSelection = [this](const SelectionContext &) {
BackgroundColorSelection::showBackgroundColorSelectionWidget(edit3DWidget());
};
m_backgroundColorSelectionAction = new Edit3DAction(
QmlDesigner::Constants::EDIT3D_EDIT_SELECT_BACKGROUND_COLOR, View3DActionCommand::SelectBackgroundColor,
QCoreApplication::translate("SelectBackgroundColorAction", "Select Background Color"),
{}, false, false, {}, {}, showBackgroundColorSelection,
QCoreApplication::translate("SelectBackgroundColorAction", "Select a color for the background of the 3D Editor."));
m_resetBackgroundColorAction = new Edit3DAction(
QmlDesigner::Constants::EDIT3D_EDIT_RESET_BACKGROUND_COLOR, View3DActionCommand::ResetBackgroundColor,
QCoreApplication::translate("ResetBackgroundColorAction", "Reset Background Color"),
{}, false, false, {}, {}, [](const SelectionContext &) {
QList<QColor> colors = {QRgb(0x222222), QRgb(0x999999)};
auto view = QmlDesignerPlugin::instance()->viewManager().nodeInstanceView();
View3DActionCommand cmd(View3DActionCommand::SelectBackgroundColor, QVariant::fromValue(colors));
view->view3DAction(cmd);
QList<QString> colorsToSave = {colors[0].name(), colors[1].name()};
QmlDesigner::DesignerSettings::setValue(
QmlDesigner::DesignerSettingsKey::EDIT3DVIEW_BACKGROUND_COLOR,
QVariant::fromValue(colorsToSave));
},
QCoreApplication::translate("ResetBackgroundColorAction", "Reset background color of the 3D Editor to the default value."));
m_showSelectionBoxAction = new Edit3DAction( m_showSelectionBoxAction = new Edit3DAction(
QmlDesigner::Constants::EDIT3D_EDIT_SHOW_SELECTION_BOX, View3DActionCommand::ShowSelectionBox, QmlDesigner::Constants::EDIT3D_EDIT_SHOW_SELECTION_BOX, View3DActionCommand::ShowSelectionBox,
QCoreApplication::translate("ShowSelectionBoxAction", "Show Selection Boxes"), QCoreApplication::translate("ShowSelectionBoxAction", "Show Selection Boxes"),
@@ -521,8 +559,9 @@ void Edit3DView::createEdit3DActions()
m_visibilityToggleActions << m_showCameraFrustumAction; m_visibilityToggleActions << m_showCameraFrustumAction;
m_visibilityToggleActions << m_showParticleEmitterAction; m_visibilityToggleActions << m_showParticleEmitterAction;
m_backgroundColorActions << m_backgroundColorSelectionAction; m_backgroundColorActions << createSelectBackgrounColorAction();
m_backgroundColorActions << m_resetBackgroundColorAction; m_backgroundColorActions << createGridColorSelectionAction();
m_backgroundColorActions << createResetColorAction();
} }
QVector<Edit3DAction *> Edit3DView::leftActions() const QVector<Edit3DAction *> Edit3DView::leftActions() const

View File

@@ -85,6 +85,10 @@ private:
void createEdit3DWidget(); void createEdit3DWidget();
void checkImports(); void checkImports();
Edit3DAction *createSelectBackgrounColorAction();
Edit3DAction *createGridColorSelectionAction();
Edit3DAction *createResetColorAction();
QPointer<Edit3DWidget> m_edit3DWidget; QPointer<Edit3DWidget> m_edit3DWidget;
QVector<Edit3DAction *> m_leftActions; QVector<Edit3DAction *> m_leftActions;
QVector<Edit3DAction *> m_rightActions; QVector<Edit3DAction *> m_rightActions;
@@ -101,8 +105,6 @@ private:
Edit3DAction *m_orientationModeAction = nullptr; Edit3DAction *m_orientationModeAction = nullptr;
Edit3DAction *m_editLightAction = nullptr; Edit3DAction *m_editLightAction = nullptr;
Edit3DAction *m_showGridAction = nullptr; Edit3DAction *m_showGridAction = nullptr;
Edit3DAction *m_backgroundColorSelectionAction = nullptr;
Edit3DAction *m_resetBackgroundColorAction = nullptr;
Edit3DAction *m_showSelectionBoxAction = nullptr; Edit3DAction *m_showSelectionBoxAction = nullptr;
Edit3DAction *m_showIconGizmoAction = nullptr; Edit3DAction *m_showIconGizmoAction = nullptr;
Edit3DAction *m_showCameraFrustumAction = nullptr; Edit3DAction *m_showCameraFrustumAction = nullptr;

View File

@@ -0,0 +1,98 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <view3dactioncommand.h>
#include <nodeinstanceview.h>
#include <qmldesignerplugin.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
namespace QmlDesigner {
class Edit3DViewConfig
{
public:
static QList<QColor> load(const char key[])
{
QVariant var = DesignerSettings::getValue(key);
if (!var.isValid())
return {};
auto colorNameList = var.value<QStringList>();
return Utils::transform(colorNameList, [](const QString &colorName) {
return QColor{colorName};
});
}
static void set(View3DActionCommand::Type type, const QList<QColor> &colorConfig)
{
if (colorConfig.size() == 1)
set(type, colorConfig.at(0));
else
setVariant(type, QVariant::fromValue(colorConfig));
}
static void set(View3DActionCommand::Type type, const QColor &color)
{
setVariant(type, QVariant::fromValue(color));
}
static void save(const QByteArray &key, const QList<QColor> &colorConfig)
{
QStringList colorNames = Utils::transform(colorConfig, [](const QColor &color) {
return color.name();
});
saveVariant(key, QVariant::fromValue(colorNames));
}
static void save(const QByteArray &key, const QColor &color)
{
saveVariant(key, QVariant::fromValue(color.name()));
}
static bool isValid(const QList<QColor> &colorConfig) { return !colorConfig.isEmpty(); }
private:
static void setVariant(View3DActionCommand::Type type, const QVariant &colorConfig)
{
auto view = QmlDesignerPlugin::instance()->viewManager().nodeInstanceView();
View3DActionCommand cmd(type, colorConfig);
view->view3DAction(cmd);
}
static void saveVariant(const QByteArray &key, const QVariant &colorConfig)
{
DesignerSettings::setValue(key, colorConfig);
}
};
} // namespace QmlDesigner

View File

@@ -33,6 +33,8 @@
#include <rewritingexception.h> #include <rewritingexception.h>
#include "qmldesignerconstants.h" #include "qmldesignerconstants.h"
#include <utils/qtcassert.h>
#include <QDebug> #include <QDebug>
#include <QGraphicsSceneMouseEvent> #include <QGraphicsSceneMouseEvent>
#include <QLoggingCategory> #include <QLoggingCategory>
@@ -405,10 +407,23 @@ void DragTool::move(const QPointF &scenePosition, const QList<QGraphicsItem *> &
void DragTool::commitTransaction() void DragTool::commitTransaction()
{ {
try { try {
handleView3dDrop();
m_rewriterTransaction.commit(); m_rewriterTransaction.commit();
} catch (const RewritingException &e) { } catch (const RewritingException &e) {
e.showException(); e.showException();
} }
} }
void DragTool::handleView3dDrop()
{
// If a View3D is dropped, we need to assign material to the included model
for (const QmlItemNode &dragNode : qAsConst(m_dragNodes)) {
if (dragNode.modelNode().isSubclassOf("QtQuick3D.View3D")) {
const QList<ModelNode> models = dragNode.modelNode().subModelNodesOfType("QtQuick3D.Model");
QTC_ASSERT(models.size() == 1, return);
view()->assignMaterialTo3dModel(models.at(0));
}
}
}
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -90,6 +90,7 @@ protected:
void move(const QPointF &scenePos, const QList<QGraphicsItem *> &itemList); void move(const QPointF &scenePos, const QList<QGraphicsItem *> &itemList);
void createDragNodes(const QMimeData *mimeData, const QPointF &scenePosition, const QList<QGraphicsItem *> &itemList); void createDragNodes(const QMimeData *mimeData, const QPointF &scenePosition, const QList<QGraphicsItem *> &itemList);
void commitTransaction(); void commitTransaction();
void handleView3dDrop();
private: private:
MoveManipulator m_moveManipulator; MoveManipulator m_moveManipulator;

View File

@@ -471,7 +471,7 @@ void FormEditorView::documentMessagesChanged(const QList<DocumentMessage> &error
if (!errors.isEmpty() && !model()->rewriterView()->hasIncompleteTypeInformation()) if (!errors.isEmpty() && !model()->rewriterView()->hasIncompleteTypeInformation())
m_formEditorWidget->showErrorMessageBox(errors); m_formEditorWidget->showErrorMessageBox(errors);
else else if (rewriterView()->errors().isEmpty())
m_formEditorWidget->hideErrorMessageBox(); m_formEditorWidget->hideErrorMessageBox();
checkRootModelNode(); checkRootModelNode();

View File

@@ -74,7 +74,7 @@ static void addFormattedMessage(Utils::OutputFormatter *formatter, const QString
formatter->plainTextEdit()->verticalScrollBar()->maximum()); formatter->plainTextEdit()->verticalScrollBar()->maximum());
} }
static const int rowHeight = 28; static const int rowHeight = 32;
static const int checkBoxColWidth = 18; static const int checkBoxColWidth = 18;
static const int labelMinWidth = 130; static const int labelMinWidth = 130;
static const int controlMinWidth = 65; static const int controlMinWidth = 65;
@@ -781,7 +781,7 @@ QGridLayout *ItemLibraryAssetImportDialog::createOptionsGrid(
int &globalOptionsHeight = advanced ? m_advancedData.optionsHeight : m_simpleData.optionsHeight; int &globalOptionsHeight = advanced ? m_advancedData.optionsHeight : m_simpleData.optionsHeight;
globalOptionRows = qMax(globalOptionRows, optionRows); globalOptionRows = qMax(globalOptionRows, optionRows);
globalOptionsHeight = qMax(rowHeight * optionRows + 20, globalOptionsHeight); globalOptionsHeight = qMax(rowHeight * optionRows + 20, globalOptionsHeight);
layout->setContentsMargins(8, 8, 8, 0); layout->setContentsMargins(8, 6, 8, 0);
return layout; return layout;
} }

View File

@@ -37,6 +37,7 @@
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/runextensions.h> #include <utils/runextensions.h>
#include <utils/qtcassert.h>
#include <QApplication> #include <QApplication>
#include <QDir> #include <QDir>
@@ -90,21 +91,16 @@ void ItemLibraryAssetImporter::importQuick3D(const QStringList &inputFiles,
if (!isCancelled()) { if (!isCancelled()) {
const auto parseData = m_parseData; const auto parseData = m_parseData;
for (const auto &pd : parseData) { for (const auto &pd : parseData)
if (!startImportProcess(pd)) { m_puppetQueue.append(pd.importId);
addError(tr("Failed to start import 3D asset process."), startNextImportProcess();
pd.sourceInfo.absoluteFilePath());
m_parseData.remove(pd.importId);
}
}
} }
if (!isCancelled()) { if (!isCancelled()) {
// Wait for puppet processes to finish // Wait for puppet processes to finish
if (m_qmlPuppetProcesses.empty()) { if (m_puppetQueue.isEmpty() && !m_puppetProcess) {
postImport(); postImport();
} else { } else {
m_qmlPuppetCount = static_cast<int>(m_qmlPuppetProcesses.size());
const QString progressTitle = tr("Importing 3D assets."); const QString progressTitle = tr("Importing 3D assets.");
addInfo(progressTitle); addInfo(progressTitle);
notifyProgress(0, progressTitle); notifyProgress(0, progressTitle);
@@ -142,21 +138,14 @@ void ItemLibraryAssetImporter::addInfo(const QString &infoMsg, const QString &sr
emit infoReported(infoMsg, srcPath); emit infoReported(infoMsg, srcPath);
} }
void ItemLibraryAssetImporter::importProcessFinished(int exitCode, QProcess::ExitStatus exitStatus, void ItemLibraryAssetImporter::importProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
int importId)
{ {
Q_UNUSED(exitCode) Q_UNUSED(exitCode)
++m_qmlImportFinishedCount; m_puppetProcess.reset();
m_qmlPuppetProcesses.erase( if (m_parseData.contains(m_currentImportId)) {
std::remove_if(m_qmlPuppetProcesses.begin(), m_qmlPuppetProcesses.end(), const ParseData &pd = m_parseData[m_currentImportId];
[&](const auto &entry) {
return !entry || entry->state() == QProcess::NotRunning;
}));
if (m_parseData.contains(importId)) {
const ParseData &pd = m_parseData[importId];
QString errStr; QString errStr;
if (exitStatus == QProcess::ExitStatus::CrashExit) { if (exitStatus == QProcess::ExitStatus::CrashExit) {
errStr = tr("Import process crashed."); errStr = tr("Import process crashed.");
@@ -179,15 +168,19 @@ void ItemLibraryAssetImporter::importProcessFinished(int exitCode, QProcess::Exi
addError(tr("Asset import process failed: \"%1\".") addError(tr("Asset import process failed: \"%1\".")
.arg(pd.sourceInfo.absoluteFilePath())); .arg(pd.sourceInfo.absoluteFilePath()));
addError(errStr); addError(errStr);
m_parseData.remove(importId); m_parseData.remove(m_currentImportId);
} }
} }
if (m_qmlImportFinishedCount == m_qmlPuppetCount) { int finishedCount = m_parseData.size() - m_puppetQueue.size();
if (!m_puppetQueue.isEmpty())
startNextImportProcess();
if (m_puppetQueue.isEmpty() && !m_puppetProcess) {
notifyProgress(100); notifyProgress(100);
QTimer::singleShot(0, this, &ItemLibraryAssetImporter::postImport); QTimer::singleShot(0, this, &ItemLibraryAssetImporter::postImport);
} else { } else {
notifyProgress(int(100. * (double(m_qmlImportFinishedCount) / double(m_qmlPuppetCount)))); notifyProgress(int(100. * (double(finishedCount) / double(m_parseData.size()))));
} }
} }
@@ -196,17 +189,17 @@ void ItemLibraryAssetImporter::iconProcessFinished(int exitCode, QProcess::ExitS
Q_UNUSED(exitCode) Q_UNUSED(exitCode)
Q_UNUSED(exitStatus) Q_UNUSED(exitStatus)
m_qmlPuppetProcesses.erase( m_puppetProcess.reset();
std::remove_if(m_qmlPuppetProcesses.begin(), m_qmlPuppetProcesses.end(),
[&](const auto &entry) {
return !entry || entry->state() == QProcess::NotRunning;
}));
if (m_qmlPuppetProcesses.empty()) { int finishedCount = m_parseData.size() - m_puppetQueue.size();
if (!m_puppetQueue.isEmpty())
startNextIconProcess();
if (m_puppetQueue.isEmpty() && !m_puppetProcess) {
notifyProgress(100); notifyProgress(100);
QTimer::singleShot(0, this, &ItemLibraryAssetImporter::finalizeQuick3DImport); QTimer::singleShot(0, this, &ItemLibraryAssetImporter::finalizeQuick3DImport);
} else { } else {
notifyProgress(int(100. * (1. - (double(m_qmlPuppetProcesses.size()) / double(m_qmlPuppetCount))))); notifyProgress(int(100. * (double(finishedCount) / double(m_parseData.size()))));
} }
} }
@@ -225,11 +218,11 @@ void ItemLibraryAssetImporter::reset()
m_tempDir = new QTemporaryDir; m_tempDir = new QTemporaryDir;
m_importFiles.clear(); m_importFiles.clear();
m_overwrittenImports.clear(); m_overwrittenImports.clear();
m_qmlPuppetProcesses.clear(); m_puppetProcess.reset();
m_qmlPuppetCount = 0;
m_qmlImportFinishedCount = 0;
m_parseData.clear(); m_parseData.clear();
m_requiredImports.clear(); m_requiredImports.clear();
m_currentImportId = 0;
m_puppetQueue.clear();
} }
void ItemLibraryAssetImporter::parseFiles(const QStringList &filePaths, void ItemLibraryAssetImporter::parseFiles(const QStringList &filePaths,
@@ -351,7 +344,7 @@ bool ItemLibraryAssetImporter::preParseQuick3DAsset(const QString &file, ParseDa
return true; return true;
} }
void ItemLibraryAssetImporter::postParseQuick3DAsset(const ParseData &pd) void ItemLibraryAssetImporter::postParseQuick3DAsset(ParseData &pd)
{ {
QDir outDir = pd.outDir; QDir outDir = pd.outDir;
if (pd.originalAssetName != pd.assetName) { if (pd.originalAssetName != pd.assetName) {
@@ -452,8 +445,10 @@ void ItemLibraryAssetImporter::postParseQuick3DAsset(const ParseData &pd)
"QtQuick3D", impVersionStr)); "QtQuick3D", impVersionStr));
} }
} }
if (impVersionMajor > 0 && impVersionMajor < 6 if (impVersionMajor > 0 && impVersionMajor < 6) {
&& startIconProcess(24, iconFileName, qmlIt.filePath())) { pd.iconFile = iconFileName;
pd.iconSource = qmlIt.filePath();
m_puppetQueue.append(pd.importId);
// Since icon is generated by external process, the file won't be // Since icon is generated by external process, the file won't be
// ready for asset gathering below, so assume its generation succeeds // ready for asset gathering below, so assume its generation succeeds
// and add it now. // and add it now.
@@ -589,51 +584,67 @@ ItemLibraryAssetImporter::OverwriteResult ItemLibraryAssetImporter::confirmAsset
return OverwriteResult::Skip; return OverwriteResult::Skip;
} }
bool ItemLibraryAssetImporter::startImportProcess(const ParseData &pd) void ItemLibraryAssetImporter::startNextImportProcess()
{ {
if (m_puppetQueue.isEmpty())
return;
auto doc = QmlDesignerPlugin::instance()->currentDesignDocument(); auto doc = QmlDesignerPlugin::instance()->currentDesignDocument();
Model *model = doc ? doc->currentModel() : nullptr; Model *model = doc ? doc->currentModel() : nullptr;
if (model) { if (model) {
PuppetCreator puppetCreator(doc->currentTarget(), model); PuppetCreator puppetCreator(doc->currentTarget(), model);
puppetCreator.createQml2PuppetExecutableIfMissing(); puppetCreator.createQml2PuppetExecutableIfMissing();
bool done = false;
while (!m_puppetQueue.isEmpty() && !done) {
const ParseData pd = m_parseData.value(m_puppetQueue.takeLast());
QStringList puppetArgs; QStringList puppetArgs;
QJsonDocument optDoc(pd.options); QJsonDocument optDoc(pd.options);
puppetArgs << "--import3dAsset" << pd.sourceInfo.absoluteFilePath() puppetArgs << "--import3dAsset" << pd.sourceInfo.absoluteFilePath()
<< pd.outDir.absolutePath() << QString::fromUtf8(optDoc.toJson()); << pd.outDir.absolutePath() << QString::fromUtf8(optDoc.toJson());
QProcessUniquePointer process = puppetCreator.createPuppetProcess( m_currentImportId = pd.importId;
m_puppetProcess = puppetCreator.createPuppetProcess(
"custom", "custom",
{}, {},
[&] {}, [&] {},
[&](int exitCode, QProcess::ExitStatus exitStatus) { [&](int exitCode, QProcess::ExitStatus exitStatus) {
importProcessFinished(exitCode, exitStatus, pd.importId); importProcessFinished(exitCode, exitStatus);
}, },
puppetArgs); puppetArgs);
if (process->waitForStarted(5000)) { if (m_puppetProcess->waitForStarted(10000)) {
m_qmlPuppetProcesses.push_back(std::move(process)); done = true;
return true;
} else { } else {
process.reset(); addError(tr("Failed to start import 3D asset process."),
pd.sourceInfo.absoluteFilePath());
m_parseData.remove(pd.importId);
m_puppetProcess.reset();
}
} }
} }
return false;
} }
bool ItemLibraryAssetImporter::startIconProcess(int size, const QString &iconFile, void ItemLibraryAssetImporter::startNextIconProcess()
const QString &iconSource)
{ {
if (m_puppetQueue.isEmpty())
return;
auto doc = QmlDesignerPlugin::instance()->currentDesignDocument(); auto doc = QmlDesignerPlugin::instance()->currentDesignDocument();
Model *model = doc ? doc->currentModel() : nullptr; Model *model = doc ? doc->currentModel() : nullptr;
if (model) { if (model) {
PuppetCreator puppetCreator(doc->currentTarget(), model); PuppetCreator puppetCreator(doc->currentTarget(), model);
puppetCreator.createQml2PuppetExecutableIfMissing(); puppetCreator.createQml2PuppetExecutableIfMissing();
bool done = false;
while (!m_puppetQueue.isEmpty() && !done) {
const ParseData pd = m_parseData.value(m_puppetQueue.takeLast());
QStringList puppetArgs; QStringList puppetArgs;
puppetArgs << "--rendericon" << QString::number(size) << iconFile << iconSource; puppetArgs << "--rendericon" << QString::number(24) << pd.iconFile << pd.iconSource;
QProcessUniquePointer process = puppetCreator.createPuppetProcess( m_puppetProcess = puppetCreator.createPuppetProcess(
"custom", "custom",
{}, {},
[&] {}, [&] {},
@@ -642,31 +653,32 @@ bool ItemLibraryAssetImporter::startIconProcess(int size, const QString &iconFil
}, },
puppetArgs); puppetArgs);
if (process->waitForStarted(5000)) { if (m_puppetProcess->waitForStarted(10000)) {
m_qmlPuppetProcesses.push_back(std::move(process)); done = true;
return true;
} else { } else {
process.reset(); addError(tr("Failed to start icon generation process."),
pd.sourceInfo.absoluteFilePath());
m_puppetProcess.reset();
}
} }
} }
return false;
} }
void ItemLibraryAssetImporter::postImport() void ItemLibraryAssetImporter::postImport()
{ {
Q_ASSERT(m_qmlPuppetProcesses.empty()); QTC_ASSERT(m_puppetQueue.isEmpty() && !m_puppetProcess, return);
if (!isCancelled()) { if (!isCancelled()) {
for (const auto &pd : qAsConst(m_parseData)) for (auto &pd : m_parseData)
postParseQuick3DAsset(pd); postParseQuick3DAsset(pd);
startNextIconProcess();
} }
if (!isCancelled()) { if (!isCancelled()) {
// Wait for icon generation processes to finish // Wait for icon generation processes to finish
if (m_qmlPuppetProcesses.empty()) { if (m_puppetQueue.isEmpty() && !m_puppetProcess) {
finalizeQuick3DImport(); finalizeQuick3DImport();
} else { } else {
m_qmlPuppetCount = static_cast<int>(m_qmlPuppetProcesses.size());
const QString progressTitle = tr("Generating icons."); const QString progressTitle = tr("Generating icons.");
addInfo(progressTitle); addInfo(progressTitle);
notifyProgress(0, progressTitle); notifyProgress(0, progressTitle);

View File

@@ -74,7 +74,7 @@ signals:
void importFinished(); void importFinished();
private slots: private slots:
void importProcessFinished(int exitCode, QProcess::ExitStatus exitStatus, int importId); void importProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
void iconProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); void iconProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
private: private:
@@ -87,6 +87,8 @@ private:
QString assetName; QString assetName;
QString originalAssetName; QString originalAssetName;
int importId; int importId;
QString iconFile;
QString iconSource;
}; };
void notifyFinished(); void notifyFinished();
@@ -96,7 +98,7 @@ private:
const QSet<QString> &preselectedFilesForOverwrite); const QSet<QString> &preselectedFilesForOverwrite);
bool preParseQuick3DAsset(const QString &file, ParseData &pd, bool preParseQuick3DAsset(const QString &file, ParseData &pd,
const QSet<QString> &preselectedFilesForOverwrite); const QSet<QString> &preselectedFilesForOverwrite);
void postParseQuick3DAsset(const ParseData &pd); void postParseQuick3DAsset(ParseData &pd);
void copyImportedFiles(); void copyImportedFiles();
void notifyProgress(int value, const QString &text); void notifyProgress(int value, const QString &text);
@@ -110,8 +112,8 @@ private:
}; };
OverwriteResult confirmAssetOverwrite(const QString &assetName); OverwriteResult confirmAssetOverwrite(const QString &assetName);
bool startImportProcess(const ParseData &pd); void startNextImportProcess();
bool startIconProcess(int size, const QString &iconFile, const QString &iconSource); void startNextIconProcess();
void postImport(); void postImport();
void finalizeQuick3DImport(); void finalizeQuick3DImport();
QString sourceSceneTargetFilePath(const ParseData &pd); QString sourceSceneTargetFilePath(const ParseData &pd);
@@ -122,12 +124,12 @@ private:
bool m_cancelled = false; bool m_cancelled = false;
QString m_importPath; QString m_importPath;
QTemporaryDir *m_tempDir = nullptr; QTemporaryDir *m_tempDir = nullptr;
std::vector<QProcessUniquePointer> m_qmlPuppetProcesses; QProcessUniquePointer m_puppetProcess;
int m_qmlPuppetCount = 0;
int m_qmlImportFinishedCount = 0;
int m_importIdCounter = 0; int m_importIdCounter = 0;
int m_currentImportId = 0;
QHash<int, ParseData> m_parseData; QHash<int, ParseData> m_parseData;
QString m_progressTitle; QString m_progressTitle;
QList<Import> m_requiredImports; QList<Import> m_requiredImports;
QList<int> m_puppetQueue;
}; };
} // QmlDesigner } // QmlDesigner

View File

@@ -126,10 +126,6 @@ bool ItemLibraryWidget::eventFilter(QObject *obj, QEvent *event)
m_itemToDrag = {}; m_itemToDrag = {};
} }
} }
} else if (event->type() == QMouseEvent::MouseButtonRelease) {
m_itemToDrag = {};
if (model)
model->endDrag();
} }
return QObject::eventFilter(obj, event); return QObject::eventFilter(obj, event);

View File

@@ -120,6 +120,11 @@ void MaterialBrowserModel::setHasModelSelection(bool b)
emit hasModelSelectionChanged(); emit hasModelSelectionChanged();
} }
QList<ModelNode> MaterialBrowserModel::materials() const
{
return m_materialList;
}
void MaterialBrowserModel::setSearchText(const QString &searchText) void MaterialBrowserModel::setSearchText(const QString &searchText)
{ {
QString lowerSearchText = searchText.toLower(); QString lowerSearchText = searchText.toLower();

View File

@@ -58,6 +58,7 @@ public:
bool hasModelSelection() const; bool hasModelSelection() const;
void setHasModelSelection(bool b); void setHasModelSelection(bool b);
QList<ModelNode> materials() const;
void setMaterials(const QList<ModelNode> &materials, bool hasQuick3DImport); void setMaterials(const QList<ModelNode> &materials, bool hasQuick3DImport);
void removeMaterial(const ModelNode &material); void removeMaterial(const ModelNode &material);
void updateMaterialName(const ModelNode &material); void updateMaterialName(const ModelNode &material);

View File

@@ -96,11 +96,19 @@ void MaterialBrowserView::modelAttached(Model *model)
m_widget->clearSearchFilter(); m_widget->clearSearchFilter();
m_hasQuick3DImport = model->hasImport("QtQuick3D"); m_hasQuick3DImport = model->hasImport("QtQuick3D");
QTimer::singleShot(0, this, &MaterialBrowserView::refreshModel);
// Project load is already very busy and may even trigger puppet reset, so let's wait a moment
// before refreshing the model
QTimer::singleShot(1000, this, [this]() {
refreshModel(true);
});
} }
void MaterialBrowserView::refreshModel() void MaterialBrowserView::refreshModel(bool updateImages)
{ {
if (!model() || !model()->nodeInstanceView())
return;
ModelNode matLib = modelNodeForId(Constants::MATERIAL_LIB_ID); ModelNode matLib = modelNodeForId(Constants::MATERIAL_LIB_ID);
QList <ModelNode> materials; QList <ModelNode> materials;
@@ -114,9 +122,11 @@ void MaterialBrowserView::refreshModel()
m_widget->materialBrowserModel()->setMaterials(materials, m_hasQuick3DImport); m_widget->materialBrowserModel()->setMaterials(materials, m_hasQuick3DImport);
if (updateImages) {
for (const ModelNode &node : std::as_const(materials)) for (const ModelNode &node : std::as_const(materials))
model()->nodeInstanceView()->previewImageDataForGenericNode(node, {}); model()->nodeInstanceView()->previewImageDataForGenericNode(node, {});
} }
}
bool MaterialBrowserView::isMaterial(const ModelNode &node) const bool MaterialBrowserView::isMaterial(const ModelNode &node) const
{ {
@@ -203,8 +213,12 @@ void MaterialBrowserView::nodeReparented(const ModelNode &node,
bool matRemoved = oldParentNode.isValid() && oldParentNode.id() == Constants::MATERIAL_LIB_ID; bool matRemoved = oldParentNode.isValid() && oldParentNode.id() == Constants::MATERIAL_LIB_ID;
if (matAdded || matRemoved) { if (matAdded || matRemoved) {
refreshModel(); if (matAdded && !m_puppetResetPending) {
// Workaround to fix various material issues all likely caused by QTBUG-103316
resetPuppet();
m_puppetResetPending = true;
}
refreshModel(!matAdded);
int idx = m_widget->materialBrowserModel()->materialIndex(node); int idx = m_widget->materialBrowserModel()->materialIndex(node);
m_widget->materialBrowserModel()->selectMaterial(idx); m_widget->materialBrowserModel()->selectMaterial(idx);
} }
@@ -251,7 +265,9 @@ void MaterialBrowserView::importsChanged(const QList<Import> &addedImports, cons
return; return;
m_hasQuick3DImport = hasQuick3DImport; m_hasQuick3DImport = hasQuick3DImport;
refreshModel();
// Import change will trigger puppet reset, so we don't want to update previews immediately
refreshModel(false);
} }
void MaterialBrowserView::customNotification(const AbstractView *view, const QString &identifier, void MaterialBrowserView::customNotification(const AbstractView *view, const QString &identifier,
@@ -269,4 +285,22 @@ void MaterialBrowserView::customNotification(const AbstractView *view, const QSt
} }
} }
void MaterialBrowserView::instancesCompleted(const QVector<ModelNode> &completedNodeList)
{
for (const ModelNode &node : completedNodeList) {
// We use root node completion as indication of puppet reset
if (node.isRootNode()) {
m_puppetResetPending = false;
QTimer::singleShot(1000, this, [this]() {
if (!model() || !model()->nodeInstanceView())
return;
const QList<ModelNode> materials = m_widget->materialBrowserModel()->materials();
for (const ModelNode &node : materials)
model()->nodeInstanceView()->previewImageDataForGenericNode(node, {});
});
break;
}
}
}
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -60,14 +60,16 @@ public:
void importsChanged(const QList<Import> &addedImports, const QList<Import> &removedImports) override; void importsChanged(const QList<Import> &addedImports, const QList<Import> &removedImports) override;
void customNotification(const AbstractView *view, const QString &identifier, void customNotification(const AbstractView *view, const QString &identifier,
const QList<ModelNode> &nodeList, const QList<QVariant> &data) override; const QList<ModelNode> &nodeList, const QList<QVariant> &data) override;
void instancesCompleted(const QVector<ModelNode> &completedNodeList) override;
private: private:
void refreshModel(); void refreshModel(bool updateImages);
bool isMaterial(const ModelNode &node) const; bool isMaterial(const ModelNode &node) const;
QPointer<MaterialBrowserWidget> m_widget; QPointer<MaterialBrowserWidget> m_widget;
bool m_hasQuick3DImport = false; bool m_hasQuick3DImport = false;
bool m_autoSelectModelMaterial = false; // TODO: wire this to some action bool m_autoSelectModelMaterial = false; // TODO: wire this to some action
bool m_puppetResetPending = false;
}; };
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -32,6 +32,7 @@
#include <designermcumanager.h> #include <designermcumanager.h>
#include <documentmanager.h> #include <documentmanager.h>
#include <qmldesignerconstants.h> #include <qmldesignerconstants.h>
#include <qmldesignerplugin.h>
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/stylehelper.h> #include <utils/stylehelper.h>
@@ -103,6 +104,27 @@ bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event)
if (event->type() == QEvent::FocusOut) { if (event->type() == QEvent::FocusOut) {
if (obj == m_quickWidget.data()) if (obj == m_quickWidget.data())
QMetaObject::invokeMethod(m_quickWidget->rootObject(), "closeContextMenu"); QMetaObject::invokeMethod(m_quickWidget->rootObject(), "closeContextMenu");
} else if (event->type() == QMouseEvent::MouseMove) {
DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument();
QTC_ASSERT(document, return false);
Model *model = document->currentModel();
QTC_ASSERT(model, return false);
if (m_materialToDrag.isValid()) {
QMouseEvent *me = static_cast<QMouseEvent *>(event);
if ((me->globalPos() - m_dragStartPoint).manhattanLength() > 10) {
QByteArray data;
QMimeData *mimeData = new QMimeData;
QDataStream stream(&data, QIODevice::WriteOnly);
stream << m_materialToDrag.internalId();
mimeData->setData(Constants::MIME_TYPE_MATERIAL, data);
mimeData->removeFormat("text/plain");
model->startDrag(mimeData, m_previewImageProvider->requestPixmap(
QString::number(m_materialToDrag.internalId()), nullptr, {128, 128}));
m_materialToDrag = {};
}
}
} }
return QObject::eventFilter(obj, event); return QObject::eventFilter(obj, event);
@@ -166,6 +188,12 @@ void MaterialBrowserWidget::handleSearchfilterChanged(const QString &filterText)
} }
} }
void MaterialBrowserWidget::startDragMaterial(int index, const QPointF &mousePos)
{
m_materialToDrag = m_materialBrowserModel->materialAt(index);
m_dragStartPoint = mousePos.toPoint();
}
QString MaterialBrowserWidget::qmlSourcesPath() QString MaterialBrowserWidget::qmlSourcesPath()
{ {
#ifdef SHARE_QML_PATH #ifdef SHARE_QML_PATH

View File

@@ -69,6 +69,7 @@ public:
void updateMaterialPreview(const ModelNode &node, const QPixmap &pixmap); void updateMaterialPreview(const ModelNode &node, const QPixmap &pixmap);
Q_INVOKABLE void handleSearchfilterChanged(const QString &filterText); Q_INVOKABLE void handleSearchfilterChanged(const QString &filterText);
Q_INVOKABLE void startDragMaterial(int index, const QPointF &mousePos);
QQuickWidget *quickWidget() const; QQuickWidget *quickWidget() const;
@@ -86,6 +87,9 @@ private:
PreviewImageProvider *m_previewImageProvider = nullptr; PreviewImageProvider *m_previewImageProvider = nullptr;
QString m_filterText; QString m_filterText;
ModelNode m_materialToDrag;
QPoint m_dragStartPoint;
}; };
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -73,52 +73,21 @@ MaterialEditorView::MaterialEditorView(QWidget *parent)
m_updateShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F7), m_stackedWidget); m_updateShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F7), m_stackedWidget);
connect(m_updateShortcut, &QShortcut::activated, this, &MaterialEditorView::reloadQml); connect(m_updateShortcut, &QShortcut::activated, this, &MaterialEditorView::reloadQml);
m_ensureMatLibTimer.callOnTimeout([this] {
if (model() && model()->rewriterView() && !model()->rewriterView()->hasIncompleteTypeInformation()
&& model()->rewriterView()->errors().isEmpty()) {
executeInTransaction("MaterialEditorView::MaterialEditorView", [this] {
ensureMaterialLibraryNode();
});
m_ensureMatLibTimer.stop();
}
});
m_stackedWidget->setStyleSheet(Theme::replaceCssColors( m_stackedWidget->setStyleSheet(Theme::replaceCssColors(
QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css")))); QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css"))));
m_stackedWidget->setMinimumWidth(250); m_stackedWidget->setMinimumWidth(250);
} }
void MaterialEditorView::ensureMaterialLibraryNode()
{
if (!m_hasQuick3DImport)
return;
m_materialLibrary = modelNodeForId(Constants::MATERIAL_LIB_ID);
if (m_materialLibrary.isValid())
return;
// create material library node
TypeName nodeType = rootModelNode().isSubclassOf("QtQuick3D.Node") ? "QtQuick3D.Node" : "QtQuick.Item";
NodeMetaInfo metaInfo = model()->metaInfo(nodeType);
m_materialLibrary = createModelNode(nodeType, metaInfo.majorVersion(), metaInfo.minorVersion());
m_materialLibrary.setIdWithoutRefactoring(Constants::MATERIAL_LIB_ID);
rootModelNode().defaultNodeListProperty().reparentHere(m_materialLibrary);
const QList<ModelNode> materials = rootModelNode().subModelNodesOfType("QtQuick3D.Material");
if (materials.isEmpty())
return;
RewriterTransaction transaction = beginRewriterTransaction(
"MaterialEditorView::ensureMaterialLibraryNode");
try {
// move all materials to under material library node
for (const ModelNode &node : materials) {
// if material has no name, set name to id
QString matName = node.variantProperty("objectName").value().toString();
if (matName.isEmpty()) {
VariantProperty objNameProp = node.variantProperty("objectName");
objNameProp.setValue(node.id());
}
m_materialLibrary.defaultNodeListProperty().reparentHere(node);
}
} catch (Exception &e) {
e.showException();
}
}
MaterialEditorView::~MaterialEditorView() MaterialEditorView::~MaterialEditorView()
{ {
qDeleteAll(m_qmlBackendHash); qDeleteAll(m_qmlBackendHash);
@@ -447,15 +416,15 @@ void MaterialEditorView::handleToolBarAction(int action)
} }
case MaterialEditorContextObject::AddNewMaterial: { case MaterialEditorContextObject::AddNewMaterial: {
ensureMaterialLibraryNode(); if (!model())
break;
executeInTransaction("MaterialEditorView:handleToolBarAction", [&] { executeInTransaction("MaterialEditorView:handleToolBarAction", [&] {
NodeMetaInfo metaInfo = model()->metaInfo("QtQuick3D.DefaultMaterial"); NodeMetaInfo metaInfo = model()->metaInfo("QtQuick3D.DefaultMaterial");
ModelNode newMatNode = createModelNode("QtQuick3D.DefaultMaterial", metaInfo.majorVersion(), ModelNode newMatNode = createModelNode("QtQuick3D.DefaultMaterial", metaInfo.majorVersion(),
metaInfo.minorVersion()); metaInfo.minorVersion());
renameMaterial(newMatNode, "New Material"); renameMaterial(newMatNode, "New Material");
m_materialLibrary.defaultNodeListProperty().reparentHere(newMatNode); materialLibraryNode().defaultNodeListProperty().reparentHere(newMatNode);
}); });
break; break;
} }
@@ -567,6 +536,11 @@ void MaterialEditorView::modelAttached(Model *model)
m_hasQuick3DImport = model->hasImport("QtQuick3D"); m_hasQuick3DImport = model->hasImport("QtQuick3D");
// Creating the material library node on model attach causes errors as long as the type information
// not complete yet, so we keep checking until type info is complete.
if (m_hasQuick3DImport)
m_ensureMatLibTimer.start(500);
if (!m_setupCompleted) { if (!m_setupCompleted) {
reloadQml(); reloadQml();
m_setupCompleted = true; m_setupCompleted = true;
@@ -580,8 +554,6 @@ void MaterialEditorView::modelAboutToBeDetached(Model *model)
{ {
AbstractView::modelAboutToBeDetached(model); AbstractView::modelAboutToBeDetached(model);
m_qmlBackEnd->materialEditorTransaction()->end(); m_qmlBackEnd->materialEditorTransaction()->end();
} }
void MaterialEditorView::propertiesRemoved(const QList<AbstractProperty> &propertyList) void MaterialEditorView::propertiesRemoved(const QList<AbstractProperty> &propertyList)
@@ -589,6 +561,7 @@ void MaterialEditorView::propertiesRemoved(const QList<AbstractProperty> &proper
if (noValidSelection()) if (noValidSelection())
return; return;
bool changed = false;
for (const AbstractProperty &property : propertyList) { for (const AbstractProperty &property : propertyList) {
ModelNode node(property.parentModelNode()); ModelNode node(property.parentModelNode());
@@ -597,8 +570,11 @@ void MaterialEditorView::propertiesRemoved(const QList<AbstractProperty> &proper
if (node == m_selectedMaterial || QmlObjectNode(m_selectedMaterial).propertyChangeForCurrentState() == node) { if (node == m_selectedMaterial || QmlObjectNode(m_selectedMaterial).propertyChangeForCurrentState() == node) {
setValue(m_selectedMaterial, property.name(), QmlObjectNode(m_selectedMaterial).instanceValue(property.name())); setValue(m_selectedMaterial, property.name(), QmlObjectNode(m_selectedMaterial).instanceValue(property.name()));
changed = true;
} }
} }
if (changed)
requestPreviewRender();
} }
void MaterialEditorView::variantPropertiesChanged(const QList<VariantProperty> &propertyList, PropertyChangeFlags /*propertyChange*/) void MaterialEditorView::variantPropertiesChanged(const QList<VariantProperty> &propertyList, PropertyChangeFlags /*propertyChange*/)
@@ -660,7 +636,7 @@ void MaterialEditorView::auxiliaryDataChanged(const ModelNode &node, const Prope
// request render image for the selected material node // request render image for the selected material node
void MaterialEditorView::requestPreviewRender() void MaterialEditorView::requestPreviewRender()
{ {
if (m_selectedMaterial.isValid()) if (model() && model()->nodeInstanceView() && m_selectedMaterial.isValid())
model()->nodeInstanceView()->previewImageDataForGenericNode(m_selectedMaterial, {}); model()->nodeInstanceView()->previewImageDataForGenericNode(m_selectedMaterial, {});
} }
@@ -707,6 +683,7 @@ void MaterialEditorView::instancePropertyChanged(const QList<QPair<ModelNode, Pr
m_locked = true; m_locked = true;
bool changed = false;
for (const QPair<ModelNode, PropertyName> &propertyPair : propertyList) { for (const QPair<ModelNode, PropertyName> &propertyPair : propertyList) {
const ModelNode modelNode = propertyPair.first; const ModelNode modelNode = propertyPair.first;
const QmlObjectNode qmlObjectNode(modelNode); const QmlObjectNode qmlObjectNode(modelNode);
@@ -718,8 +695,11 @@ void MaterialEditorView::instancePropertyChanged(const QList<QPair<ModelNode, Pr
setValue(modelNode, property.name(), qmlObjectNode.instanceValue(property.name())); setValue(modelNode, property.name(), qmlObjectNode.instanceValue(property.name()));
else else
setValue(modelNode, property.name(), qmlObjectNode.modelValue(property.name())); setValue(modelNode, property.name(), qmlObjectNode.modelValue(property.name()));
changed = true;
} }
} }
if (changed)
requestPreviewRender();
m_locked = false; m_locked = false;
} }
@@ -744,6 +724,9 @@ void MaterialEditorView::importsChanged(const QList<Import> &addedImports, const
m_hasQuick3DImport = model()->hasImport("QtQuick3D"); m_hasQuick3DImport = model()->hasImport("QtQuick3D");
m_qmlBackEnd->contextObject()->setHasQuick3DImport(m_hasQuick3DImport); m_qmlBackEnd->contextObject()->setHasQuick3DImport(m_hasQuick3DImport);
if (m_hasQuick3DImport)
m_ensureMatLibTimer.start(500);
resetView(); resetView();
} }
@@ -763,7 +746,8 @@ void MaterialEditorView::duplicateMaterial(const ModelNode &material)
{ {
QTC_ASSERT(material.isValid(), return); QTC_ASSERT(material.isValid(), return);
ensureMaterialLibraryNode(); if (!model())
return;
TypeName matType = material.type(); TypeName matType = material.type();
QmlObjectNode sourceMat(material); QmlObjectNode sourceMat(material);
@@ -790,7 +774,7 @@ void MaterialEditorView::duplicateMaterial(const ModelNode &material)
duplicateMat.setBindingProperty(prop.name(), prop.toBindingProperty().expression()); duplicateMat.setBindingProperty(prop.name(), prop.toBindingProperty().expression());
} }
m_materialLibrary.defaultNodeListProperty().reparentHere(duplicateMat); materialLibraryNode().defaultNodeListProperty().reparentHere(duplicateMat);
}); });
} }
@@ -816,11 +800,16 @@ void MaterialEditorView::customNotification(const AbstractView *view, const QStr
void QmlDesigner::MaterialEditorView::highlightSupportedProperties(bool highlight) void QmlDesigner::MaterialEditorView::highlightSupportedProperties(bool highlight)
{ {
if (!m_selectedMaterial.isValid())
return;
DesignerPropertyMap &propMap = m_qmlBackEnd->backendValuesPropertyMap(); DesignerPropertyMap &propMap = m_qmlBackEnd->backendValuesPropertyMap();
const QStringList propNames = propMap.keys(); const QStringList propNames = propMap.keys();
NodeMetaInfo metaInfo = m_selectedMaterial.metaInfo();
QTC_ASSERT(metaInfo.isValid(), return);
for (const QString &propName : propNames) { for (const QString &propName : propNames) {
if (propName.endsWith("Map")) { if (metaInfo.propertyTypeName(propName.toLatin1()) == "QtQuick3D.Texture") {
QObject *propEditorValObj = propMap.value(propName).value<QObject *>(); QObject *propEditorValObj = propMap.value(propName).value<QObject *>();
PropertyEditorValue *propEditorVal = qobject_cast<PropertyEditorValue *>(propEditorValObj); PropertyEditorValue *propEditorVal = qobject_cast<PropertyEditorValue *>(propEditorValObj);
propEditorVal->setHasActiveDrag(highlight); propEditorVal->setHasActiveDrag(highlight);
@@ -852,7 +841,6 @@ void MaterialEditorView::setValue(const QmlObjectNode &qmlObjectNode, const Prop
{ {
m_locked = true; m_locked = true;
m_qmlBackEnd->setValue(qmlObjectNode, name, value); m_qmlBackEnd->setValue(qmlObjectNode, name, value);
requestPreviewRender();
m_locked = false; m_locked = false;
} }

View File

@@ -99,7 +99,6 @@ private:
void highlightSupportedProperties(bool highlight = true); void highlightSupportedProperties(bool highlight = true);
QString generateIdFromName(const QString &name); QString generateIdFromName(const QString &name);
void ensureMaterialLibraryNode();
void requestPreviewRender(); void requestPreviewRender();
void applyMaterialToSelectedModels(const ModelNode &material, bool add = false); void applyMaterialToSelectedModels(const ModelNode &material, bool add = false);
@@ -115,7 +114,7 @@ private:
bool noValidSelection() const; bool noValidSelection() const;
ModelNode m_selectedMaterial; ModelNode m_selectedMaterial;
ModelNode m_materialLibrary; QTimer m_ensureMatLibTimer;
QShortcut *m_updateShortcut = nullptr; QShortcut *m_updateShortcut = nullptr;
int m_timerId = 0; int m_timerId = 0;
QStackedWidget *m_stackedWidget = nullptr; QStackedWidget *m_stackedWidget = nullptr;

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 B

View File

@@ -56,6 +56,8 @@ ChooseFromPropertyListFilter::ChooseFromPropertyListFilter(const NodeMetaInfo &i
// ParticleAbstractShape3D // ParticleAbstractShape3D
// -> ParticleEmitter3D // -> ParticleEmitter3D
// -> Attractor3D // -> Attractor3D
// Material
// -> Model
const TypeName textureType = "QtQuick3D.Texture"; const TypeName textureType = "QtQuick3D.Texture";
if (insertInfo.isSubclassOf(textureType)) { if (insertInfo.isSubclassOf(textureType)) {
@@ -104,6 +106,9 @@ ChooseFromPropertyListFilter::ChooseFromPropertyListFilter(const NodeMetaInfo &i
if (parentInfo.isSubclassOf("QtQuick3D.Particles3D.ParticleEmitter3D") if (parentInfo.isSubclassOf("QtQuick3D.Particles3D.ParticleEmitter3D")
|| parentInfo.isSubclassOf("QtQuick3D.Particles3D.Attractor3D")) || parentInfo.isSubclassOf("QtQuick3D.Particles3D.Attractor3D"))
propertyList.append("shape"); propertyList.append("shape");
} else if (insertInfo.isSubclassOf("QtQuick3D.Material")) {
if (parentInfo.isSubclassOf("QtQuick3D.Particles3D.Model"))
propertyList.append("materials");
} }
} }

View File

@@ -13,5 +13,6 @@
<file>export_unchecked.png</file> <file>export_unchecked.png</file>
<file>export_unchecked@2x.png</file> <file>export_unchecked@2x.png</file>
<file>tooltip_placeholder.png</file> <file>tooltip_placeholder.png</file>
<file>checkers.png</file>
</qresource> </qresource>
</RCC> </RCC>

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