aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick')
-rw-r--r--src/quick/CMakeLists.txt238
-rw-r--r--src/quick/accessible/qaccessiblequickitem.cpp31
-rw-r--r--src/quick/accessible/qaccessiblequickitem_p.h2
-rw-r--r--src/quick/accessible/qaccessiblequickview.cpp10
-rw-r--r--src/quick/configure.cmake7
-rw-r--r--src/quick/designer/qqmldesignermetaobject_p.h2
-rw-r--r--src/quick/designer/qquickdesignersupport.cpp2
-rw-r--r--src/quick/doc/images/declarative-scalegrid.pngbin4228 -> 7712 bytes
-rw-r--r--src/quick/doc/images/how-to-time-picker-dark.pngbin0 -> 26831 bytes
-rw-r--r--src/quick/doc/images/how-to-time-picker-light.pngbin0 -> 25659 bytes
-rw-r--r--src/quick/doc/images/pinchAndDragHandlers-drag-one-rect.pngbin0 -> 6222 bytes
-rw-r--r--src/quick/doc/images/pinchAndDragHandlers-drag-two-rects.pngbin0 -> 7828 bytes
-rw-r--r--src/quick/doc/images/pinchAndDragHandlers-pinch.pngbin0 -> 8122 bytes
-rw-r--r--src/quick/doc/images/pointerHandlers/dragReleaseMenu.webpbin0 -> 86252 bytes
-rw-r--r--src/quick/doc/images/pointerHandlers/tapHandlerButtonReleaseWithinBounds.webpbin0 -> 53154 bytes
-rw-r--r--src/quick/doc/images/pointerHandlers/tapHandlerButtonWithinBounds.webpbin0 -> 27426 bytes
-rw-r--r--src/quick/doc/images/pointerHandlers/tapHandlerOverlappingButtons.webpbin0 -> 51008 bytes
-rw-r--r--src/quick/doc/images/qml-borderimage-rounded.pngbin0 -> 5728 bytes
-rw-r--r--src/quick/doc/images/qml-borderimage-scaled.pngbin1552 -> 5661 bytes
-rw-r--r--src/quick/doc/images/qml-borderimage-tiled.pngbin1602 -> 4936 bytes
-rw-r--r--src/quick/doc/images/qml-item-canvas-startAngle.pngbin5099 -> 5826 bytes
-rw-r--r--src/quick/doc/images/simpleProxy.pngbin0 -> 4601 bytes
-rw-r--r--src/quick/doc/qtquick.qdocconf37
-rw-r--r--src/quick/doc/snippets/code/doc_src_qtquick.cmake2
-rw-r--r--src/quick/doc/snippets/imgprovider/imageprovider-example.qml2
-rw-r--r--src/quick/doc/snippets/layouts/responsiveDeclarative.qml33
-rw-r--r--src/quick/doc/snippets/layouts/responsiveStates.qml44
-rw-r--r--src/quick/doc/snippets/layouts/simpleProxy.qml62
-rw-r--r--src/quick/doc/snippets/pointerHandlers/dragHandler.qml2
-rw-r--r--src/quick/doc/snippets/pointerHandlers/dragHandlerAcceptedButtons.qml81
-rw-r--r--src/quick/doc/snippets/pointerHandlers/dragHandlerDifferentTarget.qml2
-rw-r--r--src/quick/doc/snippets/pointerHandlers/dragHandlerNullTarget.qml2
-rw-r--r--src/quick/doc/snippets/pointerHandlers/dragReleaseMenu.qml72
-rw-r--r--src/quick/doc/snippets/pointerHandlers/handlerFlick.qml4
-rw-r--r--src/quick/doc/snippets/pointerHandlers/hoverMouseOrStylus.qml2
-rw-r--r--src/quick/doc/snippets/pointerHandlers/hoverTapKeyButton.qml2
-rw-r--r--src/quick/doc/snippets/pointerHandlers/pinchAndDragHandlers.qml54
-rw-r--r--src/quick/doc/snippets/pointerHandlers/pinchHandlerDifferentTarget.qml2
-rw-r--r--src/quick/doc/snippets/pointerHandlers/pinchHandlerSimple.qml (renamed from src/quick/doc/snippets/pointerHandlers/pinchHandler.qml)7
-rw-r--r--src/quick/doc/snippets/pointerHandlers/pointHandler.qml5
-rw-r--r--src/quick/doc/snippets/pointerHandlers/pointHandlerAcceptedButtons.qml21
-rw-r--r--src/quick/doc/snippets/pointerHandlers/pointHandlerAcceptedModifiers.qml36
-rw-r--r--src/quick/doc/snippets/pointerHandlers/pointHandlerCanvasDrawing.qml54
-rw-r--r--src/quick/doc/snippets/pointerHandlers/pointHandlerMargin.qml36
-rw-r--r--src/quick/doc/snippets/pointerHandlers/tapHandlerButtonReleaseWithinBounds.qml51
-rw-r--r--src/quick/doc/snippets/pointerHandlers/tapHandlerButtonWithinBounds.qml51
-rw-r--r--src/quick/doc/snippets/pointerHandlers/tapHandlerGrabChanged.qml46
-rw-r--r--src/quick/doc/snippets/pointerHandlers/tapHandlerOverlappingButtons.qml51
-rw-r--r--src/quick/doc/snippets/pointerHandlers/wheelHandler.qml2
-rw-r--r--src/quick/doc/snippets/qml/anchoranimation.qml2
-rw-r--r--src/quick/doc/snippets/qml/anchorchanges.qml2
-rw-r--r--src/quick/doc/snippets/qml/animatedimage.qml2
-rw-r--r--src/quick/doc/snippets/qml/animation.qml2
-rw-r--r--src/quick/doc/snippets/qml/animators.qml2
-rw-r--r--src/quick/doc/snippets/qml/behavior.qml2
-rw-r--r--src/quick/doc/snippets/qml/borderimage/borderimage-rounded.qml124
-rw-r--r--src/quick/doc/snippets/qml/borderimage/borderimage-scaled.qml92
-rw-r--r--src/quick/doc/snippets/qml/borderimage/borderimage-tiled.qml92
-rw-r--r--src/quick/doc/snippets/qml/borderimage/normal-image.qml48
-rw-r--r--src/quick/doc/snippets/qml/borderimage/pics/borderframe.pngbin0 -> 3411 bytes
-rw-r--r--src/quick/doc/snippets/qml/borderimage/pics/borderframe.svg91
-rw-r--r--src/quick/doc/snippets/qml/boundaryRule.qml4
-rw-r--r--src/quick/doc/snippets/qml/coloranimation.qml2
-rw-r--r--src/quick/doc/snippets/qml/colors.qml2
-rw-r--r--src/quick/doc/snippets/qml/column/column-transitions.qml2
-rw-r--r--src/quick/doc/snippets/qml/column/column.qml2
-rw-r--r--src/quick/doc/snippets/qml/column/vertical-positioner.qml2
-rw-r--r--src/quick/doc/snippets/qml/drag.qml2
-rw-r--r--src/quick/doc/snippets/qml/example.md146
-rw-r--r--src/quick/doc/snippets/qml/externalDragScaledImage.qml28
-rw-r--r--src/quick/doc/snippets/qml/externaldrag.qml8
-rw-r--r--src/quick/doc/snippets/qml/flickable.qml2
-rw-r--r--src/quick/doc/snippets/qml/flickableScrollbar.qml2
-rw-r--r--src/quick/doc/snippets/qml/flipable/flipable.qml2
-rw-r--r--src/quick/doc/snippets/qml/flow.qml2
-rw-r--r--src/quick/doc/snippets/qml/focus/MyClickableWidget.qml2
-rw-r--r--src/quick/doc/snippets/qml/focus/MyWidget.qml2
-rw-r--r--src/quick/doc/snippets/qml/focus/advancedFocus.qml2
-rw-r--r--src/quick/doc/snippets/qml/focus/basicwidget.qml2
-rw-r--r--src/quick/doc/snippets/qml/focus/clickablewidget.qml2
-rw-r--r--src/quick/doc/snippets/qml/focus/myfocusscopewidget.qml2
-rw-r--r--src/quick/doc/snippets/qml/focus/rectangle.qml2
-rw-r--r--src/quick/doc/snippets/qml/focus/widget.qml2
-rw-r--r--src/quick/doc/snippets/qml/font.qml22
-rw-r--r--src/quick/doc/snippets/qml/gradient.qml2
-rw-r--r--src/quick/doc/snippets/qml/grid-spacing.qml2
-rw-r--r--src/quick/doc/snippets/qml/grid/grid.qml2
-rw-r--r--src/quick/doc/snippets/qml/gridview/ContactModel.qml2
-rw-r--r--src/quick/doc/snippets/qml/gridview/gridview.qml2
-rw-r--r--src/quick/doc/snippets/qml/image.qml2
-rw-r--r--src/quick/doc/snippets/qml/images/qt-logo.pngbin0 -> 6208 bytes
-rw-r--r--src/quick/doc/snippets/qml/images/qt_logo.svg26
-rw-r--r--src/quick/doc/snippets/qml/images/red.pngbin0 -> 130 bytes
-rw-r--r--src/quick/doc/snippets/qml/item/childrenRect.qml2
-rw-r--r--src/quick/doc/snippets/qml/item/containmentMask-circle-js.qml4
-rw-r--r--src/quick/doc/snippets/qml/item/containmentMask-shape.qml4
-rw-r--r--src/quick/doc/snippets/qml/item/itemGrab.qml7
-rw-r--r--src/quick/doc/snippets/qml/keynavigation.qml2
-rw-r--r--src/quick/doc/snippets/qml/keys/keys-handler.qml2
-rw-r--r--src/quick/doc/snippets/qml/keys/keys-pressed.qml2
-rw-r--r--src/quick/doc/snippets/qml/layerblending.qml2
-rw-r--r--src/quick/doc/snippets/qml/layout-simple.qml6
-rw-r--r--src/quick/doc/snippets/qml/layoutmirroring.qml2
-rw-r--r--src/quick/doc/snippets/qml/listview-decorations.qml5
-rw-r--r--src/quick/doc/snippets/qml/listview-sections.qml2
-rw-r--r--src/quick/doc/snippets/qml/listview.qml2
-rw-r--r--src/quick/doc/snippets/qml/listview/ContactModel.qml2
-rw-r--r--src/quick/doc/snippets/qml/listview/ReusableDelegate.qml2
-rw-r--r--src/quick/doc/snippets/qml/listview/listview.qml2
-rw-r--r--src/quick/doc/snippets/qml/loader/KeyReader.qml4
-rw-r--r--src/quick/doc/snippets/qml/loader/MyComponent.qml4
-rw-r--r--src/quick/doc/snippets/qml/loader/MyItem.qml4
-rw-r--r--src/quick/doc/snippets/qml/loader/connections.qml4
-rw-r--r--src/quick/doc/snippets/qml/loader/creationContext1.qml4
-rw-r--r--src/quick/doc/snippets/qml/loader/creationContext2.qml4
-rw-r--r--src/quick/doc/snippets/qml/loader/creationContext3.qml4
-rw-r--r--src/quick/doc/snippets/qml/loader/creationContext4.qml4
-rw-r--r--src/quick/doc/snippets/qml/loader/focus.qml4
-rw-r--r--src/quick/doc/snippets/qml/loader/simple.qml4
-rw-r--r--src/quick/doc/snippets/qml/loader/sizeitem.qml4
-rw-r--r--src/quick/doc/snippets/qml/loader/sizeloader.qml4
-rw-r--r--src/quick/doc/snippets/qml/localstorage/hello.qml2
-rw-r--r--src/quick/doc/snippets/qml/models/views-models-delegates.qml2
-rw-r--r--src/quick/doc/snippets/qml/mousearea/mousearea-snippet.qml2
-rw-r--r--src/quick/doc/snippets/qml/mousearea/mousearea.qml2
-rw-r--r--src/quick/doc/snippets/qml/mousearea/mouseareadragfilter.qml2
-rw-r--r--src/quick/doc/snippets/qml/multipointtoucharea/multipointtoucharea.qml2
-rw-r--r--src/quick/doc/snippets/qml/nestedWindowTransientParent.qml23
-rw-r--r--src/quick/doc/snippets/qml/numberanimation.qml2
-rw-r--r--src/quick/doc/snippets/qml/parallelanimation.qml2
-rw-r--r--src/quick/doc/snippets/qml/parentanimation.qml2
-rw-r--r--src/quick/doc/snippets/qml/parentchange.qml2
-rw-r--r--src/quick/doc/snippets/qml/path/arcdirection.qml2
-rw-r--r--src/quick/doc/snippets/qml/path/arcradius.qml2
-rw-r--r--src/quick/doc/snippets/qml/path/arcrotation.qml2
-rw-r--r--src/quick/doc/snippets/qml/path/basicarc.qml2
-rw-r--r--src/quick/doc/snippets/qml/path/basiccurve.qml2
-rw-r--r--src/quick/doc/snippets/qml/path/largearc.qml2
-rw-r--r--src/quick/doc/snippets/qml/pathinterpolator.qml2
-rw-r--r--src/quick/doc/snippets/qml/pathview/ContactModel.qml2
-rw-r--r--src/quick/doc/snippets/qml/pathview/pathattributes.qml2
-rw-r--r--src/quick/doc/snippets/qml/pathview/pathview.qml11
-rw-r--r--src/quick/doc/snippets/qml/propertyaction-sequential.qml2
-rw-r--r--src/quick/doc/snippets/qml/propertyaction.qml2
-rw-r--r--src/quick/doc/snippets/qml/propertyanimation.qml2
-rw-r--r--src/quick/doc/snippets/qml/propertychanges.qml2
-rw-r--r--src/quick/doc/snippets/qml/qml-data-models/dynamic-listmodel.qml10
-rw-r--r--src/quick/doc/snippets/qml/qml-data-models/listelements.qml10
-rw-r--r--src/quick/doc/snippets/qml/qml-data-models/listmodel-listview-required.qml2
-rw-r--r--src/quick/doc/snippets/qml/qml-data-models/listmodel-listview.qml2
-rw-r--r--src/quick/doc/snippets/qml/qml-extending-types/components/Button.qml2
-rw-r--r--src/quick/doc/snippets/qml/qml-extending-types/components/application.qml2
-rw-r--r--src/quick/doc/snippets/qml/qml-extending-types/methods/app.qml2
-rw-r--r--src/quick/doc/snippets/qml/qml-extending-types/properties/ImageViewer.qml2
-rw-r--r--src/quick/doc/snippets/qml/qml-extending-types/properties/alias-override.qml2
-rw-r--r--src/quick/doc/snippets/qml/qml-extending-types/properties/alias.qml2
-rw-r--r--src/quick/doc/snippets/qml/qml-extending-types/properties/alias/ImageViewer.qml2
-rw-r--r--src/quick/doc/snippets/qml/qml-extending-types/properties/alias/application.qml2
-rw-r--r--src/quick/doc/snippets/qml/qml-extending-types/properties/application.qml2
-rw-r--r--src/quick/doc/snippets/qml/qml-extending-types/properties/property-signals.qml2
-rw-r--r--src/quick/doc/snippets/qml/qml-extending-types/signals/Button.qml2
-rw-r--r--src/quick/doc/snippets/qml/qml-extending-types/signals/basic.qml2
-rw-r--r--src/quick/doc/snippets/qml/qml-extending-types/signals/connectdynamic.qml2
-rw-r--r--src/quick/doc/snippets/qml/qml-extending-types/signals/connectslots.qml2
-rw-r--r--src/quick/doc/snippets/qml/qml-extending-types/signals/no-parameters.qml2
-rw-r--r--src/quick/doc/snippets/qml/qml-extending-types/signals/parameters.qml2
-rw-r--r--src/quick/doc/snippets/qml/rectangle/rectangle-colors.qml2
-rw-r--r--src/quick/doc/snippets/qml/rectangle/rectangle-gradient.qml2
-rw-r--r--src/quick/doc/snippets/qml/rectangle/rectangle.qml2
-rw-r--r--src/quick/doc/snippets/qml/regularexpression.qml2
-rw-r--r--src/quick/doc/snippets/qml/repeaters/repeater-grid-index.qml2
-rw-r--r--src/quick/doc/snippets/qml/repeaters/repeater.qml12
-rw-r--r--src/quick/doc/snippets/qml/righttoleft.qml2
-rw-r--r--src/quick/doc/snippets/qml/righttoleft/Child.qml2
-rw-r--r--src/quick/doc/snippets/qml/rotation.qml2
-rw-r--r--src/quick/doc/snippets/qml/rotationanimation.qml2
-rw-r--r--src/quick/doc/snippets/qml/row.qml2
-rw-r--r--src/quick/doc/snippets/qml/row/row.qml2
-rw-r--r--src/quick/doc/snippets/qml/sequentialanimation.qml2
-rw-r--r--src/quick/doc/snippets/qml/smoothedanimation.qml2
-rw-r--r--src/quick/doc/snippets/qml/splashWindow.qml57
-rw-r--r--src/quick/doc/snippets/qml/springanimation.qml2
-rw-r--r--src/quick/doc/snippets/qml/state-when.qml2
-rw-r--r--src/quick/doc/snippets/qml/state.qml2
-rw-r--r--src/quick/doc/snippets/qml/states.qml2
-rw-r--r--src/quick/doc/snippets/qml/states/statechangescript.qml2
-rw-r--r--src/quick/doc/snippets/qml/systempalette.qml2
-rw-r--r--src/quick/doc/snippets/qml/tableview/cpp-tablemodel.h2
-rw-r--r--src/quick/doc/snippets/qml/tableview/cpp-tablemodel.qml2
-rw-r--r--src/quick/doc/snippets/qml/tableview/editdelegate.qml6
-rw-r--r--src/quick/doc/snippets/qml/tableview/keyboard-navigation.qml2
-rw-r--r--src/quick/doc/snippets/qml/tableview/qml-tablemodel.qml4
-rw-r--r--src/quick/doc/snippets/qml/tableview/reusabledelegate.qml2
-rw-r--r--src/quick/doc/snippets/qml/tableview/tableviewwithheader.qml2
-rw-r--r--src/quick/doc/snippets/qml/tableview/tableviewwithprovider.qml2
-rw-r--r--src/quick/doc/snippets/qml/text/onLinkActivated.qml2
-rw-r--r--src/quick/doc/snippets/qml/text/textEditFormats.qml2
-rw-r--r--src/quick/doc/snippets/qml/text/textFormats.qml2
-rw-r--r--src/quick/doc/snippets/qml/textEditStatusSwitch.qml31
-rw-r--r--src/quick/doc/snippets/qml/texteditor.qml2
-rw-r--r--src/quick/doc/snippets/qml/texthandling.qml2
-rw-r--r--src/quick/doc/snippets/qml/transition-animation.qml13
-rw-r--r--src/quick/doc/snippets/qml/transition-from-to-modified.qml2
-rw-r--r--src/quick/doc/snippets/qml/transition-from-to.qml2
-rw-r--r--src/quick/doc/snippets/qml/transition-reversible.qml2
-rw-r--r--src/quick/doc/snippets/qml/transition.qml2
-rw-r--r--src/quick/doc/snippets/qml/transitions-list.qml2
-rw-r--r--src/quick/doc/snippets/qml/treeview/qml-customdelegate.qml71
-rw-r--r--src/quick/doc/snippets/qml/viewtransitions/viewtransitions-basic.qml2
-rw-r--r--src/quick/doc/snippets/qml/viewtransitions/viewtransitions-delayedbyindex.qml2
-rw-r--r--src/quick/doc/snippets/qml/viewtransitions/viewtransitions-intermediatemove.qml2
-rw-r--r--src/quick/doc/snippets/qml/viewtransitions/viewtransitions-interruptedgood.qml2
-rw-r--r--src/quick/doc/snippets/qml/viewtransitions/viewtransitions-pathanim.qml2
-rw-r--r--src/quick/doc/snippets/qml/viewtransitions/viewtransitions-scriptactionbad.qml2
-rw-r--r--src/quick/doc/snippets/qml/viewtransitions/viewtransitions-scriptactiongood.qml2
-rw-r--r--src/quick/doc/snippets/qml/visualparent.qml2
-rw-r--r--src/quick/doc/snippets/qml/visualparent2.qml2
-rw-r--r--src/quick/doc/snippets/qml/windowActiveAttached.qml10
-rw-r--r--src/quick/doc/snippets/qml/windowPalette.qml40
-rw-r--r--src/quick/doc/snippets/qml/windowVisibility.qml29
-rw-r--r--src/quick/doc/snippets/qml/windowconstraints.qml8
-rw-r--r--src/quick/doc/snippets/qmllint/config.ini19
-rw-r--r--src/quick/doc/snippets/qquickrhiitem/qquickrhiitem_intro.cpp147
-rw-r--r--src/quick/doc/snippets/qquickrhiitem/qquickrhiitem_intro.frag10
-rw-r--r--src/quick/doc/snippets/qquickrhiitem/qquickrhiitem_intro.vert15
-rw-r--r--src/quick/doc/src/advtutorial.qdoc2
-rw-r--r--src/quick/doc/src/concepts/effects/particles.qdoc1
-rw-r--r--src/quick/doc/src/concepts/effects/topic.qdoc3
-rw-r--r--src/quick/doc/src/concepts/input/focus.qdoc4
-rw-r--r--src/quick/doc/src/concepts/inputhandlers/qtquickhandlers-index.qdoc5
-rw-r--r--src/quick/doc/src/concepts/layouts/qtquicklayouts-index.qdoc2
-rw-r--r--src/quick/doc/src/concepts/layouts/qtquicklayouts-overview.qdoc10
-rw-r--r--src/quick/doc/src/concepts/layouts/qtquicklayouts-responsive.qdoc101
-rw-r--r--src/quick/doc/src/concepts/modelviewsdata/cppmodels.qdoc40
-rw-r--r--src/quick/doc/src/concepts/modelviewsdata/modelview.qdoc162
-rw-r--r--src/quick/doc/src/concepts/positioning/righttoleft.qdoc10
-rw-r--r--src/quick/doc/src/concepts/positioning/topic.qdoc1
-rw-r--r--src/quick/doc/src/concepts/visualcanvas/adaptations.qdoc22
-rw-r--r--src/quick/doc/src/concepts/visualcanvas/scenegraph.qdoc164
-rw-r--r--src/quick/doc/src/dynamicview-tutorial.qdoc1
-rw-r--r--src/quick/doc/src/examples.qdoc68
-rw-r--r--src/quick/doc/src/guidelines/qtquick-bestpractices.qdoc35
-rw-r--r--src/quick/doc/src/guidelines/qtquick-tool-qmllint.qdoc140
-rw-r--r--src/quick/doc/src/guidelines/qtquick-toolsnutilities.qdoc22
-rw-r--r--src/quick/doc/src/includes/item.qdocinc4
-rw-r--r--src/quick/doc/src/includes/layout.qdocinc2
-rw-r--r--src/quick/doc/src/internal/deliverMatchingPointsToItem.puml14
-rw-r--r--src/quick/doc/src/internal/ideal-pointer-event-delivery-single-drag.dox242
-rw-r--r--src/quick/doc/src/internal/ideal-pointer-event-delivery.dox22
-rw-r--r--src/quick/doc/src/internal/ideal-pointer-event-delivery.puml55
-rw-r--r--src/quick/doc/src/internal/pinchAndDragHandlers-singleMoveDelivery.svg851
-rw-r--r--src/quick/doc/src/internal/pinchAndDragHandlers-singlePressDelivery.svg714
-rw-r--r--src/quick/doc/src/internal/pinchAndDragHandlers-singlePressPrep.svg648
-rw-r--r--src/quick/doc/src/internal/pinchAndDragHandlers-singleReleaseDelivery.svg825
-rw-r--r--src/quick/doc/src/internal/pinchAndDragHandlers.dot30
-rw-r--r--src/quick/doc/src/internal/pinchAndDragHandlers.svg137
-rw-r--r--src/quick/doc/src/internal/textEdit.dot52
-rw-r--r--src/quick/doc/src/qmltypereference.qdoc107
-rw-r--r--src/quick/doc/src/qtquick-android.qdoc14
-rw-r--r--src/quick/doc/src/qtquick-how-tos.qdoc109
-rw-r--r--src/quick/doc/src/qtquick.qdoc29
-rw-r--r--src/quick/handlers/qquickdragaxis_p.h2
-rw-r--r--src/quick/handlers/qquickdraghandler.cpp41
-rw-r--r--src/quick/handlers/qquickdraghandler_p.h4
-rw-r--r--src/quick/handlers/qquickhandlerpoint.cpp56
-rw-r--r--src/quick/handlers/qquickhandlerpoint_p.h30
-rw-r--r--src/quick/handlers/qquickhoverhandler.cpp17
-rw-r--r--src/quick/handlers/qquickhoverhandler_p.h2
-rw-r--r--src/quick/handlers/qquickmultipointhandler.cpp2
-rw-r--r--src/quick/handlers/qquickmultipointhandler_p.h2
-rw-r--r--src/quick/handlers/qquickmultipointhandler_p_p.h2
-rw-r--r--src/quick/handlers/qquickpinchhandler.cpp66
-rw-r--r--src/quick/handlers/qquickpinchhandler_p.h11
-rw-r--r--src/quick/handlers/qquickpointerdevicehandler.cpp11
-rw-r--r--src/quick/handlers/qquickpointerdevicehandler_p.h2
-rw-r--r--src/quick/handlers/qquickpointerdevicehandler_p_p.h2
-rw-r--r--src/quick/handlers/qquickpointerhandler.cpp238
-rw-r--r--src/quick/handlers/qquickpointerhandler_p.h4
-rw-r--r--src/quick/handlers/qquickpointerhandler_p_p.h3
-rw-r--r--src/quick/handlers/qquickpointhandler.cpp128
-rw-r--r--src/quick/handlers/qquickpointhandler_p.h2
-rw-r--r--src/quick/handlers/qquicksinglepointhandler.cpp38
-rw-r--r--src/quick/handlers/qquicksinglepointhandler_p.h2
-rw-r--r--src/quick/handlers/qquicksinglepointhandler_p_p.h2
-rw-r--r--src/quick/handlers/qquicktaphandler.cpp267
-rw-r--r--src/quick/handlers/qquicktaphandler_p.h18
-rw-r--r--src/quick/handlers/qquickwheelhandler.cpp8
-rw-r--r--src/quick/handlers/qquickwheelhandler_p.h2
-rw-r--r--src/quick/handlers/qquickwheelhandler_p_p.h2
-rw-r--r--src/quick/items/context2d/qquickcanvasitem.cpp39
-rw-r--r--src/quick/items/context2d/qquickcanvasitem_p.h15
-rw-r--r--src/quick/items/context2d/qquickcontext2d.cpp306
-rw-r--r--src/quick/items/context2d/qquickcontext2d_p.h4
-rw-r--r--src/quick/items/qquickaccessibleattached.cpp109
-rw-r--r--src/quick/items/qquickaccessibleattached_p.h46
-rw-r--r--src/quick/items/qquickanchors.cpp12
-rw-r--r--src/quick/items/qquickanchors_p.h4
-rw-r--r--src/quick/items/qquickanchors_p_p.h2
-rw-r--r--src/quick/items/qquickanimatedimage.cpp83
-rw-r--r--src/quick/items/qquickanimatedimage_p.h4
-rw-r--r--src/quick/items/qquickanimatedimage_p_p.h24
-rw-r--r--src/quick/items/qquickanimatedsprite_p.h2
-rw-r--r--src/quick/items/qquickborderimage.cpp126
-rw-r--r--src/quick/items/qquickborderimage_p.h3
-rw-r--r--src/quick/items/qquickborderimage_p_p.h17
-rw-r--r--src/quick/items/qquickclipnode_p.h2
-rw-r--r--src/quick/items/qquickcolorgroup.cpp29
-rw-r--r--src/quick/items/qquickcolorgroup_p.h12
-rw-r--r--src/quick/items/qquickdrag.cpp86
-rw-r--r--src/quick/items/qquickdrag_p.h57
-rw-r--r--src/quick/items/qquickdrag_p_p.h4
-rw-r--r--src/quick/items/qquickdroparea.cpp13
-rw-r--r--src/quick/items/qquickdroparea_p.h44
-rw-r--r--src/quick/items/qquickevents.cpp19
-rw-r--r--src/quick/items/qquickevents_p_p.h93
-rw-r--r--src/quick/items/qquickflickable.cpp340
-rw-r--r--src/quick/items/qquickflickable_p.h4
-rw-r--r--src/quick/items/qquickflickable_p_p.h82
-rw-r--r--src/quick/items/qquickflickablebehavior_p.h7
-rw-r--r--src/quick/items/qquickflipable.cpp90
-rw-r--r--src/quick/items/qquickflipable_p.h4
-rw-r--r--src/quick/items/qquickfocusscope_p.h4
-rw-r--r--src/quick/items/qquickframebufferobject.cpp4
-rw-r--r--src/quick/items/qquickgraphicsconfiguration.cpp104
-rw-r--r--src/quick/items/qquickgraphicsconfiguration.h3
-rw-r--r--src/quick/items/qquickgraphicsconfiguration_p.h7
-rw-r--r--src/quick/items/qquickgraphicsdevice.cpp21
-rw-r--r--src/quick/items/qquickgraphicsdevice.h4
-rw-r--r--src/quick/items/qquickgraphicsdevice_p.h4
-rw-r--r--src/quick/items/qquickgraphicsinfo.cpp62
-rw-r--r--src/quick/items/qquickgraphicsinfo_p.h1
-rw-r--r--src/quick/items/qquickgridview.cpp175
-rw-r--r--src/quick/items/qquickgridview_p.h4
-rw-r--r--src/quick/items/qquickimage.cpp153
-rw-r--r--src/quick/items/qquickimage_p.h5
-rw-r--r--src/quick/items/qquickimage_p_p.h4
-rw-r--r--src/quick/items/qquickimagebase.cpp164
-rw-r--r--src/quick/items/qquickimagebase_p.h8
-rw-r--r--src/quick/items/qquickimagebase_p_p.h37
-rw-r--r--src/quick/items/qquickimplicitsizeitem_p.h2
-rw-r--r--src/quick/items/qquickimplicitsizeitem_p_p.h2
-rw-r--r--src/quick/items/qquickitem.cpp710
-rw-r--r--src/quick/items/qquickitem.h26
-rw-r--r--src/quick/items/qquickitem_p.h127
-rw-r--r--src/quick/items/qquickitemanimation.cpp13
-rw-r--r--src/quick/items/qquickitemanimation_p.h12
-rw-r--r--src/quick/items/qquickitemchangelistener_p.h6
-rw-r--r--src/quick/items/qquickitemgrabresult.cpp16
-rw-r--r--src/quick/items/qquickitemsmodule.cpp160
-rw-r--r--src/quick/items/qquickitemview.cpp93
-rw-r--r--src/quick/items/qquickitemview_p.h22
-rw-r--r--src/quick/items/qquickitemview_p_p.h31
-rw-r--r--src/quick/items/qquickitemviewfxitem.cpp35
-rw-r--r--src/quick/items/qquickitemviewfxitem_p_p.h10
-rw-r--r--src/quick/items/qquickitemviewtransition_p.h20
-rw-r--r--src/quick/items/qquicklistview.cpp299
-rw-r--r--src/quick/items/qquicklistview_p.h11
-rw-r--r--src/quick/items/qquickloader.cpp50
-rw-r--r--src/quick/items/qquickloader_p.h5
-rw-r--r--src/quick/items/qquickloader_p_p.h1
-rw-r--r--src/quick/items/qquickmousearea.cpp30
-rw-r--r--src/quick/items/qquickmousearea_p.h4
-rw-r--r--src/quick/items/qquickmultipointtoucharea.cpp57
-rw-r--r--src/quick/items/qquickmultipointtoucharea_p.h28
-rw-r--r--src/quick/items/qquickpainteditem.cpp21
-rw-r--r--src/quick/items/qquickpainteditem_p.h3
-rw-r--r--src/quick/items/qquickpalette.cpp55
-rw-r--r--src/quick/items/qquickpalette_p.h17
-rw-r--r--src/quick/items/qquickpalettecolorprovider.cpp53
-rw-r--r--src/quick/items/qquickpalettecolorprovider_p.h3
-rw-r--r--src/quick/items/qquickpaletteproviderprivatebase_p.h32
-rw-r--r--src/quick/items/qquickpathview.cpp130
-rw-r--r--src/quick/items/qquickpathview_p.h10
-rw-r--r--src/quick/items/qquickpathview_p_p.h10
-rw-r--r--src/quick/items/qquickpincharea.cpp2
-rw-r--r--src/quick/items/qquickpincharea_p.h39
-rw-r--r--src/quick/items/qquickpositioners.cpp66
-rw-r--r--src/quick/items/qquickpositioners_p.h34
-rw-r--r--src/quick/items/qquickpositioners_p_p.h7
-rw-r--r--src/quick/items/qquickrectangle.cpp280
-rw-r--r--src/quick/items/qquickrectangle_p.h40
-rw-r--r--src/quick/items/qquickrectangle_p_p.h20
-rw-r--r--src/quick/items/qquickrendercontrol.cpp118
-rw-r--r--src/quick/items/qquickrendercontrol.h5
-rw-r--r--src/quick/items/qquickrendercontrol_p.h4
-rw-r--r--src/quick/items/qquickrendertarget.cpp1165
-rw-r--r--src/quick/items/qquickrendertarget.h31
-rw-r--r--src/quick/items/qquickrendertarget_p.h25
-rw-r--r--src/quick/items/qquickrepeater_p.h4
-rw-r--r--src/quick/items/qquickrhiitem.cpp1158
-rw-r--r--src/quick/items/qquickrhiitem.h124
-rw-r--r--src/quick/items/qquickrhiitem_p.h101
-rw-r--r--src/quick/items/qquickscalegrid_p_p.h14
-rw-r--r--src/quick/items/qquickscreen.cpp2
-rw-r--r--src/quick/items/qquickscreen_p.h42
-rw-r--r--src/quick/items/qquickselectable_p.h13
-rw-r--r--src/quick/items/qquickshadereffect.cpp48
-rw-r--r--src/quick/items/qquickshadereffect_p.h2
-rw-r--r--src/quick/items/qquickshadereffect_p_p.h1
-rw-r--r--src/quick/items/qquickshadereffectmesh_p.h8
-rw-r--r--src/quick/items/qquickshadereffectsource.cpp32
-rw-r--r--src/quick/items/qquickshadereffectsource_p.h2
-rw-r--r--src/quick/items/qquicksprite_p.h2
-rw-r--r--src/quick/items/qquickspriteengine_p.h14
-rw-r--r--src/quick/items/qquickspritesequence_p.h2
-rw-r--r--src/quick/items/qquickstateoperations.cpp54
-rw-r--r--src/quick/items/qquickstateoperations_p.h24
-rw-r--r--src/quick/items/qquicktableview.cpp1360
-rw-r--r--src/quick/items/qquicktableview_p.h45
-rw-r--r--src/quick/items/qquicktableview_p_p.h148
-rw-r--r--src/quick/items/qquicktext.cpp642
-rw-r--r--src/quick/items/qquicktext_p.h26
-rw-r--r--src/quick/items/qquicktext_p_p.h15
-rw-r--r--src/quick/items/qquicktextcontrol.cpp55
-rw-r--r--src/quick/items/qquicktextcontrol_p.h1
-rw-r--r--src/quick/items/qquicktextcontrol_p_p.h1
-rw-r--r--src/quick/items/qquicktextdocument.cpp660
-rw-r--r--src/quick/items/qquicktextdocument.h43
-rw-r--r--src/quick/items/qquicktextdocument_p.h94
-rw-r--r--src/quick/items/qquicktextedit.cpp837
-rw-r--r--src/quick/items/qquicktextedit_p.h30
-rw-r--r--src/quick/items/qquicktextedit_p_p.h130
-rw-r--r--src/quick/items/qquicktextinput.cpp329
-rw-r--r--src/quick/items/qquicktextinput_p.h10
-rw-r--r--src/quick/items/qquicktextinput_p_p.h6
-rw-r--r--src/quick/items/qquicktextinterface_p.h2
-rw-r--r--src/quick/items/qquicktextnode_p.h95
-rw-r--r--src/quick/items/qquicktextnodeengine.cpp44
-rw-r--r--src/quick/items/qquicktextnodeengine_p.h7
-rw-r--r--src/quick/items/qquicktextutil_p.h2
-rw-r--r--src/quick/items/qquicktranslate_p.h10
-rw-r--r--src/quick/items/qquicktreeview.cpp115
-rw-r--r--src/quick/items/qquicktreeview_p.h16
-rw-r--r--src/quick/items/qquicktreeview_p_p.h2
-rw-r--r--src/quick/items/qquickview.cpp71
-rw-r--r--src/quick/items/qquickview.h2
-rw-r--r--src/quick/items/qquickview_p.h5
-rw-r--r--src/quick/items/qquickwindow.cpp762
-rw-r--r--src/quick/items/qquickwindow.h14
-rw-r--r--src/quick/items/qquickwindow_p.h63
-rw-r--r--src/quick/items/qquickwindowattached_p.h16
-rw-r--r--src/quick/items/qquickwindowcontainer.cpp590
-rw-r--r--src/quick/items/qquickwindowcontainer_p.h79
-rw-r--r--src/quick/items/qquickwindowmodule.cpp524
-rw-r--r--src/quick/items/qquickwindowmodule_p.h26
-rw-r--r--src/quick/items/qquickwindowmodule_p_p.h15
-rw-r--r--src/quick/items/qsginternaltextnode.cpp (renamed from src/quick/items/qquicktextnode.cpp)157
-rw-r--r--src/quick/items/qsginternaltextnode_p.h199
-rw-r--r--src/quick/jar/CMakeLists.txt22
-rw-r--r--src/quick/jar/build.gradle51
-rw-r--r--src/quick/jar/org/qtproject/qt/android/QtAbstractItemModel.java105
-rw-r--r--src/quick/jar/org/qtproject/qt/android/QtAbstractItemModelProxy.java35
-rw-r--r--src/quick/jar/org/qtproject/qt/android/QtAbstractListModel.java30
-rw-r--r--src/quick/jar/org/qtproject/qt/android/QtModelIndex.java42
-rw-r--r--src/quick/jar/org/qtproject/qt/android/QtQmlComponent.java206
-rw-r--r--src/quick/jar/org/qtproject/qt/android/QtQmlStatus.java49
-rw-r--r--src/quick/jar/org/qtproject/qt/android/QtQmlStatusChangeListener.java17
-rw-r--r--src/quick/jar/org/qtproject/qt/android/QtQuickView.java304
-rw-r--r--src/quick/jar/org/qtproject/qt/android/QtQuickView.qdoc277
-rw-r--r--src/quick/jar/org/qtproject/qt/android/QtSignalListener.java17
-rw-r--r--src/quick/jar/settings.gradle1
-rw-r--r--src/quick/platform/android/qandroiditemmodelproxy.cpp379
-rw-r--r--src/quick/platform/android/qandroiditemmodelproxy_p.h191
-rw-r--r--src/quick/platform/android/qandroidmodelindexproxy.cpp103
-rw-r--r--src/quick/platform/android/qandroidmodelindexproxy_p.h56
-rw-r--r--src/quick/platform/android/qandroidquickviewembedding.cpp332
-rw-r--r--src/quick/platform/android/qandroidquickviewembedding_p.h62
-rw-r--r--src/quick/platform/android/qandroidtypeconverter_p.h99
-rw-r--r--src/quick/platform/android/qandroidtypes_p.h43
-rw-r--r--src/quick/platform/android/qandroidviewsignalmanager.cpp77
-rw-r--r--src/quick/platform/android/qandroidviewsignalmanager_p.h57
-rw-r--r--src/quick/qtquick.tracepoints48
-rw-r--r--src/quick/qtquickglobal_p.h7
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer.cpp8
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer_p.h2
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp17
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgsoftwarecontext_p.h2
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgsoftwareglyphnode.cpp4
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgsoftwareinternalrectanglenode.cpp148
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgsoftwareinternalrectanglenode_p.h13
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgsoftwarepainternode.cpp3
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgsoftwarerenderablenode.cpp4
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgsoftwarerenderablenode_p.h2
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgsoftwarerenderer_p.h2
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgsoftwarethreadedrenderloop.cpp57
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgsoftwarethreadedrenderloop_p.h4
-rw-r--r--src/quick/scenegraph/compressedtexture/qsgcompressedtexture.cpp2
-rw-r--r--src/quick/scenegraph/compressedtexture/qsgcompressedtexture_p.h6
-rw-r--r--src/quick/scenegraph/coreapi/qsgabstractrenderer.cpp148
-rw-r--r--src/quick/scenegraph/coreapi/qsgabstractrenderer_p.h28
-rw-r--r--src/quick/scenegraph/coreapi/qsgabstractrenderer_p_p.h7
-rw-r--r--src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp236
-rw-r--r--src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h34
-rw-r--r--src/quick/scenegraph/coreapi/qsggeometry.cpp25
-rw-r--r--src/quick/scenegraph/coreapi/qsggeometry.h4
-rw-r--r--src/quick/scenegraph/coreapi/qsgmaterial.cpp198
-rw-r--r--src/quick/scenegraph/coreapi/qsgmaterial.h6
-rw-r--r--src/quick/scenegraph/coreapi/qsgmaterialshader.cpp85
-rw-r--r--src/quick/scenegraph/coreapi/qsgmaterialshader.h17
-rw-r--r--src/quick/scenegraph/coreapi/qsgmaterialshader_p.h6
-rw-r--r--src/quick/scenegraph/coreapi/qsgnode.cpp9
-rw-r--r--src/quick/scenegraph/coreapi/qsgnode.h4
-rw-r--r--src/quick/scenegraph/coreapi/qsgnodeupdater_p.h2
-rw-r--r--src/quick/scenegraph/coreapi/qsgrenderer.cpp11
-rw-r--r--src/quick/scenegraph/coreapi/qsgrenderer_p.h22
-rw-r--r--src/quick/scenegraph/coreapi/qsgrendererinterface.cpp89
-rw-r--r--src/quick/scenegraph/coreapi/qsgrendererinterface.h5
-rw-r--r--src/quick/scenegraph/coreapi/qsgrendernode.cpp214
-rw-r--r--src/quick/scenegraph/coreapi/qsgrendernode.h5
-rw-r--r--src/quick/scenegraph/coreapi/qsgrendernode_p.h5
-rw-r--r--src/quick/scenegraph/coreapi/qsgrhivisualizer.cpp8
-rw-r--r--src/quick/scenegraph/coreapi/qsgtexture.cpp104
-rw-r--r--src/quick/scenegraph/coreapi/qsgtexture_mac.mm2
-rw-r--r--src/quick/scenegraph/coreapi/qsgtexture_p.h28
-rw-r--r--src/quick/scenegraph/coreapi/qsgtexture_platform.h27
-rw-r--r--src/quick/scenegraph/qsgadaptationlayer.cpp24
-rw-r--r--src/quick/scenegraph/qsgadaptationlayer_p.h40
-rw-r--r--src/quick/scenegraph/qsgbasicglyphnode_p.h2
-rw-r--r--src/quick/scenegraph/qsgbasicinternalimagenode.cpp18
-rw-r--r--src/quick/scenegraph/qsgbasicinternalimagenode_p.h2
-rw-r--r--src/quick/scenegraph/qsgbasicinternalrectanglenode.cpp580
-rw-r--r--src/quick/scenegraph/qsgbasicinternalrectanglenode_p.h14
-rw-r--r--src/quick/scenegraph/qsgcontext.cpp173
-rw-r--r--src/quick/scenegraph/qsgcontext_p.h56
-rw-r--r--src/quick/scenegraph/qsgcontextplugin.cpp4
-rw-r--r--src/quick/scenegraph/qsgcontextplugin_p.h4
-rw-r--r--src/quick/scenegraph/qsgcurveabstractnode_p.h32
-rw-r--r--src/quick/scenegraph/qsgcurvefillnode.cpp61
-rw-r--r--src/quick/scenegraph/qsgcurvefillnode_p.cpp396
-rw-r--r--src/quick/scenegraph/qsgcurvefillnode_p.h273
-rw-r--r--src/quick/scenegraph/qsgcurvefillnode_p_p.h57
-rw-r--r--src/quick/scenegraph/qsgcurveglyphatlas.cpp142
-rw-r--r--src/quick/scenegraph/qsgcurveglyphatlas_p.h69
-rw-r--r--src/quick/scenegraph/qsgcurveglyphnode.cpp164
-rw-r--r--src/quick/scenegraph/qsgcurveglyphnode_p.h68
-rw-r--r--src/quick/scenegraph/qsgcurveprocessor.cpp1887
-rw-r--r--src/quick/scenegraph/qsgcurveprocessor_p.h57
-rw-r--r--src/quick/scenegraph/qsgcurvestrokenode.cpp112
-rw-r--r--src/quick/scenegraph/qsgcurvestrokenode_p.cpp90
-rw-r--r--src/quick/scenegraph/qsgcurvestrokenode_p.h116
-rw-r--r--src/quick/scenegraph/qsgcurvestrokenode_p_p.h75
-rw-r--r--src/quick/scenegraph/qsgdefaultcontext.cpp19
-rw-r--r--src/quick/scenegraph/qsgdefaultcontext_p.h5
-rw-r--r--src/quick/scenegraph/qsgdefaultglyphnode.cpp7
-rw-r--r--src/quick/scenegraph/qsgdefaultglyphnode_p.cpp187
-rw-r--r--src/quick/scenegraph/qsgdefaultglyphnode_p_p.h1
-rw-r--r--src/quick/scenegraph/qsgdefaultinternalimagenode.cpp17
-rw-r--r--src/quick/scenegraph/qsgdefaultinternalimagenode_p.h4
-rw-r--r--src/quick/scenegraph/qsgdefaultinternalrectanglenode.cpp30
-rw-r--r--src/quick/scenegraph/qsgdefaultinternalrectanglenode_p.h4
-rw-r--r--src/quick/scenegraph/qsgdefaultrendercontext.cpp98
-rw-r--r--src/quick/scenegraph/qsgdefaultrendercontext_p.h13
-rw-r--r--src/quick/scenegraph/qsgdefaultspritenode.cpp26
-rw-r--r--src/quick/scenegraph/qsgdistancefieldglyphnode.cpp10
-rw-r--r--src/quick/scenegraph/qsgdistancefieldglyphnode_p.cpp189
-rw-r--r--src/quick/scenegraph/qsgdistancefieldglyphnode_p.h2
-rw-r--r--src/quick/scenegraph/qsgdistancefieldglyphnode_p_p.h12
-rw-r--r--src/quick/scenegraph/qsgrenderloop.cpp71
-rw-r--r--src/quick/scenegraph/qsgrenderloop_p.h30
-rw-r--r--src/quick/scenegraph/qsgrhidistancefieldglyphcache.cpp23
-rw-r--r--src/quick/scenegraph/qsgrhidistancefieldglyphcache_p.h4
-rw-r--r--src/quick/scenegraph/qsgrhiinternaltextnode.cpp41
-rw-r--r--src/quick/scenegraph/qsgrhiinternaltextnode_p.h31
-rw-r--r--src/quick/scenegraph/qsgrhilayer.cpp9
-rw-r--r--src/quick/scenegraph/qsgrhilayer_p.h4
-rw-r--r--src/quick/scenegraph/qsgrhishadereffectnode.cpp139
-rw-r--r--src/quick/scenegraph/qsgrhishadereffectnode_p.h1
-rw-r--r--src/quick/scenegraph/qsgrhisupport.cpp209
-rw-r--r--src/quick/scenegraph/qsgrhisupport_p.h37
-rw-r--r--src/quick/scenegraph/qsgrhitextureglyphcache.cpp72
-rw-r--r--src/quick/scenegraph/qsgrhitextureglyphcache_p.h3
-rw-r--r--src/quick/scenegraph/qsgthreadedrenderloop.cpp192
-rw-r--r--src/quick/scenegraph/qsgthreadedrenderloop_p.h2
-rw-r--r--src/quick/scenegraph/shaders_ng/24bittextmask.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/32bitcolortext.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/8bittextmask.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/8bittextmask_a.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldoutlinetext.frag15
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldoutlinetext.vert16
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_a.frag15
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_a_fwidth.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_fwidth.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldshiftedtext.frag15
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldshiftedtext.vert18
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_a.frag15
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_a_fwidth.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_fwidth.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldtext.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldtext.vert16
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldtext_a.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldtext_a_fwidth.frag11
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldtext_fwidth.frag11
-rw-r--r--src/quick/scenegraph/shaders_ng/flatcolor.frag11
-rw-r--r--src/quick/scenegraph/shaders_ng/flatcolor.vert14
-rw-r--r--src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.frag15
-rw-r--r--src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.vert20
-rw-r--r--src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext_a.frag15
-rw-r--r--src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.vert20
-rw-r--r--src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext_a.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/opaquetexture.frag3
-rw-r--r--src/quick/scenegraph/shaders_ng/opaquetexture.vert14
-rw-r--r--src/quick/scenegraph/shaders_ng/outlinedtext.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/outlinedtext.vert28
-rw-r--r--src/quick/scenegraph/shaders_ng/outlinedtext_a.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/shadereffect.frag7
-rw-r--r--src/quick/scenegraph/shaders_ng/shadereffect.vert10
-rw-r--r--src/quick/scenegraph/shaders_ng/shapecurve.frag152
-rw-r--r--src/quick/scenegraph/shaders_ng/shapecurve.vert92
-rw-r--r--src/quick/scenegraph/shaders_ng/shapestroke.frag134
-rw-r--r--src/quick/scenegraph/shaders_ng/shapestroke.vert82
-rw-r--r--src/quick/scenegraph/shaders_ng/smoothcolor.frag3
-rw-r--r--src/quick/scenegraph/shaders_ng/smoothcolor.vert28
-rw-r--r--src/quick/scenegraph/shaders_ng/smoothtexture.frag3
-rw-r--r--src/quick/scenegraph/shaders_ng/smoothtexture.vert28
-rw-r--r--src/quick/scenegraph/shaders_ng/sprite.frag11
-rw-r--r--src/quick/scenegraph/shaders_ng/sprite.vert20
-rw-r--r--src/quick/scenegraph/shaders_ng/stencilclip.frag3
-rw-r--r--src/quick/scenegraph/shaders_ng/styledtext.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/styledtext.vert22
-rw-r--r--src/quick/scenegraph/shaders_ng/styledtext_a.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/textmask.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/textmask.vert20
-rw-r--r--src/quick/scenegraph/shaders_ng/texture.frag11
-rw-r--r--src/quick/scenegraph/shaders_ng/texture.vert14
-rw-r--r--src/quick/scenegraph/shaders_ng/vertexcolor.frag3
-rw-r--r--src/quick/scenegraph/shaders_ng/vertexcolor.vert16
-rw-r--r--src/quick/scenegraph/shaders_ng/visualization.frag3
-rw-r--r--src/quick/scenegraph/util/qquadpath.cpp951
-rw-r--r--src/quick/scenegraph/util/qquadpath_p.h341
-rw-r--r--src/quick/scenegraph/util/qsgareaallocator.cpp13
-rw-r--r--src/quick/scenegraph/util/qsgareaallocator_p.h2
-rw-r--r--src/quick/scenegraph/util/qsgdefaultimagenode_p.h2
-rw-r--r--src/quick/scenegraph/util/qsgdefaultninepatchnode_p.h2
-rw-r--r--src/quick/scenegraph/util/qsgdefaultpainternode.cpp5
-rw-r--r--src/quick/scenegraph/util/qsgdefaultpainternode_p.h4
-rw-r--r--src/quick/scenegraph/util/qsgflatcolormaterial.cpp26
-rw-r--r--src/quick/scenegraph/util/qsggradientcache.cpp121
-rw-r--r--src/quick/scenegraph/util/qsggradientcache_p.h72
-rw-r--r--src/quick/scenegraph/util/qsgplaintexture.cpp7
-rw-r--r--src/quick/scenegraph/util/qsgplaintexture_p.h4
-rw-r--r--src/quick/scenegraph/util/qsgrhiatlastexture_p.h2
-rw-r--r--src/quick/scenegraph/util/qsgsimpletexturenode.cpp2
-rw-r--r--src/quick/scenegraph/util/qsgtextnode.cpp320
-rw-r--r--src/quick/scenegraph/util/qsgtextnode.h101
-rw-r--r--src/quick/scenegraph/util/qsgtexturematerial.cpp35
-rw-r--r--src/quick/scenegraph/util/qsgtexturematerial_p.h6
-rw-r--r--src/quick/scenegraph/util/qsgtransform.cpp10
-rw-r--r--src/quick/scenegraph/util/qsgtransform_p.h105
-rw-r--r--src/quick/scenegraph/util/qsgvertexcolormaterial.cpp32
-rw-r--r--src/quick/util/qminimalflatset_p.h143
-rw-r--r--src/quick/util/qquickanimation.cpp143
-rw-r--r--src/quick/util/qquickanimation_p.h36
-rw-r--r--src/quick/util/qquickanimation_p_p.h15
-rw-r--r--src/quick/util/qquickanimationcontroller_p.h4
-rw-r--r--src/quick/util/qquickanimator.cpp21
-rw-r--r--src/quick/util/qquickanimator_p.h23
-rw-r--r--src/quick/util/qquickanimator_p_p.h2
-rw-r--r--src/quick/util/qquickanimatorjob.cpp4
-rw-r--r--src/quick/util/qquickanimatorjob_p.h19
-rw-r--r--src/quick/util/qquickapplication.cpp1
-rw-r--r--src/quick/util/qquickapplication_p.h20
-rw-r--r--src/quick/util/qquickbehavior.cpp7
-rw-r--r--src/quick/util/qquickbehavior_p.h4
-rw-r--r--src/quick/util/qquickdeliveryagent.cpp430
-rw-r--r--src/quick/util/qquickdeliveryagent_p.h4
-rw-r--r--src/quick/util/qquickdeliveryagent_p_p.h14
-rw-r--r--src/quick/util/qquickfontloader.cpp30
-rw-r--r--src/quick/util/qquickfontloader_p.h4
-rw-r--r--src/quick/util/qquickfontmetrics_p.h4
-rw-r--r--src/quick/util/qquickforeignutils.cpp31
-rw-r--r--src/quick/util/qquickforeignutils_p.h16
-rw-r--r--src/quick/util/qquickframeanimation_p.h4
-rw-r--r--src/quick/util/qquickglobal.cpp1
-rw-r--r--src/quick/util/qquickimageprovider.cpp16
-rw-r--r--src/quick/util/qquickinputmethod_p.h18
-rw-r--r--src/quick/util/qquickpath.cpp408
-rw-r--r--src/quick/util/qquickpath_p.h131
-rw-r--r--src/quick/util/qquickpath_p_p.h3
-rw-r--r--src/quick/util/qquickpathinterpolator_p.h4
-rw-r--r--src/quick/util/qquickpixmap_p.h205
-rw-r--r--src/quick/util/qquickpixmapcache.cpp739
-rw-r--r--src/quick/util/qquickpixmapcache_p.h188
-rw-r--r--src/quick/util/qquickprofiler_p.h22
-rw-r--r--src/quick/util/qquickpropertychanges.cpp6
-rw-r--r--src/quick/util/qquickpropertychanges_p.h4
-rw-r--r--src/quick/util/qquickshortcut.cpp24
-rw-r--r--src/quick/util/qquickshortcut_p.h2
-rw-r--r--src/quick/util/qquicksmoothedanimation.cpp19
-rw-r--r--src/quick/util/qquicksmoothedanimation_p.h4
-rw-r--r--src/quick/util/qquicksmoothedanimation_p_p.h2
-rw-r--r--src/quick/util/qquickspringanimation.cpp6
-rw-r--r--src/quick/util/qquickspringanimation_p.h4
-rw-r--r--src/quick/util/qquickstate.cpp42
-rw-r--r--src/quick/util/qquickstate_p.h11
-rw-r--r--src/quick/util/qquickstatechangescript_p.h4
-rw-r--r--src/quick/util/qquickstategroup.cpp144
-rw-r--r--src/quick/util/qquickstategroup_p.h4
-rw-r--r--src/quick/util/qquickstyledtext.cpp22
-rw-r--r--src/quick/util/qquickstyledtext_p.h10
-rw-r--r--src/quick/util/qquicksvgparser_p.h2
-rw-r--r--src/quick/util/qquicksystempalette.cpp26
-rw-r--r--src/quick/util/qquicksystempalette_p.h6
-rw-r--r--src/quick/util/qquicktextmetrics.cpp19
-rw-r--r--src/quick/util/qquicktextmetrics_p.h4
-rw-r--r--src/quick/util/qquicktextselection.cpp187
-rw-r--r--src/quick/util/qquicktextselection_p.h80
-rw-r--r--src/quick/util/qquicktimeline_p_p.h22
-rw-r--r--src/quick/util/qquicktransition.cpp9
-rw-r--r--src/quick/util/qquicktransition_p.h4
-rw-r--r--src/quick/util/qquicktransitionmanager.cpp6
-rw-r--r--src/quick/util/qquicktransitionmanager_p_p.h2
-rw-r--r--src/quick/util/qquickvalidator.cpp8
-rw-r--r--src/quick/util/qquickvalidator_p.h13
-rw-r--r--src/quick/util/qquickvaluetypes.cpp326
-rw-r--r--src/quick/util/qquickvaluetypes_p.h92
716 files changed, 33725 insertions, 7123 deletions
diff --git a/src/quick/CMakeLists.txt b/src/quick/CMakeLists.txt
index 5819b5f1c8..6f294d30d1 100644
--- a/src/quick/CMakeLists.txt
+++ b/src/quick/CMakeLists.txt
@@ -75,6 +75,7 @@ qt_internal_add_qml_module(Quick
items/qquickrectangle_p_p.h
items/qquickrendercontrol.cpp items/qquickrendercontrol.h items/qquickrendercontrol_p.h
items/qquickrendertarget.cpp items/qquickrendertarget.h items/qquickrendertarget_p.h
+ items/qquickrhiitem.cpp items/qquickrhiitem.h items/qquickrhiitem_p.h
items/qquickscalegrid.cpp
items/qquickscalegrid_p_p.h
items/qquickscreen.cpp items/qquickscreen_p.h
@@ -89,13 +90,14 @@ qt_internal_add_qml_module(Quick
items/qquicktextedit_p_p.h
items/qquicktextinput.cpp items/qquicktextinput_p.h
items/qquicktextinput_p_p.h
- items/qquicktextnode.cpp items/qquicktextnode_p.h
+ items/qsginternaltextnode.cpp items/qsginternaltextnode_p.h
items/qquicktextnodeengine.cpp items/qquicktextnodeengine_p.h
items/qquicktextutil.cpp items/qquicktextutil_p.h
items/qquicktranslate.cpp items/qquicktranslate_p.h
items/qquickview.cpp items/qquickview.h items/qquickview_p.h
items/qquickwindow.cpp items/qquickwindow.h items/qquickwindow_p.h
items/qquickwindowattached.cpp items/qquickwindowattached_p.h
+ items/qquickwindowcontainer.cpp items/qquickwindowcontainer_p.h
items/qquickwindowmodule.cpp items/qquickwindowmodule_p.h
items/qquickwindowmodule_p_p.h
qtquickglobal.h qtquickglobal_p.h
@@ -133,13 +135,18 @@ qt_internal_add_qml_module(Quick
scenegraph/coreapi/qsgtexture.cpp scenegraph/coreapi/qsgtexture.h scenegraph/coreapi/qsgtexture_p.h
scenegraph/coreapi/qsgtexture_platform.h
scenegraph/qsgadaptationlayer.cpp scenegraph/qsgadaptationlayer_p.h
+ scenegraph/qsgcurveabstractnode_p.h
scenegraph/qsgbasicglyphnode.cpp scenegraph/qsgbasicglyphnode_p.h
scenegraph/qsgbasicinternalimagenode.cpp scenegraph/qsgbasicinternalimagenode_p.h
scenegraph/qsgbasicinternalrectanglenode.cpp scenegraph/qsgbasicinternalrectanglenode_p.h
scenegraph/qsgcontext.cpp scenegraph/qsgcontext_p.h
scenegraph/qsgcontextplugin.cpp scenegraph/qsgcontextplugin_p.h
+ scenegraph/qsgcurvefillnode.cpp scenegraph/qsgcurvefillnode_p.cpp scenegraph/qsgcurvefillnode_p.h scenegraph/qsgcurvefillnode_p_p.h
+ scenegraph/qsgcurvestrokenode.cpp scenegraph/qsgcurvestrokenode_p.cpp scenegraph/qsgcurvestrokenode_p.h scenegraph/qsgcurvestrokenode_p_p.h
scenegraph/qsgdefaultcontext.cpp scenegraph/qsgdefaultcontext_p.h
scenegraph/qsgdefaultglyphnode.cpp scenegraph/qsgdefaultglyphnode_p.cpp scenegraph/qsgdefaultglyphnode_p.h
+ scenegraph/qsgcurveglyphatlas.cpp scenegraph/qsgcurveglyphatlas_p.h
+ scenegraph/qsgcurveglyphnode.cpp scenegraph/qsgcurveglyphnode_p.h
scenegraph/qsgdefaultglyphnode_p_p.h
scenegraph/qsgdefaultinternalimagenode.cpp scenegraph/qsgdefaultinternalimagenode_p.h
scenegraph/qsgdefaultinternalrectanglenode.cpp scenegraph/qsgdefaultinternalrectanglenode_p.h
@@ -148,10 +155,12 @@ qt_internal_add_qml_module(Quick
scenegraph/qsgdistancefieldglyphnode_p_p.h
scenegraph/qsgrenderloop.cpp scenegraph/qsgrenderloop_p.h
scenegraph/qsgrhidistancefieldglyphcache.cpp scenegraph/qsgrhidistancefieldglyphcache_p.h
+ scenegraph/qsgrhiinternaltextnode.cpp scenegraph/qsgrhiinternaltextnode_p.h
scenegraph/qsgrhilayer.cpp scenegraph/qsgrhilayer_p.h
scenegraph/qsgrhishadereffectnode.cpp scenegraph/qsgrhishadereffectnode_p.h
scenegraph/qsgrhisupport.cpp scenegraph/qsgrhisupport_p.h
scenegraph/qsgrhitextureglyphcache.cpp scenegraph/qsgrhitextureglyphcache_p.h
+ scenegraph/qsgcurveprocessor.cpp scenegraph/qsgcurveprocessor_p.h
scenegraph/util/qsgareaallocator.cpp scenegraph/util/qsgareaallocator_p.h
scenegraph/util/qsgdefaultimagenode.cpp scenegraph/util/qsgdefaultimagenode_p.h
scenegraph/util/qsgdefaultninepatchnode.cpp scenegraph/util/qsgdefaultninepatchnode_p.h
@@ -165,11 +174,14 @@ qt_internal_add_qml_module(Quick
scenegraph/util/qsgrhiatlastexture.cpp scenegraph/util/qsgrhiatlastexture_p.h
scenegraph/util/qsgsimplerectnode.cpp scenegraph/util/qsgsimplerectnode.h
scenegraph/util/qsgsimpletexturenode.cpp scenegraph/util/qsgsimpletexturenode.h
+ scenegraph/util/qsgtextnode.cpp scenegraph/util/qsgtexturematerial.h scenegraph/util/qsgtextnode.h
scenegraph/util/qsgtexturematerial.cpp scenegraph/util/qsgtexturematerial.h scenegraph/util/qsgtexturematerial_p.h
scenegraph/util/qsgtextureprovider.cpp scenegraph/util/qsgtextureprovider.h
scenegraph/util/qsgtexturereader.cpp scenegraph/util/qsgtexturereader_p.h
scenegraph/util/qsgvertexcolormaterial.cpp scenegraph/util/qsgvertexcolormaterial.h
- util/qminimalflatset_p.h
+ scenegraph/util/qquadpath.cpp scenegraph/util/qquadpath_p.h
+ scenegraph/util/qsggradientcache.cpp scenegraph/util/qsggradientcache_p.h
+ scenegraph/util/qsgtransform.cpp scenegraph/util/qsgtransform_p.h
util/qquickanimation.cpp util/qquickanimation_p.h
util/qquickanimation_p_p.h
util/qquickanimationcontroller.cpp util/qquickanimationcontroller_p.h
@@ -185,6 +197,7 @@ qt_internal_add_qml_module(Quick
util/qquickforeignutils.cpp util/qquickforeignutils_p.h
util/qquickglobal.cpp
util/qquickimageprovider.cpp util/qquickimageprovider.h util/qquickimageprovider_p.h
+ util/qquickpixmap_p.h
util/qquickpixmapcache.cpp util/qquickpixmapcache_p.h
util/qquickprofiler_p.h
util/qquickpropertychanges.cpp util/qquickpropertychanges_p.h
@@ -199,6 +212,7 @@ qt_internal_add_qml_module(Quick
util/qquicksvgparser.cpp util/qquicksvgparser_p.h
util/qquicksystempalette.cpp util/qquicksystempalette_p.h
util/qquicktextmetrics.cpp util/qquicktextmetrics_p.h
+ util/qquicktextselection.cpp util/qquicktextselection_p.h
util/qquicktimeline.cpp
util/qquicktimeline_p_p.h
util/qquicktransition.cpp util/qquicktransition_p.h
@@ -207,6 +221,11 @@ qt_internal_add_qml_module(Quick
util/qquickvalidator.cpp util/qquickvalidator_p.h
util/qquickvaluetypes.cpp util/qquickvaluetypes_p.h
util/qquickframeanimation.cpp util/qquickframeanimation_p.h
+ NO_UNITY_BUILD_SOURCES
+ scenegraph/qsgdefaultcontext.cpp # redefinition of 'qmlDisableDistanceField' (from qquicktextinput.cpp)
+ scenegraph/util/qsgtexturematerial.cpp # redefinition of 'isPowerOfTwo' (from qsgdefaultinternalimagenode.cpp)
+ util/qquickvalidator.cpp # expl. spec. of 'QMetaTypeId<QValidator *>' after inst.
+ quick_qmltyperegistrations.cpp # qquickvalidator_p.h: error: explicit specialization of 'QMetaTypeId<QValidator *>' after instantiation
DEFINES
QT_NO_FOREACH
QT_NO_INTEGER_EVENT_COORDINATES
@@ -218,19 +237,20 @@ qt_internal_add_qml_module(Quick
Qt::GuiPrivate
Qt::QmlModelsPrivate
Qt::QmlPrivate
+ Qt::QmlMetaPrivate
PUBLIC_LIBRARIES
Qt::Core
Qt::Gui
Qt::Qml
+ Qt::QmlMeta
Qt::QmlModels
PRIVATE_MODULE_INTERFACE
Qt::CorePrivate
Qt::GuiPrivate
Qt::QmlModelsPrivate
Qt::QmlPrivate
- GENERATE_CPP_EXPORTS
- GENERATE_PRIVATE_CPP_EXPORTS
-)
+ Qt::QmlMetaPrivate
+ )
# We need to do additional initialization, so we have to provide our own
# plugin class rather than using the generated one
@@ -244,6 +264,7 @@ qt_internal_add_shaders(Quick "scenegraph_shaders"
BATCHABLE
PRECOMPILE
OPTIMIZED
+ MULTIVIEW
PREFIX
"/qt-project.org"
FILES
@@ -302,6 +323,186 @@ qt_internal_add_shaders(Quick "scenegraph_shaders"
"scenegraph/shaders_ng/visualization.vert"
)
+qt_internal_add_shaders(Quick "scenegraph_curve_shaders"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ MULTIVIEW
+ PREFIX
+ "/qt-project.org"
+ FILES
+ "scenegraph/shaders_ng/shapecurve.frag"
+ "scenegraph/shaders_ng/shapecurve.vert"
+ "scenegraph/shaders_ng/shapestroke.frag"
+ "scenegraph/shaders_ng/shapestroke.vert"
+)
+
+qt_internal_add_shaders(Quick "scenegraph_curve_shaders_derivatives"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ MULTIVIEW
+ DEFINES "USE_DERIVATIVES"
+ PREFIX
+ "/qt-project.org"
+ FILES
+ "scenegraph/shaders_ng/shapecurve.frag"
+ "scenegraph/shaders_ng/shapecurve.vert"
+ OUTPUTS
+ "scenegraph/shaders_ng/shapecurve_derivatives.frag.qsb"
+ "scenegraph/shaders_ng/shapecurve_derivatives.vert.qsb"
+)
+
+qt_internal_add_shaders(Quick "scenegraph_curve_shaders_lg"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ MULTIVIEW
+ DEFINES
+ "LINEARGRADIENT"
+ PREFIX
+ "/qt-project.org"
+ FILES
+ "scenegraph/shaders_ng/shapecurve.frag"
+ "scenegraph/shaders_ng/shapecurve.vert"
+ OUTPUTS
+ "scenegraph/shaders_ng/shapecurve_lg.frag.qsb"
+ "scenegraph/shaders_ng/shapecurve_lg.vert.qsb"
+)
+
+qt_internal_add_shaders(Quick "scenegraph_curve_shaders_lg_derivatives"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ MULTIVIEW
+ DEFINES
+ "LINEARGRADIENT"
+ "USE_DERIVATIVES"
+ PREFIX
+ "/qt-project.org"
+ FILES
+ "scenegraph/shaders_ng/shapecurve.frag"
+ "scenegraph/shaders_ng/shapecurve.vert"
+ OUTPUTS
+ "scenegraph/shaders_ng/shapecurve_lg_derivatives.frag.qsb"
+ "scenegraph/shaders_ng/shapecurve_lg_derivatives.vert.qsb"
+)
+
+qt_internal_add_shaders(Quick "scenegraph_curve_shaders_rg"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ MULTIVIEW
+ DEFINES
+ "RADIALGRADIENT"
+ PREFIX
+ "/qt-project.org"
+ FILES
+ "scenegraph/shaders_ng/shapecurve.frag"
+ "scenegraph/shaders_ng/shapecurve.vert"
+ OUTPUTS
+ "scenegraph/shaders_ng/shapecurve_rg.frag.qsb"
+ "scenegraph/shaders_ng/shapecurve_rg.vert.qsb"
+)
+
+qt_internal_add_shaders(Quick "scenegraph_curve_shaders_rg_derivatives"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ MULTIVIEW
+ DEFINES
+ "RADIALGRADIENT"
+ "USE_DERIVATIVES"
+ PREFIX
+ "/qt-project.org"
+ FILES
+ "scenegraph/shaders_ng/shapecurve.frag"
+ "scenegraph/shaders_ng/shapecurve.vert"
+ OUTPUTS
+ "scenegraph/shaders_ng/shapecurve_rg_derivatives.frag.qsb"
+ "scenegraph/shaders_ng/shapecurve_rg_derivatives.vert.qsb"
+)
+
+qt_internal_add_shaders(Quick "scenegraph_curve_shaders_cg"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ MULTIVIEW
+ DEFINES
+ "CONICALGRADIENT"
+ PREFIX
+ "/qt-project.org"
+ FILES
+ "scenegraph/shaders_ng/shapecurve.frag"
+ "scenegraph/shaders_ng/shapecurve.vert"
+ OUTPUTS
+ "scenegraph/shaders_ng/shapecurve_cg.frag.qsb"
+ "scenegraph/shaders_ng/shapecurve_cg.vert.qsb"
+)
+
+qt_internal_add_shaders(Quick "scenegraph_curve_shaders_cg_derivatives"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ MULTIVIEW
+ DEFINES
+ "CONICALGRADIENT"
+ "USE_DERIVATIVES"
+ PREFIX
+ "/qt-project.org"
+ FILES
+ "scenegraph/shaders_ng/shapecurve.frag"
+ "scenegraph/shaders_ng/shapecurve.vert"
+ OUTPUTS
+ "scenegraph/shaders_ng/shapecurve_cg_derivatives.frag.qsb"
+ "scenegraph/shaders_ng/shapecurve_cg_derivatives.vert.qsb"
+)
+
+qt_internal_add_shaders(Quick "scenegraph_curve_shaders_tf"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ MULTIVIEW
+ DEFINES
+ "TEXTUREFILL"
+ PREFIX
+ "/qt-project.org"
+ FILES
+ "scenegraph/shaders_ng/shapecurve.frag"
+ "scenegraph/shaders_ng/shapecurve.vert"
+ OUTPUTS
+ "scenegraph/shaders_ng/shapecurve_tf.frag.qsb"
+ "scenegraph/shaders_ng/shapecurve_tf.vert.qsb"
+)
+
+qt_internal_add_shaders(Quick "scenegraph_curve_shaders_tf_derivatives"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ MULTIVIEW
+ DEFINES
+ "TEXTUREFILL"
+ "USE_DERIVATIVES"
+ PREFIX
+ "/qt-project.org"
+ FILES
+ "scenegraph/shaders_ng/shapecurve.frag"
+ "scenegraph/shaders_ng/shapecurve.vert"
+ OUTPUTS
+ "scenegraph/shaders_ng/shapecurve_tf_derivatives.frag.qsb"
+ "scenegraph/shaders_ng/shapecurve_tf_derivatives.vert.qsb"
+)
+
qt_internal_extend_target(Quick CONDITION QT_FEATURE_qml_network
LIBRARIES
Qt::Network
@@ -391,12 +592,28 @@ qt_internal_extend_target(Quick CONDITION QT_FEATURE_opengl OR QT_FEATURE_opengl
util/qquickopenglutils.cpp util/qquickopenglutils.h
)
-qt_internal_extend_target(Quick CONDITION IOS OR MACOS
+qt_internal_extend_target(Quick CONDITION QT_FEATURE_metal
SOURCES
scenegraph/coreapi/qsgtexture_mac.mm
scenegraph/qsgrhisupport_mac.mm
)
+qt_internal_extend_target(Quick CONDITION ANDROID
+ SOURCES
+ platform/android/qandroidquickviewembedding.cpp platform/android/qandroidquickviewembedding_p.h
+ platform/android/qandroidviewsignalmanager.cpp platform/android/qandroidviewsignalmanager_p.h
+ platform/android/qandroiditemmodelproxy.cpp platform/android/qandroiditemmodelproxy_p.h
+ platform/android/qandroidmodelindexproxy.cpp platform/android/qandroidmodelindexproxy_p.h
+ platform/android/qandroidtypes_p.h
+ platform/android/qandroidtypeconverter_p.h
+)
+if (ANDROID)
+ add_subdirectory(jar)
+ set_property(TARGET Quick PROPERTY QT_ANDROID_BUNDLED_JAR_DEPENDENCIES
+ jar/Qt${QtDeclarative_VERSION_MAJOR}AndroidQuick.jar
+ )
+endif()
+
qt_internal_extend_target(Quick CONDITION QT_FEATURE_thread
SOURCES
scenegraph/adaptations/software/qsgsoftwarethreadedrenderloop.cpp scenegraph/adaptations/software/qsgsoftwarethreadedrenderloop_p.h
@@ -514,7 +731,14 @@ qt_internal_extend_target(Quick CONDITION QT_FEATURE_im
util/qquickinputmethod.cpp util/qquickinputmethod_p.h
)
-qt_internal_create_tracepoints(Quick qtquick.tracepoints)
+qt_internal_generate_tracepoints(Quick quick
+ SOURCES
+ scenegraph/qsgrenderloop.cpp
+ scenegraph/coreapi/qsgrenderer.cpp
+ scenegraph/qsgthreadedrenderloop.cpp
+ scenegraph/adaptations/software/qsgsoftwarethreadedrenderloop.cpp
+ scenegraph/qsgadaptationlayer.cpp
+)
qt_internal_add_docs(Quick
doc/qtquick.qdocconf
)
diff --git a/src/quick/accessible/qaccessiblequickitem.cpp b/src/quick/accessible/qaccessiblequickitem.cpp
index 4a446e468d..5824b63a4c 100644
--- a/src/quick/accessible/qaccessiblequickitem.cpp
+++ b/src/quick/accessible/qaccessiblequickitem.cpp
@@ -456,7 +456,7 @@ QAccessible::Role QAccessibleQuickItem::role() const
QAccessible::Role role = QAccessible::NoRole;
if (item())
- role = QQuickItemPrivate::get(item())->accessibleRole();
+ role = QQuickItemPrivate::get(item())->effectiveAccessibleRole();
if (role == QAccessible::NoRole) {
if (qobject_cast<QQuickText*>(const_cast<QQuickItem *>(item())))
role = QAccessible::StaticText;
@@ -604,6 +604,20 @@ QString QAccessibleQuickItem::text(QAccessible::Text textType) const
if (!accessibleDecription.isNull())
return accessibleDecription.toString();
break;}
+ case QAccessible::Identifier: {
+ QVariant accessibleIdentifier = QQuickAccessibleAttached::property(object(), "id");
+ if (!accessibleIdentifier.isNull())
+ return accessibleIdentifier.toString();
+ auto quickItem = item();
+ if (quickItem->isComponentComplete()) {
+ QQmlContext *context = qmlContext(quickItem);
+ if (context) {
+ const auto objectId = context->nameForObject(quickItem);
+ if (!objectId.isEmpty())
+ return objectId;
+ }
+ }
+ break;}
#ifdef Q_ACCESSIBLE_QUICK_ITEM_ENABLE_DEBUG_DESCRIPTION
case QAccessible::DebugDescription: {
QString debugString;
@@ -622,6 +636,8 @@ QString QAccessibleQuickItem::text(QAccessible::Text textType) const
// the following block handles item-specific behavior
if (role() == QAccessible::EditableText) {
if (textType == QAccessible::Value) {
+ if (auto textInput = qobject_cast<QQuickTextInput *>(item()))
+ return textInput->displayText();
if (QTextDocument *doc = textDocument()) {
return doc->toPlainText();
}
@@ -707,19 +723,20 @@ QRect itemScreenRect(QQuickItem *item)
return QRect();
}
- QSize itemSize((int)item->width(), (int)item->height());
+ QSizeF itemSize(item->width(), item->height());
// ### If the bounding rect fails, we first try the implicit size, then we go for the
// parent size. WE MIGHT HAVE TO REVISIT THESE FALLBACKS.
if (itemSize.isEmpty()) {
- itemSize = QSize((int)item->implicitWidth(), (int)item->implicitHeight());
+ itemSize = QSize(item->implicitWidth(), item->implicitHeight());
if (itemSize.isEmpty() && item->parentItem())
// ### Seems that the above fallback is not enough, fallback to use the parent size...
- itemSize = QSize((int)item->parentItem()->width(), (int)item->parentItem()->height());
+ itemSize = QSize(item->parentItem()->width(), item->parentItem()->height());
}
- QPointF scenePoint = item->mapToScene(QPointF(0, 0));
- QPoint screenPos = item->window()->mapToGlobal(scenePoint.toPoint());
- return QRect(screenPos, itemSize);
+ QRectF sceneRect = item->mapRectToScene(QRectF(QPointF(0, 0), itemSize));
+ QPoint screenPos = item->window()->mapToGlobal(sceneRect.topLeft().toPoint());
+ QSize screenSize = sceneRect.size().toSize();
+ return QRect(screenPos, screenSize);
}
QTextDocument *QAccessibleQuickItem::textDocument() const
diff --git a/src/quick/accessible/qaccessiblequickitem_p.h b/src/quick/accessible/qaccessiblequickitem_p.h
index 9239295175..50fd09a5c0 100644
--- a/src/quick/accessible/qaccessiblequickitem_p.h
+++ b/src/quick/accessible/qaccessiblequickitem_p.h
@@ -26,7 +26,7 @@ QT_BEGIN_NAMESPACE
class QTextDocument;
-class Q_QUICK_PRIVATE_EXPORT QAccessibleQuickItem : public QAccessibleObject, public QAccessibleActionInterface, public QAccessibleValueInterface, public QAccessibleTextInterface
+class Q_QUICK_EXPORT QAccessibleQuickItem : public QAccessibleObject, public QAccessibleActionInterface, public QAccessibleValueInterface, public QAccessibleTextInterface
{
public:
QAccessibleQuickItem(QQuickItem *item);
diff --git a/src/quick/accessible/qaccessiblequickview.cpp b/src/quick/accessible/qaccessiblequickview.cpp
index 5cd93b9613..08f5889070 100644
--- a/src/quick/accessible/qaccessiblequickview.cpp
+++ b/src/quick/accessible/qaccessiblequickview.cpp
@@ -21,7 +21,7 @@ QAccessibleQuickWindow::QAccessibleQuickWindow(QQuickWindow *object)
QList<QQuickItem *> QAccessibleQuickWindow::rootItems() const
{
- if (QQuickItem *ci = window()->contentItem())
+ if (QQuickItem *ci = window() ? window()->contentItem() : nullptr)
return accessibleUnignoredChildren(ci);
return QList<QQuickItem *>();
}
@@ -47,7 +47,7 @@ QAccessibleInterface *QAccessibleQuickWindow::child(int index) const
QAccessibleInterface *QAccessibleQuickWindow::focusChild() const
{
- QObject *focusObject = window()->focusObject();
+ QObject *focusObject = window() ? window()->focusObject() : nullptr;
if (focusObject) {
QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(focusObject);
if (!iface || iface == this || !iface->focusChild())
@@ -67,18 +67,22 @@ QAccessible::State QAccessibleQuickWindow::state() const
QAccessible::State st;
if (window() == QGuiApplication::focusWindow())
st.active = true;
- if (!window()->isVisible())
+ if (!window() || !window()->isVisible())
st.invisible = true;
return st;
}
QRect QAccessibleQuickWindow::rect() const
{
+ if (!window())
+ return {};
return QRect(window()->x(), window()->y(), window()->width(), window()->height());
}
QString QAccessibleQuickWindow::text(QAccessible::Text text) const
{
+ if (!window())
+ return {};
#ifdef Q_ACCESSIBLE_QUICK_ITEM_ENABLE_DEBUG_DESCRIPTION
if (text == QAccessible::DebugDescription) {
return QString::fromLatin1(object()->metaObject()->className()) ;
diff --git a/src/quick/configure.cmake b/src/quick/configure.cmake
index ad4c645983..99dcc24569 100644
--- a/src/quick/configure.cmake
+++ b/src/quick/configure.cmake
@@ -119,6 +119,13 @@ qt_feature("quick-draganddrop" PUBLIC
PURPOSE "Drag and drop support for Qt Quick"
CONDITION ( QT_FEATURE_draganddrop ) AND ( QT_FEATURE_regularexpression )
)
+
+qt_feature("quick-pixmap-cache-threaded-download" PUBLIC
+ SECTION "Qt Quick"
+ LABEL "Threaded download in pixmap cache"
+ PURPOSE "Pixmap cache pixmap downloads on separate threads"
+ CONDITION ( QT_FEATURE_thread ) AND ( NOT WASM )
+)
qt_configure_add_summary_section(NAME "Qt Quick")
qt_configure_add_summary_entry(ARGS "quick-animatedimage")
qt_configure_add_summary_entry(ARGS "quick-canvas")
diff --git a/src/quick/designer/qqmldesignermetaobject_p.h b/src/quick/designer/qqmldesignermetaobject_p.h
index 71623f46ab..7fd8e77387 100644
--- a/src/quick/designer/qqmldesignermetaobject_p.h
+++ b/src/quick/designer/qqmldesignermetaobject_p.h
@@ -22,6 +22,8 @@
#include <private/qqmlopenmetaobject_p.h>
#include <private/qqmlvmemetaobject_p.h>
+#include <QtCore/qpointer.h>
+
#include <memory>
QT_BEGIN_NAMESPACE
diff --git a/src/quick/designer/qquickdesignersupport.cpp b/src/quick/designer/qquickdesignersupport.cpp
index 5fccd43e96..6f937e38d6 100644
--- a/src/quick/designer/qquickdesignersupport.cpp
+++ b/src/quick/designer/qquickdesignersupport.cpp
@@ -139,7 +139,7 @@ QTransform QQuickDesignerSupport::parentTransform(QQuickItem *referencedItem)
QTransform parentTransform;
- QQuickItemPrivate::get(referencedItem)->itemToParentTransform(parentTransform);
+ QQuickItemPrivate::get(referencedItem)->itemToParentTransform(&parentTransform);
return parentTransform;
}
diff --git a/src/quick/doc/images/declarative-scalegrid.png b/src/quick/doc/images/declarative-scalegrid.png
index 32d8712568..69ad06f834 100644
--- a/src/quick/doc/images/declarative-scalegrid.png
+++ b/src/quick/doc/images/declarative-scalegrid.png
Binary files differ
diff --git a/src/quick/doc/images/how-to-time-picker-dark.png b/src/quick/doc/images/how-to-time-picker-dark.png
new file mode 100644
index 0000000000..87fa6bd2cd
--- /dev/null
+++ b/src/quick/doc/images/how-to-time-picker-dark.png
Binary files differ
diff --git a/src/quick/doc/images/how-to-time-picker-light.png b/src/quick/doc/images/how-to-time-picker-light.png
new file mode 100644
index 0000000000..6eb1aba2ef
--- /dev/null
+++ b/src/quick/doc/images/how-to-time-picker-light.png
Binary files differ
diff --git a/src/quick/doc/images/pinchAndDragHandlers-drag-one-rect.png b/src/quick/doc/images/pinchAndDragHandlers-drag-one-rect.png
new file mode 100644
index 0000000000..0c9482d8f7
--- /dev/null
+++ b/src/quick/doc/images/pinchAndDragHandlers-drag-one-rect.png
Binary files differ
diff --git a/src/quick/doc/images/pinchAndDragHandlers-drag-two-rects.png b/src/quick/doc/images/pinchAndDragHandlers-drag-two-rects.png
new file mode 100644
index 0000000000..494d92e836
--- /dev/null
+++ b/src/quick/doc/images/pinchAndDragHandlers-drag-two-rects.png
Binary files differ
diff --git a/src/quick/doc/images/pinchAndDragHandlers-pinch.png b/src/quick/doc/images/pinchAndDragHandlers-pinch.png
new file mode 100644
index 0000000000..ed21a74c38
--- /dev/null
+++ b/src/quick/doc/images/pinchAndDragHandlers-pinch.png
Binary files differ
diff --git a/src/quick/doc/images/pointerHandlers/dragReleaseMenu.webp b/src/quick/doc/images/pointerHandlers/dragReleaseMenu.webp
new file mode 100644
index 0000000000..16aaca4d86
--- /dev/null
+++ b/src/quick/doc/images/pointerHandlers/dragReleaseMenu.webp
Binary files differ
diff --git a/src/quick/doc/images/pointerHandlers/tapHandlerButtonReleaseWithinBounds.webp b/src/quick/doc/images/pointerHandlers/tapHandlerButtonReleaseWithinBounds.webp
new file mode 100644
index 0000000000..3edab7d7a1
--- /dev/null
+++ b/src/quick/doc/images/pointerHandlers/tapHandlerButtonReleaseWithinBounds.webp
Binary files differ
diff --git a/src/quick/doc/images/pointerHandlers/tapHandlerButtonWithinBounds.webp b/src/quick/doc/images/pointerHandlers/tapHandlerButtonWithinBounds.webp
new file mode 100644
index 0000000000..05cb2f2276
--- /dev/null
+++ b/src/quick/doc/images/pointerHandlers/tapHandlerButtonWithinBounds.webp
Binary files differ
diff --git a/src/quick/doc/images/pointerHandlers/tapHandlerOverlappingButtons.webp b/src/quick/doc/images/pointerHandlers/tapHandlerOverlappingButtons.webp
new file mode 100644
index 0000000000..0455097159
--- /dev/null
+++ b/src/quick/doc/images/pointerHandlers/tapHandlerOverlappingButtons.webp
Binary files differ
diff --git a/src/quick/doc/images/qml-borderimage-rounded.png b/src/quick/doc/images/qml-borderimage-rounded.png
new file mode 100644
index 0000000000..562fc277bb
--- /dev/null
+++ b/src/quick/doc/images/qml-borderimage-rounded.png
Binary files differ
diff --git a/src/quick/doc/images/qml-borderimage-scaled.png b/src/quick/doc/images/qml-borderimage-scaled.png
index e42891ecc7..230e8a010e 100644
--- a/src/quick/doc/images/qml-borderimage-scaled.png
+++ b/src/quick/doc/images/qml-borderimage-scaled.png
Binary files differ
diff --git a/src/quick/doc/images/qml-borderimage-tiled.png b/src/quick/doc/images/qml-borderimage-tiled.png
index e27d9601c4..f7523e8e94 100644
--- a/src/quick/doc/images/qml-borderimage-tiled.png
+++ b/src/quick/doc/images/qml-borderimage-tiled.png
Binary files differ
diff --git a/src/quick/doc/images/qml-item-canvas-startAngle.png b/src/quick/doc/images/qml-item-canvas-startAngle.png
index bf82c3aa4b..7930284896 100644
--- a/src/quick/doc/images/qml-item-canvas-startAngle.png
+++ b/src/quick/doc/images/qml-item-canvas-startAngle.png
Binary files differ
diff --git a/src/quick/doc/images/simpleProxy.png b/src/quick/doc/images/simpleProxy.png
new file mode 100644
index 0000000000..f99898dca8
--- /dev/null
+++ b/src/quick/doc/images/simpleProxy.png
Binary files differ
diff --git a/src/quick/doc/qtquick.qdocconf b/src/quick/doc/qtquick.qdocconf
index 02e12c4c11..deda199b96 100644
--- a/src/quick/doc/qtquick.qdocconf
+++ b/src/quick/doc/qtquick.qdocconf
@@ -37,10 +37,12 @@ tagfile = qtquick.tags
depends += \
qtcore \
+ qtopengl \
qtqml \
qtqmlmodels \
qtqmlxmllistmodel \
qtqmltest \
+ qtqmlcore \
qtgui \
qtlinguist \
qtquickcontrols \
@@ -51,8 +53,10 @@ depends += \
qmake \
qtsql \
qtshadertools \
+ qtsvg \
qmake \
- qtcmake
+ qtcmake \
+ qtquickeffectmaker
{headerdirs,sourcedirs} += \
.. \
@@ -60,7 +64,9 @@ depends += \
../../quickwidgets \
../../qmllocalstorage \
../../quicklayouts \
- ../../labs
+ ../../labs \
+ ../../quick/jar/org/qtproject/qt/android \
+ ../../../examples/platforms
# both have their own documentation project
excludedirs += \
@@ -70,7 +76,10 @@ excludedirs += \
exampledirs += \
../../../examples/quick \
../../qmlmodels/doc/snippets \
- snippets
+ ../../quickcontrols/doc/snippets \
+ snippets \
+ ../../../tests/auto/quick/doc \
+ ../../../examples/platforms
imagedirs += images
@@ -78,7 +87,8 @@ imagedirs += images
{headerdirs,sourcedirs} += \
../../particles \
../../quickshapes \
- ../../effects
+ ../../effects \
+ ../../quickvectorimage
# Add imports and plugins directories because of dependencies
{headerdirs,sourcedirs} += \
@@ -86,12 +96,16 @@ imagedirs += images
../../plugins
excludefiles += ../util/qquickpropertychanges_p.h
-examples.fileextensions += "*.qm"
+examples.fileextensions += "*.qm" \
+ "*.java" \
+ "*.kt"
-manifestmeta.thumbnail.names += "QtQuick/Threaded ListModel Example" \
- "QtQuick/QML Dynamic View Ordering Tutorial*"
+manifestmeta.thumbnail.names += "QtQuick/QML Dynamic View Ordering Tutorial*"
-manifestmeta.highlighted.names = "QtQuick/Qt Quick Demo - Same Game"
+# Highlight examples for the Graphics & Multimedia category
+manifestmeta.highlighted.names = \
+ "QtQuick/Qt Quick Layouts - Responsive Layout Example" \
+ "QtQuick/Scene Graph - RHI Under QML"
navigation.landingpage = "Qt Quick"
navigation.cppclassespage = "Qt Quick C++ Classes"
@@ -99,12 +113,15 @@ navigation.qmltypespage = "Qt Quick QML Types"
# \svgcolor {#ffdead}
macro.svgcolor.HTML = "<div style=\"padding:10px;color:#fff;background:\1;\"></div>"
+macro.svgcolor.DocBook = "<db:phrase role=\"color:\1\">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</db:phrase>"
+
+macro.QQEM = "Qt Quick Effect Maker"
# YouTube video thumbnail that show up in offline docs
-{HTML.extraimages,qhp.QtQuick.extraFiles} += images/9BcAYDlpuT8.jpg
+{HTML.extraimages,DocBook.extraFiles,qhp.QtQuick.extraFiles} += images/9BcAYDlpuT8.jpg
# suppress qdoc warnings for \instantiates entries
spurious += "C\\+\\+ class .*\\\\instantiates .*"
-# Fail the documentation build if there are more warnings than the limit
+# Enforce zero documentation warnings
warninglimit = 0
diff --git a/src/quick/doc/snippets/code/doc_src_qtquick.cmake b/src/quick/doc/snippets/code/doc_src_qtquick.cmake
index 5bbb4ca0b0..e1ebae4021 100644
--- a/src/quick/doc/snippets/code/doc_src_qtquick.cmake
+++ b/src/quick/doc/snippets/code/doc_src_qtquick.cmake
@@ -1,5 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: BSD-3-Clause
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#! [0]
find_package(Qt6 REQUIRED COMPONENTS Quick)
diff --git a/src/quick/doc/snippets/imgprovider/imageprovider-example.qml b/src/quick/doc/snippets/imgprovider/imageprovider-example.qml
index db65a3bc41..05f8e4375b 100644
--- a/src/quick/doc/snippets/imgprovider/imageprovider-example.qml
+++ b/src/quick/doc/snippets/imgprovider/imageprovider-example.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//![0]
Column {
Image { source: "image://colors/yellow" }
diff --git a/src/quick/doc/snippets/layouts/responsiveDeclarative.qml b/src/quick/doc/snippets/layouts/responsiveDeclarative.qml
new file mode 100644
index 0000000000..e5a0c6c4f4
--- /dev/null
+++ b/src/quick/doc/snippets/layouts/responsiveDeclarative.qml
@@ -0,0 +1,33 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Window
+import QtQuick.Controls
+
+Window {
+ visible: true
+ width: 350
+ height: 250
+ //! [document]
+ GridLayout {
+ columns: width < 300 ? 1 : 2
+ anchors.fill: parent
+
+ Rectangle {
+ id: rectangle1
+ color: "tomato"
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ }
+
+ Rectangle {
+ id: rectangle2
+ color: "lightskyblue"
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ }
+ }
+ //! [document]
+}
diff --git a/src/quick/doc/snippets/layouts/responsiveStates.qml b/src/quick/doc/snippets/layouts/responsiveStates.qml
new file mode 100644
index 0000000000..5506407230
--- /dev/null
+++ b/src/quick/doc/snippets/layouts/responsiveStates.qml
@@ -0,0 +1,44 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Window
+
+Window {
+ visible: true
+ width: 350
+ height: 250
+ //! [document]
+ GridLayout {
+ anchors.fill: parent
+
+ Rectangle {
+ id: rectangle1
+ color: "tomato"
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ }
+
+ Rectangle {
+ id: rectangle2
+ color: "lightskyblue"
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ }
+
+ states: [
+ State {
+ when: width < 300
+ PropertyChanges { target: rectangle2; Layout.row: 1 }
+ PropertyChanges { target: rectangle2; Layout.column: 0 }
+ },
+ State {
+ when: width >= 300
+ PropertyChanges { target: rectangle2; Layout.row: 0 }
+ PropertyChanges { target: rectangle2; Layout.column: 1 }
+ }
+ ]
+ }
+ //! [document]
+}
diff --git a/src/quick/doc/snippets/layouts/simpleProxy.qml b/src/quick/doc/snippets/layouts/simpleProxy.qml
new file mode 100644
index 0000000000..460e6bda32
--- /dev/null
+++ b/src/quick/doc/snippets/layouts/simpleProxy.qml
@@ -0,0 +1,62 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Window
+import QtQuick.Controls
+
+Window {
+ visible: true
+
+ width: 350
+ //! [document]
+ //! [item definition]
+ Rectangle {
+ id: rectangle1
+ color: "tomato"
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ }
+
+ Rectangle {
+ id: rectangle2
+ color: "lightskyblue"
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ }
+ //! [item definition]
+
+ //! [layout definition]
+ GridLayout {
+ id: l1
+ columns: 1
+ visible: false
+ anchors.fill: parent
+ LayoutItemProxy { target: rectangle1 }
+ LayoutItemProxy { target: rectangle2 }
+ }
+
+ GridLayout {
+ id: l2
+ columns: 2
+ visible: true
+ anchors.fill: parent
+ LayoutItemProxy { target: rectangle1 }
+ LayoutItemProxy { target: rectangle2 }
+ }
+ //! [layout definition]
+
+ //! [layout choice]
+ onWidthChanged: {
+ if (width < 300) {
+ l2.visible = false
+ l1.visible = true
+ } else {
+ l1.visible = false
+ l2.visible = true
+ }
+ }
+ //! [layout choice]
+ //! [document]
+}
diff --git a/src/quick/doc/snippets/pointerHandlers/dragHandler.qml b/src/quick/doc/snippets/pointerHandlers/dragHandler.qml
index 6c694f6512..5a8f5d2a37 100644
--- a/src/quick/doc/snippets/pointerHandlers/dragHandler.qml
+++ b/src/quick/doc/snippets/pointerHandlers/dragHandler.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.12
+import QtQuick
Rectangle {
width: 100
diff --git a/src/quick/doc/snippets/pointerHandlers/dragHandlerAcceptedButtons.qml b/src/quick/doc/snippets/pointerHandlers/dragHandlerAcceptedButtons.qml
new file mode 100644
index 0000000000..9aeceb04f6
--- /dev/null
+++ b/src/quick/doc/snippets/pointerHandlers/dragHandlerAcceptedButtons.qml
@@ -0,0 +1,81 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+pragma ComponentBehavior: Bound
+import QtQuick
+import Qt.labs.animation
+import Qt.labs.folderlistmodel
+
+//![0]
+Rectangle {
+ id: canvas
+ width: 640
+ height: 480
+ color: "#333"
+ property int highestZ: 0
+
+ Repeater {
+ model: FolderListModel { nameFilters: ["*.qml"] }
+
+ delegate: Rectangle {
+ required property string fileName
+ required property url fileUrl
+ required property int index
+
+ id: frame
+ x: index * 30; y: index * 30
+ width: 320; height: 240
+ property bool dragging: ldh.active || rdh.active
+ onDraggingChanged: if (dragging) z = ++canvas.highestZ
+ border { width: 2; color: dragging ? "red" : "steelblue" }
+ color: "beige"
+ clip: true
+
+ TextEdit {
+ // drag to select text
+ id: textEdit
+ textDocument.source: frame.fileUrl
+ x: 3; y: 3
+
+ BoundaryRule on y {
+ id: ybr
+ minimum: textEdit.parent.height - textEdit.height; maximum: 0
+ minimumOvershoot: 200; maximumOvershoot: 200
+ overshootFilter: BoundaryRule.Peak
+ }
+ }
+
+ DragHandler {
+ id: rdh
+ // right-drag to position the "window"
+ acceptedButtons: Qt.RightButton
+ }
+
+ WheelHandler {
+ target: textEdit
+ property: "y"
+ onActiveChanged: if (!active) ybr.returnToBounds()
+ }
+
+ Rectangle {
+ anchors.right: parent.right
+ width: titleText.implicitWidth + 12
+ height: titleText.implicitHeight + 6
+ border { width: 2; color: parent.border.color }
+ bottomLeftRadius: 6
+ Text {
+ id: titleText
+ color: "saddlebrown"
+ anchors.centerIn: parent
+ text: frame.fileName
+ textFormat: Text.PlainText
+ }
+ DragHandler {
+ id: ldh
+ // left-drag to position the "window"
+ target: frame
+ }
+ }
+ }
+ }
+}
+//![0]
diff --git a/src/quick/doc/snippets/pointerHandlers/dragHandlerDifferentTarget.qml b/src/quick/doc/snippets/pointerHandlers/dragHandlerDifferentTarget.qml
index 2d2bb78c07..103e4febff 100644
--- a/src/quick/doc/snippets/pointerHandlers/dragHandlerDifferentTarget.qml
+++ b/src/quick/doc/snippets/pointerHandlers/dragHandlerDifferentTarget.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.12
+import QtQuick
Item {
width: 640
diff --git a/src/quick/doc/snippets/pointerHandlers/dragHandlerNullTarget.qml b/src/quick/doc/snippets/pointerHandlers/dragHandlerNullTarget.qml
index 39765fd4b8..a2e2ae7c04 100644
--- a/src/quick/doc/snippets/pointerHandlers/dragHandlerNullTarget.qml
+++ b/src/quick/doc/snippets/pointerHandlers/dragHandlerNullTarget.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.12
+import QtQuick
Item {
width: 640
diff --git a/src/quick/doc/snippets/pointerHandlers/dragReleaseMenu.qml b/src/quick/doc/snippets/pointerHandlers/dragReleaseMenu.qml
new file mode 100644
index 0000000000..e6d2266408
--- /dev/null
+++ b/src/quick/doc/snippets/pointerHandlers/dragReleaseMenu.qml
@@ -0,0 +1,72 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+//![0]
+import QtQuick
+
+Rectangle {
+ id: rect
+ width: 140; height: 100
+
+ //![1]
+ TapHandler {
+ id: menuPopupHandler
+ gesturePolicy: TapHandler.DragWithinBounds
+ onPressedChanged:
+ if (pressed) {
+ menu.x = point.position.x - menu.width / 2
+ menu.y = point.position.y - menu.height / 2
+ } else {
+ feedback.text = menu.highlightedMenuItem
+ selectFlash.start()
+ }
+ onCanceled: feedback.text = "canceled"
+ }
+ //![1]
+
+ Column {
+ id: menu
+ visible: menuPopupHandler.pressed
+ opacity: Math.min(1, menuPopupHandler.timeHeld)
+ property string highlightedMenuItem: ""
+ Repeater {
+ model: [ "top", "middle", "bottom" ]
+ delegate: Rectangle {
+ property bool highlighted: menuPopupHandler.pressed &&
+ contains(mapFromItem(rect, menuPopupHandler.point.position))
+ onHighlightedChanged: {
+ if (highlighted)
+ menu.highlightedMenuItem = menuItemText.text
+ else if (menu.highlightedMenuItem === menuItemText.text)
+ menu.highlightedMenuItem = ""
+ }
+ width: 100
+ height: 20
+ color: highlighted ? "lightsteelblue" : "aliceblue"
+ Text {
+ id: menuItemText
+ anchors.centerIn: parent
+ text: modelData
+ }
+ }
+ }
+ }
+
+ Text {
+ id: feedback
+ y: 6; anchors.horizontalCenter: parent.horizontalCenter
+ textFormat: Text.MarkdownText
+ text: "hold for context menu"
+
+ SequentialAnimation on font.weight {
+ id: selectFlash
+ running: false
+ loops: 3
+ PropertyAction { value: Font.Black }
+ PauseAnimation { duration: 100 }
+ PropertyAction { value: Font.Normal }
+ PauseAnimation { duration: 100 }
+ }
+ }
+}
+//![0]
diff --git a/src/quick/doc/snippets/pointerHandlers/handlerFlick.qml b/src/quick/doc/snippets/pointerHandlers/handlerFlick.qml
index 601e94fc5d..83ab245244 100644
--- a/src/quick/doc/snippets/pointerHandlers/handlerFlick.qml
+++ b/src/quick/doc/snippets/pointerHandlers/handlerFlick.qml
@@ -1,8 +1,8 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.14
-import Qt.labs.animation 1.0
+import QtQuick
+import Qt.labs.animation
Item {
width: 320; height: 480
diff --git a/src/quick/doc/snippets/pointerHandlers/hoverMouseOrStylus.qml b/src/quick/doc/snippets/pointerHandlers/hoverMouseOrStylus.qml
index f79133df8d..40c9367f6a 100644
--- a/src/quick/doc/snippets/pointerHandlers/hoverMouseOrStylus.qml
+++ b/src/quick/doc/snippets/pointerHandlers/hoverMouseOrStylus.qml
@@ -15,7 +15,7 @@ Rectangle {
HoverHandler {
id: mouse
- acceptedDevices: PointerDevice.Mouse
+ acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
cursorShape: Qt.PointingHandCursor
}
}
diff --git a/src/quick/doc/snippets/pointerHandlers/hoverTapKeyButton.qml b/src/quick/doc/snippets/pointerHandlers/hoverTapKeyButton.qml
index a72bbf8232..a301547b07 100644
--- a/src/quick/doc/snippets/pointerHandlers/hoverTapKeyButton.qml
+++ b/src/quick/doc/snippets/pointerHandlers/hoverTapKeyButton.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.12
+import QtQuick
Rectangle {
id: button
diff --git a/src/quick/doc/snippets/pointerHandlers/pinchAndDragHandlers.qml b/src/quick/doc/snippets/pointerHandlers/pinchAndDragHandlers.qml
new file mode 100644
index 0000000000..a6b990ac62
--- /dev/null
+++ b/src/quick/doc/snippets/pointerHandlers/pinchAndDragHandlers.qml
@@ -0,0 +1,54 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+//![entire]
+import QtQuick
+
+Rectangle {
+ id: root
+ width: 400
+ height: 400
+ color: ph.active ? "aquamarine" : "beige"
+
+ PinchHandler {
+ id: ph
+ grabPermissions: PointerHandler.TakeOverForbidden
+ }
+
+ Rectangle {
+ objectName: "rect1"
+ x: 50
+ width: 100
+ height: 100
+ color: dh1.active ? "tomato" : "wheat"
+ DragHandler {
+ id: dh1
+ objectName: "dh1"
+ }
+ }
+
+ Rectangle {
+ objectName: "rect2"
+ x: 250
+ width: 100
+ height: 100
+ color: dh2.active ? "tomato" : "lightsteelblue"
+ DragHandler {
+ id: dh2
+ objectName: "dh2"
+ }
+ }
+
+ Rectangle {
+ objectName: "rect3"
+ x: 150
+ y: 150
+ width: 100
+ height: 100
+ color: dh3.active ? "tomato" : "darksalmon"
+ DragHandler {
+ id: dh3
+ objectName: "dh3"
+ }
+ }
+}
+//![entire]
diff --git a/src/quick/doc/snippets/pointerHandlers/pinchHandlerDifferentTarget.qml b/src/quick/doc/snippets/pointerHandlers/pinchHandlerDifferentTarget.qml
index 92d26b8e67..7bff2e0a70 100644
--- a/src/quick/doc/snippets/pointerHandlers/pinchHandlerDifferentTarget.qml
+++ b/src/quick/doc/snippets/pointerHandlers/pinchHandlerDifferentTarget.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.12
+import QtQuick
Item {
width: 640
diff --git a/src/quick/doc/snippets/pointerHandlers/pinchHandler.qml b/src/quick/doc/snippets/pointerHandlers/pinchHandlerSimple.qml
index 5027204ff6..b78a1d7871 100644
--- a/src/quick/doc/snippets/pointerHandlers/pinchHandler.qml
+++ b/src/quick/doc/snippets/pointerHandlers/pinchHandlerSimple.qml
@@ -1,7 +1,8 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-//![0]
-import QtQuick 2.12
+
+//! [0]
+import QtQuick
Rectangle {
width: 400
@@ -9,4 +10,4 @@ Rectangle {
color: "lightsteelblue"
PinchHandler { }
}
-//![0]
+//! [0]
diff --git a/src/quick/doc/snippets/pointerHandlers/pointHandler.qml b/src/quick/doc/snippets/pointerHandlers/pointHandler.qml
index f5fe29566e..d363150ab5 100644
--- a/src/quick/doc/snippets/pointerHandlers/pointHandler.qml
+++ b/src/quick/doc/snippets/pointerHandlers/pointHandler.qml
@@ -1,8 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.12
-import QtQuick.Window 2.2
+import QtQuick
Window {
width: 480
@@ -14,6 +13,7 @@ Window {
z: 10000
anchors.fill: parent
+ //![1]
PointHandler {
id: handler
acceptedDevices: PointerDevice.TouchScreen | PointerDevice.TouchPad
@@ -26,6 +26,7 @@ Window {
width: 20; height: width; radius: width / 2
}
}
+ //![1]
}
}
//![0]
diff --git a/src/quick/doc/snippets/pointerHandlers/pointHandlerAcceptedButtons.qml b/src/quick/doc/snippets/pointerHandlers/pointHandlerAcceptedButtons.qml
new file mode 100644
index 0000000000..3eab8b319b
--- /dev/null
+++ b/src/quick/doc/snippets/pointerHandlers/pointHandlerAcceptedButtons.qml
@@ -0,0 +1,21 @@
+// Copyright (C) 2023 Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+//![0]
+import QtQuick
+
+Item {
+ width: 480; height: 320
+
+ Rectangle {
+ color: handler.active ? "tomato" : "wheat"
+ x: handler.point.position.x - width / 2
+ y: handler.point.position.y - height / 2
+ width: 20; height: width; radius: width / 2
+ }
+
+ PointHandler {
+ id: handler
+ acceptedButtons: Qt.MiddleButton | Qt.RightButton
+ }
+}
+//![0]
diff --git a/src/quick/doc/snippets/pointerHandlers/pointHandlerAcceptedModifiers.qml b/src/quick/doc/snippets/pointerHandlers/pointHandlerAcceptedModifiers.qml
new file mode 100644
index 0000000000..9e3cb6f465
--- /dev/null
+++ b/src/quick/doc/snippets/pointerHandlers/pointHandlerAcceptedModifiers.qml
@@ -0,0 +1,36 @@
+// Copyright (C) 2023 Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+//![0]
+import QtQuick
+
+Item {
+ id: feedbackPane
+ width: 480; height: 320
+
+ PointHandler {
+ id: control
+ acceptedModifiers: Qt.ControlModifier
+ cursorShape: Qt.PointingHandCursor
+ target: Rectangle {
+ parent: feedbackPane
+ color: control.active ? "indianred" : "khaki"
+ x: control.point.position.x - width / 2
+ y: control.point.position.y - height / 2
+ width: 20; height: width; radius: width / 2
+ }
+ }
+
+ PointHandler {
+ id: shift
+ acceptedModifiers: Qt.ShiftModifier | Qt.MetaModifier
+ cursorShape: Qt.CrossCursor
+ target: Rectangle {
+ parent: feedbackPane
+ color: shift.active ? "darkslateblue" : "lightseagreen"
+ x: shift.point.position.x - width / 2
+ y: shift.point.position.y - height / 2
+ width: 30; height: width; radius: width / 2
+ }
+ }
+}
+//![0]
diff --git a/src/quick/doc/snippets/pointerHandlers/pointHandlerCanvasDrawing.qml b/src/quick/doc/snippets/pointerHandlers/pointHandlerCanvasDrawing.qml
new file mode 100644
index 0000000000..d04bd4f149
--- /dev/null
+++ b/src/quick/doc/snippets/pointerHandlers/pointHandlerCanvasDrawing.qml
@@ -0,0 +1,54 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+//![0]
+import QtQuick
+
+Canvas {
+ id: canvas
+ width: 800
+ height: 600
+ antialiasing: true
+ renderTarget: Canvas.FramebufferObject
+ property var points: []
+ onPaint: {
+ if (points.length < 2)
+ return
+ var ctx = canvas.getContext('2d');
+ ctx.save()
+ ctx.strokeStyle = stylusHandler.active ? "blue" : "white"
+ ctx.lineCap = "round"
+ ctx.beginPath()
+ ctx.moveTo(points[0].x, points[0].y)
+ for (var i = 1; i < points.length; i++)
+ ctx.lineTo(points[i].x, points[i].y)
+ ctx.lineWidth = 3
+ ctx.stroke()
+ points = points.slice(points.length - 2, 1)
+ ctx.restore()
+ }
+
+ PointHandler {
+ id: stylusHandler
+ acceptedPointerTypes: PointerDevice.Pen
+ onPointChanged: {
+ canvas.points.push(point.position)
+ canvas.requestPaint()
+ }
+ }
+
+ PointHandler {
+ id: eraserHandler
+ acceptedPointerTypes: PointerDevice.Eraser
+ onPointChanged: {
+ canvas.points.push(point.position)
+ canvas.requestPaint()
+ }
+ }
+
+ Rectangle {
+ width: 10; height: 10
+ color: stylusHandler.active ? "green" : eraserHandler.active ? "red" : "beige"
+ }
+}
+//![0]
diff --git a/src/quick/doc/snippets/pointerHandlers/pointHandlerMargin.qml b/src/quick/doc/snippets/pointerHandlers/pointHandlerMargin.qml
new file mode 100644
index 0000000000..1b66531dbf
--- /dev/null
+++ b/src/quick/doc/snippets/pointerHandlers/pointHandlerMargin.qml
@@ -0,0 +1,36 @@
+// Copyright (C) 2023 Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+//![0]
+import QtQuick
+
+Item {
+ width: 480; height: 320
+
+ Rectangle {
+ anchors.fill: handlingContainer
+ anchors.margins: -handler.margin
+ color: "beige"
+ }
+
+ Rectangle {
+ id: handlingContainer
+ width: 200; height: 200
+ anchors.centerIn: parent
+ border.color: "green"
+ color: handler.active ? "lightsteelblue" : "khaki"
+
+ Text {
+ text: "X"
+ x: handler.point.position.x - width / 2
+ y: handler.point.position.y - height / 2
+ visible: handler.active
+ }
+
+ PointHandler {
+ id: handler
+ margin: 30
+ }
+ }
+
+}
+//![0]
diff --git a/src/quick/doc/snippets/pointerHandlers/tapHandlerButtonReleaseWithinBounds.qml b/src/quick/doc/snippets/pointerHandlers/tapHandlerButtonReleaseWithinBounds.qml
new file mode 100644
index 0000000000..3e8634a605
--- /dev/null
+++ b/src/quick/doc/snippets/pointerHandlers/tapHandlerButtonReleaseWithinBounds.qml
@@ -0,0 +1,51 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+//![0]
+import QtQuick
+
+Item {
+ width: 120; height: 80
+
+ component Button : Rectangle {
+ id: button
+ signal clicked
+ property alias text: buttonLabel.text
+
+ width: 80
+ height: 40
+ radius: 3
+ property color dark: Qt.darker(palette.button, 1.3)
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: tapHandler.pressed ? dark : palette.button }
+ GradientStop { position: 1.0; color: dark }
+ }
+
+ SequentialAnimation on border.width {
+ id: tapFlash
+ running: false
+ loops: 3
+ PropertyAction { value: 2 }
+ PauseAnimation { duration: 100 }
+ PropertyAction { value: 0 }
+ PauseAnimation { duration: 100 }
+ }
+
+ //![1]
+ TapHandler {
+ id: tapHandler
+ gesturePolicy: TapHandler.ReleaseWithinBounds
+ onTapped: tapFlash.start()
+ }
+ //![1]
+
+ Text {
+ id: buttonLabel
+ text: "Click Me"
+ color: palette.buttonText
+ anchors.centerIn: parent
+ }
+ }
+
+ Button { x: 10; y: 10 }
+}
+//![0]
diff --git a/src/quick/doc/snippets/pointerHandlers/tapHandlerButtonWithinBounds.qml b/src/quick/doc/snippets/pointerHandlers/tapHandlerButtonWithinBounds.qml
new file mode 100644
index 0000000000..4563b44014
--- /dev/null
+++ b/src/quick/doc/snippets/pointerHandlers/tapHandlerButtonWithinBounds.qml
@@ -0,0 +1,51 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+//![0]
+import QtQuick
+
+Item {
+ width: 120; height: 80
+
+ component Button : Rectangle {
+ id: button
+ signal clicked
+ property alias text: buttonLabel.text
+
+ width: 80
+ height: 40
+ radius: 3
+ property color dark: Qt.darker(palette.button, 1.3)
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: tapHandler.pressed ? dark : palette.button }
+ GradientStop { position: 1.0; color: dark }
+ }
+
+ SequentialAnimation on border.width {
+ id: tapFlash
+ running: false
+ loops: 3
+ PropertyAction { value: 2 }
+ PauseAnimation { duration: 100 }
+ PropertyAction { value: 0 }
+ PauseAnimation { duration: 100 }
+ }
+
+ //![1]
+ TapHandler {
+ id: tapHandler
+ gesturePolicy: TapHandler.WithinBounds
+ onTapped: tapFlash.start()
+ }
+ //![1]
+
+ Text {
+ id: buttonLabel
+ text: "Click Me"
+ color: palette.buttonText
+ anchors.centerIn: parent
+ }
+ }
+
+ Button { x: 10; y: 10 }
+}
+//![0]
diff --git a/src/quick/doc/snippets/pointerHandlers/tapHandlerGrabChanged.qml b/src/quick/doc/snippets/pointerHandlers/tapHandlerGrabChanged.qml
new file mode 100644
index 0000000000..73631d3551
--- /dev/null
+++ b/src/quick/doc/snippets/pointerHandlers/tapHandlerGrabChanged.qml
@@ -0,0 +1,46 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+import QtQuick
+
+Rectangle {
+ width: 100
+ height: 100
+
+ //![0]
+ TapHandler {
+ gesturePolicy: TapHandler.ReleaseWithinBounds // exclusive grab on press
+ onGrabChanged:
+ (transition, eventPoint) => {
+ switch (transition) {
+ case PointerDevice.GrabExclusive:
+ console.log("took exclusive grab of point", eventPoint.id,
+ "on", eventPoint.device.name)
+ break
+ case PointerDevice.UngrabExclusive:
+ console.log("gave up exclusive grab of point", eventPoint.id,
+ "on", eventPoint.device.name)
+ break
+ case PointerDevice.CancelGrabExclusive:
+ console.log("exclusive grab of point", eventPoint.id,
+ "on", eventPoint.device.name, "has been cancelled")
+ break
+ }
+
+ switch (eventPoint.state) {
+ case EventPoint.Pressed:
+ console.log("on press @", eventPoint.position);
+ break
+ case EventPoint.Updated:
+ console.log("on update @", eventPoint.position);
+ break
+ case EventPoint.Released:
+ console.log("on release @", eventPoint.position);
+ break
+ default:
+ console.log(eventPoint.position, "state", eventPoint.state)
+ break
+ }
+ }
+ }
+ //![0]
+}
diff --git a/src/quick/doc/snippets/pointerHandlers/tapHandlerOverlappingButtons.qml b/src/quick/doc/snippets/pointerHandlers/tapHandlerOverlappingButtons.qml
new file mode 100644
index 0000000000..917c863e53
--- /dev/null
+++ b/src/quick/doc/snippets/pointerHandlers/tapHandlerOverlappingButtons.qml
@@ -0,0 +1,51 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+//![0]
+import QtQuick
+
+Item {
+ width: 120; height: 80
+
+ component Button : Rectangle {
+ signal clicked
+ property alias text: buttonLabel.text
+
+ width: 80
+ height: 40
+ radius: 3
+ property color dark: Qt.darker(palette.button, 1.3)
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: tapHandler.pressed ? dark : palette.button }
+ GradientStop { position: 1.0; color: dark }
+ }
+
+ SequentialAnimation on border.width {
+ id: tapFlash
+ running: false
+ loops: 3
+ PropertyAction { value: 2 }
+ PauseAnimation { duration: 100 }
+ PropertyAction { value: 0 }
+ PauseAnimation { duration: 100 }
+ }
+
+ //![1]
+ TapHandler {
+ id: tapHandler
+ gesturePolicy: TapHandler.DragThreshold // the default
+ onTapped: tapFlash.start()
+ }
+ //![1]
+
+ Text {
+ id: buttonLabel
+ text: "Click Me"
+ color: palette.buttonText
+ anchors.centerIn: parent
+ }
+ }
+
+ Button { x: 10; y: 10 }
+ Button { x: 30; y: 30 }
+}
+//![0]
diff --git a/src/quick/doc/snippets/pointerHandlers/wheelHandler.qml b/src/quick/doc/snippets/pointerHandlers/wheelHandler.qml
index 38be381b81..1740b7bfe0 100644
--- a/src/quick/doc/snippets/pointerHandlers/wheelHandler.qml
+++ b/src/quick/doc/snippets/pointerHandlers/wheelHandler.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.14
+import QtQuick
Rectangle {
width: 170; height: 120
diff --git a/src/quick/doc/snippets/qml/anchoranimation.qml b/src/quick/doc/snippets/qml/anchoranimation.qml
index 97a6c79e55..e6fed602ed 100644
--- a/src/quick/doc/snippets/qml/anchoranimation.qml
+++ b/src/quick/doc/snippets/qml/anchoranimation.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Item {
id: container
diff --git a/src/quick/doc/snippets/qml/anchorchanges.qml b/src/quick/doc/snippets/qml/anchorchanges.qml
index d3ee2be000..7a306e0d6b 100644
--- a/src/quick/doc/snippets/qml/anchorchanges.qml
+++ b/src/quick/doc/snippets/qml/anchorchanges.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Rectangle {
id: window
diff --git a/src/quick/doc/snippets/qml/animatedimage.qml b/src/quick/doc/snippets/qml/animatedimage.qml
index 3d46887a6c..baca3f279a 100644
--- a/src/quick/doc/snippets/qml/animatedimage.qml
+++ b/src/quick/doc/snippets/qml/animatedimage.qml
@@ -5,7 +5,7 @@
// examples/quick/imageelements/animatedimage
//! [document]
-import QtQuick 2.0
+import QtQuick
Rectangle {
width: animation.width; height: animation.height + 8
diff --git a/src/quick/doc/snippets/qml/animation.qml b/src/quick/doc/snippets/qml/animation.qml
index 24d23860da..5f10726f48 100644
--- a/src/quick/doc/snippets/qml/animation.qml
+++ b/src/quick/doc/snippets/qml/animation.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [document]
-import QtQuick 2.0
+import QtQuick
//! [parent begin]
diff --git a/src/quick/doc/snippets/qml/animators.qml b/src/quick/doc/snippets/qml/animators.qml
index 04082ba1fb..b1e929eaaf 100644
--- a/src/quick/doc/snippets/qml/animators.qml
+++ b/src/quick/doc/snippets/qml/animators.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.2
+import QtQuick
Rectangle {
diff --git a/src/quick/doc/snippets/qml/behavior.qml b/src/quick/doc/snippets/qml/behavior.qml
index 0fc6e7e289..8370863cbe 100644
--- a/src/quick/doc/snippets/qml/behavior.qml
+++ b/src/quick/doc/snippets/qml/behavior.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Rectangle {
id: rect
diff --git a/src/quick/doc/snippets/qml/borderimage/borderimage-rounded.qml b/src/quick/doc/snippets/qml/borderimage/borderimage-rounded.qml
new file mode 100644
index 0000000000..a0b9cc680b
--- /dev/null
+++ b/src/quick/doc/snippets/qml/borderimage/borderimage-rounded.qml
@@ -0,0 +1,124 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+
+Rectangle {
+ id: page
+ color: "white"
+ width: 182; height: 164
+
+//! [tiled border image]
+BorderImage {
+ anchors { fill: parent; margins: 6 }
+ border { left: 30; top: 30; right: 30; bottom: 30 }
+ horizontalTileMode: BorderImage.Round
+ verticalTileMode: BorderImage.Round
+ source: "pics/borderframe.png"
+}
+//! [tiled border image]
+
+ Rectangle {
+ anchors.fill: parent
+ anchors.margins: 5
+ color: "transparent"
+ border.color: "gray"
+
+ Rectangle {
+ x: 30; y: 0
+ width: 1; height: parent.height
+ color: "gray"
+
+ Text {
+ text: "1"
+ font.pixelSize: 9
+ color: "red"
+ anchors.right: parent.right
+ anchors.rightMargin: 1
+ y: 20
+ }
+
+ Text {
+ text: "4"
+ font.pixelSize: 9
+ color: "red"
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.right: parent.right
+ anchors.rightMargin: 1
+ }
+
+ Text {
+ text: "7"
+ font.pixelSize: 9
+ color: "red"
+ y: parent.height - 30
+ anchors.right: parent.right
+ anchors.rightMargin: 1
+ }
+ }
+
+ Rectangle {
+ x: parent.width - 30; y: 0
+ width: 1; height: parent.height
+ color: "gray"
+
+ Text {
+ text: "3"
+ font.pixelSize: 9
+ color: "red"
+ x: 1
+ y: 20
+ }
+
+ Text {
+ text: "6"
+ font.pixelSize: 9
+ color: "red"
+ x: 1
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ Text {
+ text: "9"
+ font.pixelSize: 9
+ color: "red"
+ x: 1
+ y: parent.height - 30
+ }
+ }
+
+ Text {
+ text: "5"
+ font.pixelSize: 9
+ color: "red"
+ anchors.centerIn: parent
+ }
+
+ Rectangle {
+ x: 0; y: 30
+ width: parent.width; height: 1
+ color: "gray"
+
+ Text {
+ text: "2"
+ font.pixelSize: 9
+ color: "red"
+ anchors.horizontalCenter: parent.horizontalCenter
+ y: -10
+ }
+ }
+
+ Rectangle {
+ x: 0; y: parent.height - 30
+ width: parent.width; height: 1
+ color: "gray"
+
+ Text {
+ text: "8"
+ font.pixelSize: 9
+ color: "red"
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+ }
+}
diff --git a/src/quick/doc/snippets/qml/borderimage/borderimage-scaled.qml b/src/quick/doc/snippets/qml/borderimage/borderimage-scaled.qml
index 3a763f4998..5229060435 100644
--- a/src/quick/doc/snippets/qml/borderimage/borderimage-scaled.qml
+++ b/src/quick/doc/snippets/qml/borderimage/borderimage-scaled.qml
@@ -1,16 +1,17 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
Rectangle {
id: page
color: "white"
- width: 180; height: 180
+ width: 182; height: 182
+ border.color: "gray"
//! [scaled border image]
BorderImage {
- width: 180; height: 180
+ anchors { fill: parent; margins: 1 }
border { left: 30; top: 30; right: 30; bottom: 30 }
horizontalTileMode: BorderImage.Stretch
verticalTileMode: BorderImage.Stretch
@@ -20,25 +21,98 @@ BorderImage {
Rectangle {
x: 30; y: 0
- width: 1; height: 180
+ width: 1; height: parent.height
color: "gray"
+
+ Text {
+ text: "1"
+ font.pixelSize: 9
+ color: "red"
+ anchors.right: parent.right
+ anchors.rightMargin: 1
+ y: 20
+ }
+
+ Text {
+ text: "4"
+ font.pixelSize: 9
+ color: "red"
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.right: parent.right
+ anchors.rightMargin: 1
+ }
+
+ Text {
+ text: "7"
+ font.pixelSize: 9
+ color: "red"
+ y: parent.height - 30
+ anchors.right: parent.right
+ anchors.rightMargin: 1
+ }
}
Rectangle {
- x: 150; y: 0
- width: 1; height: 180
+ x: parent.width - 30; y: 0
+ width: 1; height: parent.height
color: "gray"
+
+ Text {
+ text: "3"
+ font.pixelSize: 9
+ color: "red"
+ x: 1
+ y: 20
+ }
+
+ Text {
+ text: "6"
+ font.pixelSize: 9
+ color: "red"
+ x: 1
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ Text {
+ text: "9"
+ font.pixelSize: 9
+ color: "red"
+ x: 1
+ y: parent.height - 30
+ }
+ }
+
+ Text {
+ text: "5"
+ font.pixelSize: 9
+ color: "red"
+ anchors.centerIn: parent
}
Rectangle {
x: 0; y: 30
- width: 180; height: 1
+ width: parent.width; height: 1
color: "gray"
+
+ Text {
+ text: "2"
+ font.pixelSize: 9
+ color: "red"
+ anchors.horizontalCenter: parent.horizontalCenter
+ y: -10
+ }
}
Rectangle {
- x: 0; y: 150
- width: 180; height: 1
+ x: 0; y: parent.height - 30
+ width: parent.width; height: 1
color: "gray"
+
+ Text {
+ text: "8"
+ font.pixelSize: 9
+ color: "red"
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
}
}
diff --git a/src/quick/doc/snippets/qml/borderimage/borderimage-tiled.qml b/src/quick/doc/snippets/qml/borderimage/borderimage-tiled.qml
index 525824d364..2f7b120b7f 100644
--- a/src/quick/doc/snippets/qml/borderimage/borderimage-tiled.qml
+++ b/src/quick/doc/snippets/qml/borderimage/borderimage-tiled.qml
@@ -1,16 +1,17 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
Rectangle {
id: page
color: "white"
- width: 180; height: 180
+ width: 182; height: 182
+ border.color: "gray"
//! [tiled border image]
BorderImage {
- width: 180; height: 180
+ anchors { fill: parent; margins: 1 }
border { left: 30; top: 30; right: 30; bottom: 30 }
horizontalTileMode: BorderImage.Repeat
verticalTileMode: BorderImage.Repeat
@@ -20,25 +21,98 @@ BorderImage {
Rectangle {
x: 30; y: 0
- width: 1; height: 180
+ width: 1; height: parent.height
color: "gray"
+
+ Text {
+ text: "1"
+ font.pixelSize: 9
+ color: "red"
+ anchors.right: parent.right
+ anchors.rightMargin: 1
+ y: 20
+ }
+
+ Text {
+ text: "4"
+ font.pixelSize: 9
+ color: "red"
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.right: parent.right
+ anchors.rightMargin: 1
+ }
+
+ Text {
+ text: "7"
+ font.pixelSize: 9
+ color: "red"
+ y: parent.height - 30
+ anchors.right: parent.right
+ anchors.rightMargin: 1
+ }
}
Rectangle {
- x: 150; y: 0
- width: 1; height: 180
+ x: parent.width - 30; y: 0
+ width: 1; height: parent.height
color: "gray"
+
+ Text {
+ text: "3"
+ font.pixelSize: 9
+ color: "red"
+ x: 1
+ y: 20
+ }
+
+ Text {
+ text: "6"
+ font.pixelSize: 9
+ color: "red"
+ x: 1
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ Text {
+ text: "9"
+ font.pixelSize: 9
+ color: "red"
+ x: 1
+ y: parent.height - 30
+ }
+ }
+
+ Text {
+ text: "5"
+ font.pixelSize: 9
+ color: "red"
+ anchors.centerIn: parent
}
Rectangle {
x: 0; y: 30
- width: 180; height: 1
+ width: parent.width; height: 1
color: "gray"
+
+ Text {
+ text: "2"
+ font.pixelSize: 9
+ color: "red"
+ anchors.horizontalCenter: parent.horizontalCenter
+ y: -10
+ }
}
Rectangle {
- x: 0; y: 150
- width: 180; height: 1
+ x: 0; y: parent.height - 30
+ width: parent.width; height: 1
color: "gray"
+
+ Text {
+ text: "8"
+ font.pixelSize: 9
+ color: "red"
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
}
}
diff --git a/src/quick/doc/snippets/qml/borderimage/normal-image.qml b/src/quick/doc/snippets/qml/borderimage/normal-image.qml
index 3bcbb258f1..44ca440732 100644
--- a/src/quick/doc/snippets/qml/borderimage/normal-image.qml
+++ b/src/quick/doc/snippets/qml/borderimage/normal-image.qml
@@ -1,40 +1,48 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
Rectangle {
id: page
color: "white"
- width: 120; height: 120
+ width: 182; height: 182
//! [normal image]
Image {
source: "pics/borderframe.png"
+ anchors.centerIn: parent
}
//! [normal image]
Rectangle {
- x: 30; y: 0
- width: 1; height: 120
- color: "gray"
- }
+ width: 120; height: 120
+ color: "transparent"
+ border.color: "gray"
+ anchors.centerIn: parent
- Rectangle {
- x: 90; y: 0
- width: 1; height: 120
- color: "gray"
- }
+ Rectangle {
+ x: 30; y: 0
+ width: 1; height: 120
+ color: "gray"
+ }
- Rectangle {
- x: 0; y: 30
- width: 200; height: 1
- color: "gray"
- }
+ Rectangle {
+ x: 90; y: 0
+ width: 1; height: 120
+ color: "gray"
+ }
- Rectangle {
- x: 0; y: 90
- width: 200; height: 1
- color: "gray"
+ Rectangle {
+ x: 0; y: 30
+ width: 120; height: 1
+ color: "gray"
+ }
+
+ Rectangle {
+ x: 0; y: 90
+ width: 120; height: 1
+ color: "gray"
+ }
}
}
diff --git a/src/quick/doc/snippets/qml/borderimage/pics/borderframe.png b/src/quick/doc/snippets/qml/borderimage/pics/borderframe.png
new file mode 100644
index 0000000000..97a9452d5c
--- /dev/null
+++ b/src/quick/doc/snippets/qml/borderimage/pics/borderframe.png
Binary files differ
diff --git a/src/quick/doc/snippets/qml/borderimage/pics/borderframe.svg b/src/quick/doc/snippets/qml/borderimage/pics/borderframe.svg
new file mode 100644
index 0000000000..1f6c1f2901
--- /dev/null
+++ b/src/quick/doc/snippets/qml/borderimage/pics/borderframe.svg
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="120.00004"
+ height="120.00018"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+ sodipodi:docname="borderframe.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ version="1.1"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <defs
+ id="defs4">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : -406.18091 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="744.09448 : -406.18091 : 1"
+ inkscape:persp3d-origin="372.04724 : -581.57461 : 1"
+ id="perspective10" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ gridtolerance="10000"
+ guidetolerance="10"
+ objecttolerance="10"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1.8998663"
+ inkscape:cx="239.22736"
+ inkscape:cy="198.43502"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:window-width="2688"
+ inkscape:window-height="1698"
+ inkscape:window-x="4020"
+ inkscape:window-y="204"
+ inkscape:showpageshadow="0"
+ inkscape:pagecheckerboard="1"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:window-maximized="0">
+ <inkscape:grid
+ type="xygrid"
+ id="grid2383"
+ visible="true"
+ enabled="true"
+ spacingx="1"
+ spacingy="1"
+ originx="-93.000067"
+ originy="-75.362307" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-93.000067,-75.362307)">
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.88977;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="m 105.75595,88.1182 c 0,-11.811047 11.81103,-11.811047 11.81103,0 h 23.62207 L 153.00008,76.307153 164.81112,88.1182 h 23.62206 c 0,-11.811047 11.81104,-11.811047 11.81104,0 11.81104,0 11.81104,11.811049 0,11.811049 v 23.622101 l 11.81104,11.81105 -11.81104,11.81105 v 23.62209 c 11.81104,0 11.81104,11.81105 0,11.81105 0,11.81105 -11.81104,11.81105 -11.81104,0 h -23.62206 l -11.81104,11.81105 -11.81103,-11.81105 h -23.62207 c 0,11.81105 -11.81103,11.81105 -11.81103,0 -11.811037,0 -11.811037,-11.81105 0,-11.81105 V 147.17345 L 93.944913,135.3624 105.75595,123.55135 V 99.929249 c -11.811037,0 -11.811037,-11.811049 0,-11.811049 z"
+ id="rect2387"
+ sodipodi:nodetypes="ccccccccccccccccccccccccc" />
+ <path
+ sodipodi:nodetypes="ccccccccccccccccccccccccc"
+ id="path3160"
+ d="m 110.48036,92.84263 c 0,-10.629942 0,-9.448848 4.72442,-1e-5 l 27.16537,1e-5 10.62993,-10.629942 10.62993,10.629942 27.16538,-1e-5 c 4.72441,-9.448838 4.72441,-10.629932 4.72441,1e-5 10.62993,0 9.44883,-1e-5 0,4.724411 v 27.165419 l 10.62993,10.62995 -10.62993,10.62994 v 27.1654 c 9.44883,4.72442 10.62993,4.72443 0,4.72443 0,10.62995 0,9.44883 -4.72441,-1e-5 l -27.16538,1e-5 -10.62993,10.62995 -10.62993,-10.62995 -27.16537,-1e-5 c -4.72442,9.44884 -4.72442,10.62996 -4.72442,1e-5 -10.629933,0 -9.44883,-1e-5 0,-4.72443 v -27.1654 L 99.850427,135.36241 110.48036,124.73246 V 97.567041 c -9.44883,-4.724421 -10.629933,-4.724411 0,-4.724411 z"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:3.77954;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/src/quick/doc/snippets/qml/boundaryRule.qml b/src/quick/doc/snippets/qml/boundaryRule.qml
index 65c8585e3f..2a1ad46f0a 100644
--- a/src/quick/doc/snippets/qml/boundaryRule.qml
+++ b/src/quick/doc/snippets/qml/boundaryRule.qml
@@ -1,8 +1,8 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.14
-import Qt.labs.animation 1.0
+import QtQuick
+import Qt.labs.animation
Rectangle {
id: root
diff --git a/src/quick/doc/snippets/qml/coloranimation.qml b/src/quick/doc/snippets/qml/coloranimation.qml
index 9e0bf37ca1..698d7f886d 100644
--- a/src/quick/doc/snippets/qml/coloranimation.qml
+++ b/src/quick/doc/snippets/qml/coloranimation.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Rectangle {
width: 100; height: 100
diff --git a/src/quick/doc/snippets/qml/colors.qml b/src/quick/doc/snippets/qml/colors.qml
index b0cc9d14c9..65cf07906b 100644
--- a/src/quick/doc/snippets/qml/colors.qml
+++ b/src/quick/doc/snippets/qml/colors.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
Rectangle {
width: 160; height: 250
diff --git a/src/quick/doc/snippets/qml/column/column-transitions.qml b/src/quick/doc/snippets/qml/column/column-transitions.qml
index 80f18b629b..70d8435d07 100644
--- a/src/quick/doc/snippets/qml/column/column-transitions.qml
+++ b/src/quick/doc/snippets/qml/column/column-transitions.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//! [document]
Column {
diff --git a/src/quick/doc/snippets/qml/column/column.qml b/src/quick/doc/snippets/qml/column/column.qml
index 8df93a83d3..3f9076bd3d 100644
--- a/src/quick/doc/snippets/qml/column/column.qml
+++ b/src/quick/doc/snippets/qml/column/column.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [document]
-import QtQuick 2.0
+import QtQuick
Item {
width: 310; height: 170
diff --git a/src/quick/doc/snippets/qml/column/vertical-positioner.qml b/src/quick/doc/snippets/qml/column/vertical-positioner.qml
index 01b4743c7c..5d8850fc27 100644
--- a/src/quick/doc/snippets/qml/column/vertical-positioner.qml
+++ b/src/quick/doc/snippets/qml/column/vertical-positioner.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//! [document]
Column {
diff --git a/src/quick/doc/snippets/qml/drag.qml b/src/quick/doc/snippets/qml/drag.qml
index 7205ad2114..2cc57c38b3 100644
--- a/src/quick/doc/snippets/qml/drag.qml
+++ b/src/quick/doc/snippets/qml/drag.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Item {
width: 200; height: 200
diff --git a/src/quick/doc/snippets/qml/example.md b/src/quick/doc/snippets/qml/example.md
new file mode 100644
index 0000000000..43956c7830
--- /dev/null
+++ b/src/quick/doc/snippets/qml/example.md
@@ -0,0 +1,146 @@
+# Markdown in Qt Quick
+
+The Text, TextEdit and TextArea items support rich text formatted in HTML.
+Since Qt 5.14, they now support two dialects of Markdown as well:
+[The CommonMark Specification](https://spec.commonmark.org/0.29/) is the
+conservative formal specification, while
+[GitHub Flavored Markdown](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown)
+adds extra features such as task lists and tables.
+
+## Font and Paragraph Styles
+
+Markdown supports **bold**, *italic*, ~~strikethrough~~ and `monospace` font
+styles.
+
+> A block quote is indented according to the convention for email quoting.
+
+ A block of code;
+ can be indented;
+ with 4 spaces or a tab;
+
+or
+
+```
+Block {
+ id: code
+ CanBe {
+ wrappedBy: "triple backticks"
+ }
+}
+```
+
+Block quotes can be nested, and block quotes can include indented code blocks.
+
+In [The CommonMark Specification](https://spec.commonmark.org/0.29/)
+John MacFarlane writes:
+
+> What distinguishes Markdown from many other lightweight markup syntaxes,
+> which are often easier to write, is its readability. As Gruber writes:
+
+> > The overriding design goal for Markdown's formatting syntax is to make it
+> > as readable as possible. The idea is that a Markdown-formatted document should
+> > be publishable as-is, as plain text, without looking like it's been marked up
+> > with tags or formatting instructions. (
+> > [http://daringfireball.net/projects/markdown/](http://daringfireball.net/projects/markdown/))
+
+> The point can be illustrated by comparing a sample of AsciiDoc with an
+> equivalent sample of Markdown. Here is a sample of AsciiDoc from the AsciiDoc
+> manual:
+
+> 1. List item one.
+> +
+> List item one continued with a second paragraph followed by an
+> Indented block.
+> +
+> .................
+> $ ls *.sh
+> $ mv *.sh ~/tmp
+> .................
+> +
+> List item continued with a third paragraph.
+>
+> 2. List item two continued with an open block.
+> ...
+>
+
+## Hyperlinks
+
+Hyperlinks can be written with the link text first, and the URL immediately
+following: [Qt Assistant](http://doc.qt.io/qt-6/qtassistant-index.html)
+
+A plain url is automatically recognized: https://doc.qt.io/qt-6/qml-qtquick-text.html
+
+There are also "reference links" where the link text is first labeled
+and then the URL for the label is given elsewhere:
+[The Qt Creator Manual][creatormanual]
+
+## Images
+
+Inline images like this one ![red square](images/red.png) flow with the surrounding text.
+
+The code for including an image is just a link that starts with a bang.
+An image in its own paragraph is given its own space.
+
+## Lists
+
+Different kinds of lists can be included. Standard bullet lists can be nested,
+using different symbols for each level of the list. List items can have nested
+items such as block quotes, code blocks and images. Check boxes can be included
+to form a task list.
+
+- Disc symbols are typically used for top-level list items.
+ * Circle symbols can be used to distinguish between items in lower-level
+ lists.
+ + Square symbols provide a reasonable alternative to discs and circles.
+ * Lists can be continued...
+ * further down
+- List items can include images: ![red square](images/red.png)
+- and even nested quotes, like this:
+
+ The [Qt Documentation](https://doc.qt.io/qt-6/qml-qtquick-textedit.html#details)
+ points out that
+ > The TextEdit item displays a block of editable, formatted text.
+ >
+ > It can display both plain and rich text. For example:
+ >
+ > TextEdit {
+ > width: 240
+ > text: "<b>Hello</b> <i>World!</i>"
+ > font.family: "Helvetica"
+ > font.pointSize: 20
+ > color: "blue"
+ > focus: true
+ > }
+- List items with check boxes allow task lists to be incorporated:
+ * [ ] This task is not yet done
+ * [x] We aced this one!
+
+Ordered lists can be used for tables of contents, for example. Each number
+should end with a period or a parenthesis:
+
+1. Markdown in Qt Quick
+ 1) Font and Paragraph Styles
+ 5) Hyperlinks
+ 3) Images ![red square](images/red.png)
+ 2) Lists
+ 4) Tables
+2. Related work
+
+The list will automatically be renumbered during rendering.
+
+## Thematic Breaks
+
+A horizontal rule is possible, as in HTML:
+
+- - -
+
+## Tables
+
+One of the GitHub extensions is support for tables:
+
+| |Development Tools |Programming Techniques |Graphical User Interfaces|
+|-------------|------------------------------------|---------------------------|-------------------------|
+|9:00 - 11:00 |Introduction to Qt |||
+|11:00 - 13:00|Using Qt Creator |QML and its runtime |Layouts in Qt |
+|13:00 - 15:00|Qt Quick Designer Tutorial |Extreme Programming |Writing Custom Styles |
+|15:00 - 17:00|Qt Linguist and Internationalization| | |
diff --git a/src/quick/doc/snippets/qml/externalDragScaledImage.qml b/src/quick/doc/snippets/qml/externalDragScaledImage.qml
new file mode 100644
index 0000000000..a7069db849
--- /dev/null
+++ b/src/quick/doc/snippets/qml/externalDragScaledImage.qml
@@ -0,0 +1,28 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+//![0]
+import QtQuick
+
+Item {
+ width: 200; height: 200
+
+ Image {
+ anchors.centerIn: parent
+ source: "images/qt_logo.svg"
+ sourceSize.width: 96
+
+ Drag.dragType: Drag.Automatic
+ Drag.supportedActions: Qt.CopyAction
+ Drag.mimeData: {
+ "text/plain": "Qt Quick rocks!"
+ }
+ Drag.imageSource: "images/qt_logo.svg"
+ Drag.imageSourceSize: Qt.size(48, 35)
+ Drag.active: dragHandler.active
+
+ DragHandler {
+ id: dragHandler
+ }
+ }
+}
+//![0]
diff --git a/src/quick/doc/snippets/qml/externaldrag.qml b/src/quick/doc/snippets/qml/externaldrag.qml
index 3c504004de..723701a7a5 100644
--- a/src/quick/doc/snippets/qml/externaldrag.qml
+++ b/src/quick/doc/snippets/qml/externaldrag.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.12
+import QtQuick
Item {
width: 200; height: 200
@@ -12,7 +12,6 @@ Item {
color: "green"
radius: 5
- Drag.active: dragHandler.active
Drag.dragType: Drag.Automatic
Drag.supportedActions: Qt.CopyAction
Drag.mimeData: {
@@ -30,8 +29,11 @@ Item {
onActiveChanged:
if (active) {
parent.grabToImage(function(result) {
- parent.Drag.imageSource = result.url;
+ parent.Drag.imageSource = result.url
+ parent.Drag.active = true
})
+ } else {
+ parent.Drag.active = false
}
}
}
diff --git a/src/quick/doc/snippets/qml/flickable.qml b/src/quick/doc/snippets/qml/flickable.qml
index 0c756f76d4..7334059b9e 100644
--- a/src/quick/doc/snippets/qml/flickable.qml
+++ b/src/quick/doc/snippets/qml/flickable.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [document]
-import QtQuick 2.0
+import QtQuick
Flickable {
width: 200; height: 200
diff --git a/src/quick/doc/snippets/qml/flickableScrollbar.qml b/src/quick/doc/snippets/qml/flickableScrollbar.qml
index 3b2b10ac2b..981b725313 100644
--- a/src/quick/doc/snippets/qml/flickableScrollbar.qml
+++ b/src/quick/doc/snippets/qml/flickableScrollbar.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//![0]
Rectangle {
diff --git a/src/quick/doc/snippets/qml/flipable/flipable.qml b/src/quick/doc/snippets/qml/flipable/flipable.qml
index 0961653dc0..80821e610f 100644
--- a/src/quick/doc/snippets/qml/flipable/flipable.qml
+++ b/src/quick/doc/snippets/qml/flipable/flipable.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [0]
-import QtQuick 2.0
+import QtQuick
Flipable {
id: flipable
diff --git a/src/quick/doc/snippets/qml/flow.qml b/src/quick/doc/snippets/qml/flow.qml
index 2757dc4bcb..6d8db1e295 100644
--- a/src/quick/doc/snippets/qml/flow.qml
+++ b/src/quick/doc/snippets/qml/flow.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [document]
-import QtQuick 2.0
+import QtQuick
Rectangle {
color: "lightblue"
diff --git a/src/quick/doc/snippets/qml/focus/MyClickableWidget.qml b/src/quick/doc/snippets/qml/focus/MyClickableWidget.qml
index 5718fb0e4f..7762a937f0 100644
--- a/src/quick/doc/snippets/qml/focus/MyClickableWidget.qml
+++ b/src/quick/doc/snippets/qml/focus/MyClickableWidget.qml
@@ -1,6 +1,6 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//! [clickable in focusscope]
FocusScope {
diff --git a/src/quick/doc/snippets/qml/focus/MyWidget.qml b/src/quick/doc/snippets/qml/focus/MyWidget.qml
index f43d7af2de..cb04176880 100644
--- a/src/quick/doc/snippets/qml/focus/MyWidget.qml
+++ b/src/quick/doc/snippets/qml/focus/MyWidget.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [document]
-import QtQuick 2.0
+import QtQuick
//! [mywidget]
Rectangle {
diff --git a/src/quick/doc/snippets/qml/focus/advancedFocus.qml b/src/quick/doc/snippets/qml/focus/advancedFocus.qml
index 42ec222237..6874b8fa5b 100644
--- a/src/quick/doc/snippets/qml/focus/advancedFocus.qml
+++ b/src/quick/doc/snippets/qml/focus/advancedFocus.qml
@@ -1,6 +1,6 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//! [FocusScope delegate]
Rectangle {
diff --git a/src/quick/doc/snippets/qml/focus/basicwidget.qml b/src/quick/doc/snippets/qml/focus/basicwidget.qml
index e5f96ce4eb..377e852a1d 100644
--- a/src/quick/doc/snippets/qml/focus/basicwidget.qml
+++ b/src/quick/doc/snippets/qml/focus/basicwidget.qml
@@ -1,6 +1,6 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//! [focus true]
Rectangle {
diff --git a/src/quick/doc/snippets/qml/focus/clickablewidget.qml b/src/quick/doc/snippets/qml/focus/clickablewidget.qml
index 99df888ba8..502fa36bf8 100644
--- a/src/quick/doc/snippets/qml/focus/clickablewidget.qml
+++ b/src/quick/doc/snippets/qml/focus/clickablewidget.qml
@@ -1,6 +1,6 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//! [clickable window]
Rectangle {
diff --git a/src/quick/doc/snippets/qml/focus/myfocusscopewidget.qml b/src/quick/doc/snippets/qml/focus/myfocusscopewidget.qml
index 5dd5f3f8b4..6703d3102a 100644
--- a/src/quick/doc/snippets/qml/focus/myfocusscopewidget.qml
+++ b/src/quick/doc/snippets/qml/focus/myfocusscopewidget.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [document]
-import QtQuick 2.0
+import QtQuick
//! [widget in focusscope]
FocusScope {
diff --git a/src/quick/doc/snippets/qml/focus/rectangle.qml b/src/quick/doc/snippets/qml/focus/rectangle.qml
index 119495767e..4264673581 100644
--- a/src/quick/doc/snippets/qml/focus/rectangle.qml
+++ b/src/quick/doc/snippets/qml/focus/rectangle.qml
@@ -1,6 +1,6 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//! [simple key event]
Rectangle {
diff --git a/src/quick/doc/snippets/qml/focus/widget.qml b/src/quick/doc/snippets/qml/focus/widget.qml
index 5a0200d26c..1c44f97c4d 100644
--- a/src/quick/doc/snippets/qml/focus/widget.qml
+++ b/src/quick/doc/snippets/qml/focus/widget.qml
@@ -1,6 +1,6 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//! [window]
diff --git a/src/quick/doc/snippets/qml/font.qml b/src/quick/doc/snippets/qml/font.qml
new file mode 100644
index 0000000000..5f76d6fc30
--- /dev/null
+++ b/src/quick/doc/snippets/qml/font.qml
@@ -0,0 +1,22 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+
+Item {
+//! [text]
+ Text {
+ font.family: "Helvetica"
+ font.pointSize: 13
+ font.bold: true
+ }
+//! [text]
+
+//! [structured-value-construction]
+ readonly property font myFont: ({
+ family: "Helvetica",
+ pointSize: 13,
+ bold: true
+ })
+//! [structured-value-construction]
+}
diff --git a/src/quick/doc/snippets/qml/gradient.qml b/src/quick/doc/snippets/qml/gradient.qml
index 861af15736..39ec1d37bd 100644
--- a/src/quick/doc/snippets/qml/gradient.qml
+++ b/src/quick/doc/snippets/qml/gradient.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//![code]
Rectangle {
diff --git a/src/quick/doc/snippets/qml/grid-spacing.qml b/src/quick/doc/snippets/qml/grid-spacing.qml
index ae976f1510..763b4acfbf 100644
--- a/src/quick/doc/snippets/qml/grid-spacing.qml
+++ b/src/quick/doc/snippets/qml/grid-spacing.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [document]
-import QtQuick 2.0
+import QtQuick
Rectangle {
width: 112; height: 112
diff --git a/src/quick/doc/snippets/qml/grid/grid.qml b/src/quick/doc/snippets/qml/grid/grid.qml
index c66e32bc07..db72263ebb 100644
--- a/src/quick/doc/snippets/qml/grid/grid.qml
+++ b/src/quick/doc/snippets/qml/grid/grid.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [document]
-import QtQuick 2.0
+import QtQuick
Grid {
columns: 3
diff --git a/src/quick/doc/snippets/qml/gridview/ContactModel.qml b/src/quick/doc/snippets/qml/gridview/ContactModel.qml
index 42319725cf..a6c7f949b8 100644
--- a/src/quick/doc/snippets/qml/gridview/ContactModel.qml
+++ b/src/quick/doc/snippets/qml/gridview/ContactModel.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
ListModel {
diff --git a/src/quick/doc/snippets/qml/gridview/gridview.qml b/src/quick/doc/snippets/qml/gridview/gridview.qml
index 247865d41f..3ce3cba61d 100644
--- a/src/quick/doc/snippets/qml/gridview/gridview.qml
+++ b/src/quick/doc/snippets/qml/gridview/gridview.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![import]
-import QtQuick 2.0
+import QtQuick
//![import]
Rectangle {
diff --git a/src/quick/doc/snippets/qml/image.qml b/src/quick/doc/snippets/qml/image.qml
index f73793384c..40d85e44c6 100644
--- a/src/quick/doc/snippets/qml/image.qml
+++ b/src/quick/doc/snippets/qml/image.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [document]
-import QtQuick 2.0
+import QtQuick
Image {
source: "pics/qtlogo.png"
diff --git a/src/quick/doc/snippets/qml/images/qt-logo.png b/src/quick/doc/snippets/qml/images/qt-logo.png
new file mode 100644
index 0000000000..30c621c9c6
--- /dev/null
+++ b/src/quick/doc/snippets/qml/images/qt-logo.png
Binary files differ
diff --git a/src/quick/doc/snippets/qml/images/qt_logo.svg b/src/quick/doc/snippets/qml/images/qt_logo.svg
new file mode 100644
index 0000000000..062daff3e9
--- /dev/null
+++ b/src/quick/doc/snippets/qml/images/qt_logo.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="462pt"
+ height="339pt"
+ viewBox="0 0 462 339"
+ version="1.1"
+ id="svg2"
+>
+ <path
+ fill="#41cd52"
+ d=" M 63.50 0.00 L 462.00 0.00 L 462.00 274.79 C 440.60 296.26 419.13 317.66 397.61 339.00 L 0.00 339.00 L 0.00 63.39 C 21.08 42.18 42.34 21.13 63.50 0.00 Z"
+ id="path6"/>
+ <path
+ d=" M 122.37 71.33 C 137.50 61.32 156.21 58.79 174.00 58.95 C 190.94 59.16 208.72 62.13 222.76 72.24 C 232.96 79.41 239.59 90.48 244.01 101.93 C 251.16 120.73 253.26 141.03 253.50 161.01 C 253.53 181.13 252.62 201.69 245.96 220.86 C 241.50 233.90 233.01 245.48 221.81 253.52 C 229.87 266.58 238.09 279.54 246.15 292.60 C 236.02 297.27 225.92 301.97 215.78 306.62 C 207.15 292.38 198.56 278.11 189.90 263.89 C 178.19 265.81 166.21 265.66 154.44 264.36 C 140.34 262.67 125.97 258.37 115.09 248.88 C 106.73 241.64 101.48 231.51 97.89 221.21 C 92.01 203.79 90.43 185.25 90.16 166.97 C 90.02 147.21 91.28 127.14 97.24 108.18 C 101.85 93.92 109.48 79.69 122.37 71.33 Z"
+ id="path8"
+ fill="#ffffff"/>
+ <path
+ d=" M 294.13 70.69 C 304.73 70.68 315.33 70.68 325.93 70.69 C 325.96 84.71 325.92 98.72 325.95 112.74 C 339.50 112.76 353.05 112.74 366.60 112.75 C 366.37 121.85 366.12 130.95 365.86 140.05 C 352.32 140.08 338.79 140.04 325.25 140.07 C 325.28 163.05 325.18 186.03 325.30 209.01 C 325.56 215.30 325.42 221.94 328.19 227.75 C 330.21 232.23 335.65 233.38 340.08 233.53 C 348.43 233.50 356.77 233.01 365.12 232.86 C 365.63 241.22 366.12 249.59 366.60 257.95 C 349.99 260.74 332.56 264.08 316.06 258.86 C 309.11 256.80 302.63 252.19 299.81 245.32 C 294.76 233.63 294.35 220.62 294.13 208.07 C 294.11 185.40 294.13 162.74 294.12 140.07 C 286.73 140.05 279.34 140.08 271.95 140.05 C 271.93 130.96 271.93 121.86 271.95 112.76 C 279.34 112.73 286.72 112.77 294.11 112.74 C 294.14 98.72 294.10 84.71 294.13 70.69 Z"
+ id="path10"
+ fill="#ffffff"/>
+ <path
+ fill="#41cd52"
+ d=" M 160.51 87.70 C 170.80 86.36 181.60 86.72 191.34 90.61 C 199.23 93.73 205.93 99.84 209.47 107.58 C 214.90 119.31 216.98 132.26 218.03 145.05 C 219.17 162.07 219.01 179.25 216.66 196.17 C 215.01 206.24 212.66 216.85 205.84 224.79 C 198.92 232.76 188.25 236.18 178.01 236.98 C 167.21 237.77 155.82 236.98 146.07 231.87 C 140.38 228.84 135.55 224.09 132.73 218.27 C 129.31 211.30 127.43 203.69 126.11 196.07 C 122.13 171.91 121.17 146.91 126.61 122.89 C 128.85 113.83 132.11 104.53 138.73 97.70 C 144.49 91.85 152.51 88.83 160.51 87.70 Z"
+ id="path12"/>
+</svg>
diff --git a/src/quick/doc/snippets/qml/images/red.png b/src/quick/doc/snippets/qml/images/red.png
new file mode 100644
index 0000000000..9038fef784
--- /dev/null
+++ b/src/quick/doc/snippets/qml/images/red.png
Binary files differ
diff --git a/src/quick/doc/snippets/qml/item/childrenRect.qml b/src/quick/doc/snippets/qml/item/childrenRect.qml
index bd7261074e..750a6fa125 100644
--- a/src/quick/doc/snippets/qml/item/childrenRect.qml
+++ b/src/quick/doc/snippets/qml/item/childrenRect.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//! [local]
Item {
diff --git a/src/quick/doc/snippets/qml/item/containmentMask-circle-js.qml b/src/quick/doc/snippets/qml/item/containmentMask-circle-js.qml
index 2865c1d067..9aa8ddcc75 100644
--- a/src/quick/doc/snippets/qml/item/containmentMask-circle-js.qml
+++ b/src/quick/doc/snippets/qml/item/containmentMask-circle-js.qml
@@ -1,8 +1,8 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQml 2.12
-import QtQuick 2.12
+import QtQml
+import QtQuick
//![0]
Rectangle {
diff --git a/src/quick/doc/snippets/qml/item/containmentMask-shape.qml b/src/quick/doc/snippets/qml/item/containmentMask-shape.qml
index 607c0e148a..171437c380 100644
--- a/src/quick/doc/snippets/qml/item/containmentMask-shape.qml
+++ b/src/quick/doc/snippets/qml/item/containmentMask-shape.qml
@@ -1,8 +1,8 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.12
-import QtQuick.Shapes 1.12
+import QtQuick
+import QtQuick.Shapes
//![0]
Rectangle {
diff --git a/src/quick/doc/snippets/qml/item/itemGrab.qml b/src/quick/doc/snippets/qml/item/itemGrab.qml
index ef4430175a..dc5a52a123 100644
--- a/src/quick/doc/snippets/qml/item/itemGrab.qml
+++ b/src/quick/doc/snippets/qml/item/itemGrab.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2014 Jolla Ltd, author: <gunnar.sletta@jollamobile.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.4
+import QtQuick
Item {
width: 320
@@ -12,12 +12,13 @@ Rectangle {
id: sourceRectangle
width: 100
height: 100
+ focus: true
gradient: Gradient {
GradientStop { position: 0; color: "steelblue" }
GradientStop { position: 1; color: "black" }
}
- Component.onCompleted: {
+ Keys.onSpacePressed: {
sourceRectangle.grabToImage(function(result) {
result.saveToFile("something.png")
})
@@ -30,7 +31,7 @@ Image {
id: image
}
-Component.onCompleted: {
+Keys.onSpacePressed: {
sourceRectangle.grabToImage(function(result) {
image.source = result.url
}, Qt.size(50, 50))
diff --git a/src/quick/doc/snippets/qml/keynavigation.qml b/src/quick/doc/snippets/qml/keynavigation.qml
index 9536cf85f2..498d53e1c7 100644
--- a/src/quick/doc/snippets/qml/keynavigation.qml
+++ b/src/quick/doc/snippets/qml/keynavigation.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Grid {
width: 100; height: 100
diff --git a/src/quick/doc/snippets/qml/keys/keys-handler.qml b/src/quick/doc/snippets/qml/keys/keys-handler.qml
index 00d7ca7fed..3b63f0ac0a 100644
--- a/src/quick/doc/snippets/qml/keys/keys-handler.qml
+++ b/src/quick/doc/snippets/qml/keys/keys-handler.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
Item {
width: 400; height: 400
diff --git a/src/quick/doc/snippets/qml/keys/keys-pressed.qml b/src/quick/doc/snippets/qml/keys/keys-pressed.qml
index fe27486b30..ddc4a239c7 100644
--- a/src/quick/doc/snippets/qml/keys/keys-pressed.qml
+++ b/src/quick/doc/snippets/qml/keys/keys-pressed.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
Item {
width: 400; height: 400
diff --git a/src/quick/doc/snippets/qml/layerblending.qml b/src/quick/doc/snippets/qml/layerblending.qml
index 0e3c628585..4f174360bf 100644
--- a/src/quick/doc/snippets/qml/layerblending.qml
+++ b/src/quick/doc/snippets/qml/layerblending.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2014 Gunnar Sletta <gunnar@sletta.org>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
Item {
width: 300
diff --git a/src/quick/doc/snippets/qml/layout-simple.qml b/src/quick/doc/snippets/qml/layout-simple.qml
index ef707b3337..4c972a9441 100644
--- a/src/quick/doc/snippets/qml/layout-simple.qml
+++ b/src/quick/doc/snippets/qml/layout-simple.qml
@@ -1,9 +1,9 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.9
-import QtQuick.Layouts 1.2
-import QtQuick.Window 2.2
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Window
//! [1]
Window {
diff --git a/src/quick/doc/snippets/qml/layoutmirroring.qml b/src/quick/doc/snippets/qml/layoutmirroring.qml
index 6ac975c1de..0c7154e0a1 100644
--- a/src/quick/doc/snippets/qml/layoutmirroring.qml
+++ b/src/quick/doc/snippets/qml/layoutmirroring.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Rectangle {
LayoutMirroring.enabled: true
diff --git a/src/quick/doc/snippets/qml/listview-decorations.qml b/src/quick/doc/snippets/qml/listview-decorations.qml
index 3c5c3bcfc1..af8d2bdcb0 100644
--- a/src/quick/doc/snippets/qml/listview-decorations.qml
+++ b/src/quick/doc/snippets/qml/listview-decorations.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [document]
-import QtQuick 2.0
+import QtQuick
//! [parent begin]
Rectangle {
@@ -24,7 +24,8 @@ ListModel {
Component {
id: nameDelegate
Text {
- text: name;
+ required property string name
+ text: name
font.pixelSize: 24
}
}
diff --git a/src/quick/doc/snippets/qml/listview-sections.qml b/src/quick/doc/snippets/qml/listview-sections.qml
index f6c4159fd7..54fc4dd8c8 100644
--- a/src/quick/doc/snippets/qml/listview-sections.qml
+++ b/src/quick/doc/snippets/qml/listview-sections.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [document]
-import QtQuick 2.0
+import QtQuick
//! [parent begin]
Rectangle {
diff --git a/src/quick/doc/snippets/qml/listview.qml b/src/quick/doc/snippets/qml/listview.qml
index 028a859fed..948249f849 100644
--- a/src/quick/doc/snippets/qml/listview.qml
+++ b/src/quick/doc/snippets/qml/listview.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [document]
-import QtQuick 2.0
+import QtQuick
//! [parent begin]
Rectangle {
diff --git a/src/quick/doc/snippets/qml/listview/ContactModel.qml b/src/quick/doc/snippets/qml/listview/ContactModel.qml
index bf4662581f..8ea6bf42aa 100644
--- a/src/quick/doc/snippets/qml/listview/ContactModel.qml
+++ b/src/quick/doc/snippets/qml/listview/ContactModel.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
ListModel {
ListElement {
diff --git a/src/quick/doc/snippets/qml/listview/ReusableDelegate.qml b/src/quick/doc/snippets/qml/listview/ReusableDelegate.qml
index bed0df639f..e8e1077509 100644
--- a/src/quick/doc/snippets/qml/listview/ReusableDelegate.qml
+++ b/src/quick/doc/snippets/qml/listview/ReusableDelegate.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.15
+import QtQuick
//![0]
Component {
diff --git a/src/quick/doc/snippets/qml/listview/listview.qml b/src/quick/doc/snippets/qml/listview/listview.qml
index cd8b4c1962..c8df8e727b 100644
--- a/src/quick/doc/snippets/qml/listview/listview.qml
+++ b/src/quick/doc/snippets/qml/listview/listview.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![import]
-import QtQuick 2.0
+import QtQuick
//![import]
Item {
diff --git a/src/quick/doc/snippets/qml/loader/KeyReader.qml b/src/quick/doc/snippets/qml/loader/KeyReader.qml
index 1f20b16291..2bb1c270e6 100644
--- a/src/quick/doc/snippets/qml/loader/KeyReader.qml
+++ b/src/quick/doc/snippets/qml/loader/KeyReader.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Item {
Item {
diff --git a/src/quick/doc/snippets/qml/loader/MyComponent.qml b/src/quick/doc/snippets/qml/loader/MyComponent.qml
index 06ec24dde5..6554648387 100644
--- a/src/quick/doc/snippets/qml/loader/MyComponent.qml
+++ b/src/quick/doc/snippets/qml/loader/MyComponent.qml
@@ -1,6 +1,6 @@
// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
Text { text: index }
diff --git a/src/quick/doc/snippets/qml/loader/MyItem.qml b/src/quick/doc/snippets/qml/loader/MyItem.qml
index dfc77c5c50..2385680d80 100644
--- a/src/quick/doc/snippets/qml/loader/MyItem.qml
+++ b/src/quick/doc/snippets/qml/loader/MyItem.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Rectangle {
id: myItem
diff --git a/src/quick/doc/snippets/qml/loader/connections.qml b/src/quick/doc/snippets/qml/loader/connections.qml
index 7e49094b2c..fca17479ba 100644
--- a/src/quick/doc/snippets/qml/loader/connections.qml
+++ b/src/quick/doc/snippets/qml/loader/connections.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Item {
width: 100; height: 100
diff --git a/src/quick/doc/snippets/qml/loader/creationContext1.qml b/src/quick/doc/snippets/qml/loader/creationContext1.qml
index 9a19cea82a..91a4051d9d 100644
--- a/src/quick/doc/snippets/qml/loader/creationContext1.qml
+++ b/src/quick/doc/snippets/qml/loader/creationContext1.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//![0]
Item {
diff --git a/src/quick/doc/snippets/qml/loader/creationContext2.qml b/src/quick/doc/snippets/qml/loader/creationContext2.qml
index 26c911dd5c..846b9de317 100644
--- a/src/quick/doc/snippets/qml/loader/creationContext2.qml
+++ b/src/quick/doc/snippets/qml/loader/creationContext2.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
Item {
width: 400
diff --git a/src/quick/doc/snippets/qml/loader/creationContext3.qml b/src/quick/doc/snippets/qml/loader/creationContext3.qml
index 3bc6439071..e9d47f1e18 100644
--- a/src/quick/doc/snippets/qml/loader/creationContext3.qml
+++ b/src/quick/doc/snippets/qml/loader/creationContext3.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
Item {
width: 400
diff --git a/src/quick/doc/snippets/qml/loader/creationContext4.qml b/src/quick/doc/snippets/qml/loader/creationContext4.qml
index 3b10ff2894..fdbeacbfa1 100644
--- a/src/quick/doc/snippets/qml/loader/creationContext4.qml
+++ b/src/quick/doc/snippets/qml/loader/creationContext4.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//![0]
Item {
diff --git a/src/quick/doc/snippets/qml/loader/focus.qml b/src/quick/doc/snippets/qml/loader/focus.qml
index 6f8adc25a2..724b6f6094 100644
--- a/src/quick/doc/snippets/qml/loader/focus.qml
+++ b/src/quick/doc/snippets/qml/loader/focus.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Rectangle {
width: 200; height: 200
diff --git a/src/quick/doc/snippets/qml/loader/simple.qml b/src/quick/doc/snippets/qml/loader/simple.qml
index 48080be758..f8d2249ac7 100644
--- a/src/quick/doc/snippets/qml/loader/simple.qml
+++ b/src/quick/doc/snippets/qml/loader/simple.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Item {
width: 200; height: 200
diff --git a/src/quick/doc/snippets/qml/loader/sizeitem.qml b/src/quick/doc/snippets/qml/loader/sizeitem.qml
index 3252d0201e..f0cf7204c2 100644
--- a/src/quick/doc/snippets/qml/loader/sizeitem.qml
+++ b/src/quick/doc/snippets/qml/loader/sizeitem.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Item {
width: 200; height: 200
diff --git a/src/quick/doc/snippets/qml/loader/sizeloader.qml b/src/quick/doc/snippets/qml/loader/sizeloader.qml
index 4229df9127..8d13def3ba 100644
--- a/src/quick/doc/snippets/qml/loader/sizeloader.qml
+++ b/src/quick/doc/snippets/qml/loader/sizeloader.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Item {
width: 200; height: 200
diff --git a/src/quick/doc/snippets/qml/localstorage/hello.qml b/src/quick/doc/snippets/qml/localstorage/hello.qml
index bfb2a8c67e..915667848b 100644
--- a/src/quick/doc/snippets/qml/localstorage/hello.qml
+++ b/src/quick/doc/snippets/qml/localstorage/hello.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Rectangle {
color: "white"
diff --git a/src/quick/doc/snippets/qml/models/views-models-delegates.qml b/src/quick/doc/snippets/qml/models/views-models-delegates.qml
index 4d97ae2cf4..8a2f0519d6 100644
--- a/src/quick/doc/snippets/qml/models/views-models-delegates.qml
+++ b/src/quick/doc/snippets/qml/models/views-models-delegates.qml
@@ -1,6 +1,6 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//! [rectangle]
Rectangle {
diff --git a/src/quick/doc/snippets/qml/mousearea/mousearea-snippet.qml b/src/quick/doc/snippets/qml/mousearea/mousearea-snippet.qml
index fe8816fc1e..d99f0efc6e 100644
--- a/src/quick/doc/snippets/qml/mousearea/mousearea-snippet.qml
+++ b/src/quick/doc/snippets/qml/mousearea/mousearea-snippet.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [document]
-import QtQuick 2.0
+import QtQuick
//! [parent begin]
Rectangle {
diff --git a/src/quick/doc/snippets/qml/mousearea/mousearea.qml b/src/quick/doc/snippets/qml/mousearea/mousearea.qml
index 4cb5e226fd..6793284080 100644
--- a/src/quick/doc/snippets/qml/mousearea/mousearea.qml
+++ b/src/quick/doc/snippets/qml/mousearea/mousearea.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [import]
-import QtQuick 2.0
+import QtQuick
//! [import]
Rectangle {
diff --git a/src/quick/doc/snippets/qml/mousearea/mouseareadragfilter.qml b/src/quick/doc/snippets/qml/mousearea/mouseareadragfilter.qml
index db8772bbb8..75b14530d1 100644
--- a/src/quick/doc/snippets/qml/mousearea/mouseareadragfilter.qml
+++ b/src/quick/doc/snippets/qml/mousearea/mouseareadragfilter.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [dragfilter]
-import QtQuick 2.0
+import QtQuick
Rectangle {
width: 480
diff --git a/src/quick/doc/snippets/qml/multipointtoucharea/multipointtoucharea.qml b/src/quick/doc/snippets/qml/multipointtoucharea/multipointtoucharea.qml
index 1cab349f3a..dff1326cdb 100644
--- a/src/quick/doc/snippets/qml/multipointtoucharea/multipointtoucharea.qml
+++ b/src/quick/doc/snippets/qml/multipointtoucharea/multipointtoucharea.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Rectangle {
width: 400; height: 400
diff --git a/src/quick/doc/snippets/qml/nestedWindowTransientParent.qml b/src/quick/doc/snippets/qml/nestedWindowTransientParent.qml
new file mode 100644
index 0000000000..51f076172d
--- /dev/null
+++ b/src/quick/doc/snippets/qml/nestedWindowTransientParent.qml
@@ -0,0 +1,23 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+
+//![0]
+Window {
+ // visible is false by default
+ Window {
+ transientParent: null
+ visible: true
+ }
+//![0]
+
+ id: outer
+ Timer {
+ interval: 2000
+ running: true
+ onTriggered: outer.visible = true
+ }
+//![1]
+}
+//![1]
diff --git a/src/quick/doc/snippets/qml/numberanimation.qml b/src/quick/doc/snippets/qml/numberanimation.qml
index 6ad18388d7..28de4c7ba3 100644
--- a/src/quick/doc/snippets/qml/numberanimation.qml
+++ b/src/quick/doc/snippets/qml/numberanimation.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Rectangle {
width: 100; height: 100
diff --git a/src/quick/doc/snippets/qml/parallelanimation.qml b/src/quick/doc/snippets/qml/parallelanimation.qml
index 95b45da4c5..1ffcd75e9d 100644
--- a/src/quick/doc/snippets/qml/parallelanimation.qml
+++ b/src/quick/doc/snippets/qml/parallelanimation.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Rectangle {
id: rect
diff --git a/src/quick/doc/snippets/qml/parentanimation.qml b/src/quick/doc/snippets/qml/parentanimation.qml
index ea66881196..ced59b6842 100644
--- a/src/quick/doc/snippets/qml/parentanimation.qml
+++ b/src/quick/doc/snippets/qml/parentanimation.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Item {
width: 200; height: 100
diff --git a/src/quick/doc/snippets/qml/parentchange.qml b/src/quick/doc/snippets/qml/parentchange.qml
index f3d7623185..4b04219f52 100644
--- a/src/quick/doc/snippets/qml/parentchange.qml
+++ b/src/quick/doc/snippets/qml/parentchange.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Item {
width: 200; height: 100
diff --git a/src/quick/doc/snippets/qml/path/arcdirection.qml b/src/quick/doc/snippets/qml/path/arcdirection.qml
index eec544f65b..ef7a68d6d2 100644
--- a/src/quick/doc/snippets/qml/path/arcdirection.qml
+++ b/src/quick/doc/snippets/qml/path/arcdirection.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//![0]
Path {
startX: 50; startY: 50
diff --git a/src/quick/doc/snippets/qml/path/arcradius.qml b/src/quick/doc/snippets/qml/path/arcradius.qml
index cf7e4226e4..c303f8fa7f 100644
--- a/src/quick/doc/snippets/qml/path/arcradius.qml
+++ b/src/quick/doc/snippets/qml/path/arcradius.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//![0]
Path {
startX: 0; startY: 100
diff --git a/src/quick/doc/snippets/qml/path/arcrotation.qml b/src/quick/doc/snippets/qml/path/arcrotation.qml
index dde11593ec..26ab0f4661 100644
--- a/src/quick/doc/snippets/qml/path/arcrotation.qml
+++ b/src/quick/doc/snippets/qml/path/arcrotation.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.9
+import QtQuick
//![0]
Path {
startX: 50; startY: 100
diff --git a/src/quick/doc/snippets/qml/path/basicarc.qml b/src/quick/doc/snippets/qml/path/basicarc.qml
index 56c3790424..60a04fc4ce 100644
--- a/src/quick/doc/snippets/qml/path/basicarc.qml
+++ b/src/quick/doc/snippets/qml/path/basicarc.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//![0]
Path {
startX: 100; startY: 0
diff --git a/src/quick/doc/snippets/qml/path/basiccurve.qml b/src/quick/doc/snippets/qml/path/basiccurve.qml
index 1f3e03056d..a5c521b967 100644
--- a/src/quick/doc/snippets/qml/path/basiccurve.qml
+++ b/src/quick/doc/snippets/qml/path/basiccurve.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Canvas {
width: 400; height: 200
diff --git a/src/quick/doc/snippets/qml/path/largearc.qml b/src/quick/doc/snippets/qml/path/largearc.qml
index ec457ace28..929da8e016 100644
--- a/src/quick/doc/snippets/qml/path/largearc.qml
+++ b/src/quick/doc/snippets/qml/path/largearc.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//![0]
Path {
startX: 0; startY: 100
diff --git a/src/quick/doc/snippets/qml/pathinterpolator.qml b/src/quick/doc/snippets/qml/pathinterpolator.qml
index fd693998a4..6fa0a8a4da 100644
--- a/src/quick/doc/snippets/qml/pathinterpolator.qml
+++ b/src/quick/doc/snippets/qml/pathinterpolator.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Rectangle {
width: 400
diff --git a/src/quick/doc/snippets/qml/pathview/ContactModel.qml b/src/quick/doc/snippets/qml/pathview/ContactModel.qml
index 8573899707..827184bbb2 100644
--- a/src/quick/doc/snippets/qml/pathview/ContactModel.qml
+++ b/src/quick/doc/snippets/qml/pathview/ContactModel.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
ListModel {
ListElement {
diff --git a/src/quick/doc/snippets/qml/pathview/pathattributes.qml b/src/quick/doc/snippets/qml/pathview/pathattributes.qml
index 1323a25002..7d07b76bf0 100644
--- a/src/quick/doc/snippets/qml/pathview/pathattributes.qml
+++ b/src/quick/doc/snippets/qml/pathview/pathattributes.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [0]
-import QtQuick 2.0
+import QtQuick
Rectangle {
width: 240; height: 200
diff --git a/src/quick/doc/snippets/qml/pathview/pathview.qml b/src/quick/doc/snippets/qml/pathview/pathview.qml
index 4ee4e8ff83..2a801e5d7c 100644
--- a/src/quick/doc/snippets/qml/pathview/pathview.qml
+++ b/src/quick/doc/snippets/qml/pathview/pathview.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [0]
-import QtQuick 2.0
+import QtQuick
Rectangle {
width: 240; height: 200
@@ -12,15 +12,20 @@ Rectangle {
id: delegate
Column {
id: wrapper
+
+ required property url icon
+ required property string name
+
opacity: PathView.isCurrentItem ? 1 : 0.5
+
Image {
anchors.horizontalCenter: nameText.horizontalCenter
width: 64; height: 64
- source: icon
+ source: wrapper.icon
}
Text {
id: nameText
- text: name
+ text: wrapper.name
font.pointSize: 16
}
}
diff --git a/src/quick/doc/snippets/qml/propertyaction-sequential.qml b/src/quick/doc/snippets/qml/propertyaction-sequential.qml
index 9ad5f06717..c2f029e709 100644
--- a/src/quick/doc/snippets/qml/propertyaction-sequential.qml
+++ b/src/quick/doc/snippets/qml/propertyaction-sequential.qml
@@ -1,6 +1,6 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
Row {
diff --git a/src/quick/doc/snippets/qml/propertyaction.qml b/src/quick/doc/snippets/qml/propertyaction.qml
index 1c4b05facc..28c2b6e247 100644
--- a/src/quick/doc/snippets/qml/propertyaction.qml
+++ b/src/quick/doc/snippets/qml/propertyaction.qml
@@ -1,6 +1,6 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
Row {
diff --git a/src/quick/doc/snippets/qml/propertyanimation.qml b/src/quick/doc/snippets/qml/propertyanimation.qml
index f950c1eeb8..8850e72d24 100644
--- a/src/quick/doc/snippets/qml/propertyanimation.qml
+++ b/src/quick/doc/snippets/qml/propertyanimation.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
Row {
diff --git a/src/quick/doc/snippets/qml/propertychanges.qml b/src/quick/doc/snippets/qml/propertychanges.qml
index ecfaf8a3f9..621abad278 100644
--- a/src/quick/doc/snippets/qml/propertychanges.qml
+++ b/src/quick/doc/snippets/qml/propertychanges.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![import]
-import QtQuick 2.0
+import QtQuick
//![import]
Column {
diff --git a/src/quick/doc/snippets/qml/qml-data-models/dynamic-listmodel.qml b/src/quick/doc/snippets/qml/qml-data-models/dynamic-listmodel.qml
index 4bf9fccb93..eacba90716 100644
--- a/src/quick/doc/snippets/qml/qml-data-models/dynamic-listmodel.qml
+++ b/src/quick/doc/snippets/qml/qml-data-models/dynamic-listmodel.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
Item {
width: 200; height: 250
@@ -15,8 +15,12 @@ Item {
anchors.fill: parent
model: fruitModel
delegate: Row {
- Text { text: "Fruit: " + name }
- Text { text: "Cost: $" + cost }
+ id: delegate
+ required property string name
+ required property real cost
+
+ Text { text: "Fruit: " + delegate.name }
+ Text { text: "Cost: $" + delegate.cost }
}
}
//! [view]
diff --git a/src/quick/doc/snippets/qml/qml-data-models/listelements.qml b/src/quick/doc/snippets/qml/qml-data-models/listelements.qml
index bab4aec6a2..aa8e5e4833 100644
--- a/src/quick/doc/snippets/qml/qml-data-models/listelements.qml
+++ b/src/quick/doc/snippets/qml/qml-data-models/listelements.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [document]
-import QtQuick 2.0
+import QtQuick
Item {
width: 200; height: 250
@@ -31,8 +31,12 @@ Item {
anchors.fill: parent
model: fruitModel
delegate: Row {
- Text { text: "Fruit: " + name }
- Text { text: "Cost: $" + cost }
+ id: delegate
+ required property string name
+ required property real cost
+
+ Text { text: "Fruit: " + delegate.name }
+ Text { text: "Cost: $" + delegate.cost }
}
}
//! [view]
diff --git a/src/quick/doc/snippets/qml/qml-data-models/listmodel-listview-required.qml b/src/quick/doc/snippets/qml/qml-data-models/listmodel-listview-required.qml
index 040b033141..dd7c21b88e 100644
--- a/src/quick/doc/snippets/qml/qml-data-models/listmodel-listview-required.qml
+++ b/src/quick/doc/snippets/qml/qml-data-models/listmodel-listview-required.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [document]
-import QtQuick 2.0
+import QtQuick
Item {
width: 200
diff --git a/src/quick/doc/snippets/qml/qml-data-models/listmodel-listview.qml b/src/quick/doc/snippets/qml/qml-data-models/listmodel-listview.qml
index 03f3df2304..2942eaf3df 100644
--- a/src/quick/doc/snippets/qml/qml-data-models/listmodel-listview.qml
+++ b/src/quick/doc/snippets/qml/qml-data-models/listmodel-listview.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [document]
-import QtQuick 2.0
+import QtQuick
Item {
width: 200; height: 250
diff --git a/src/quick/doc/snippets/qml/qml-extending-types/components/Button.qml b/src/quick/doc/snippets/qml/qml-extending-types/components/Button.qml
index 2ce2669ab3..c317be5f2f 100644
--- a/src/quick/doc/snippets/qml/qml-extending-types/components/Button.qml
+++ b/src/quick/doc/snippets/qml/qml-extending-types/components/Button.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
// Button.qml
-import QtQuick 2.0
+import QtQuick
Rectangle {
width: 100; height: 100
diff --git a/src/quick/doc/snippets/qml/qml-extending-types/components/application.qml b/src/quick/doc/snippets/qml/qml-extending-types/components/application.qml
index dedc28b3f3..e6572c542f 100644
--- a/src/quick/doc/snippets/qml/qml-extending-types/components/application.qml
+++ b/src/quick/doc/snippets/qml/qml-extending-types/components/application.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
// application.qml
-import QtQuick 2.0
+import QtQuick
Column {
Button { width: 50; height: 50 }
diff --git a/src/quick/doc/snippets/qml/qml-extending-types/methods/app.qml b/src/quick/doc/snippets/qml/qml-extending-types/methods/app.qml
index 14221c8904..f5b7714c1a 100644
--- a/src/quick/doc/snippets/qml/qml-extending-types/methods/app.qml
+++ b/src/quick/doc/snippets/qml/qml-extending-types/methods/app.qml
@@ -1,6 +1,6 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//![0]
Rectangle {
id: rect
diff --git a/src/quick/doc/snippets/qml/qml-extending-types/properties/ImageViewer.qml b/src/quick/doc/snippets/qml/qml-extending-types/properties/ImageViewer.qml
index 29567389c6..5d48114c0b 100644
--- a/src/quick/doc/snippets/qml/qml-extending-types/properties/ImageViewer.qml
+++ b/src/quick/doc/snippets/qml/qml-extending-types/properties/ImageViewer.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
// ImageViewer.qml
-import QtQuick 2.0
+import QtQuick
Item {
id: item
diff --git a/src/quick/doc/snippets/qml/qml-extending-types/properties/alias-override.qml b/src/quick/doc/snippets/qml/qml-extending-types/properties/alias-override.qml
index 9af9879839..f214e55dc9 100644
--- a/src/quick/doc/snippets/qml/qml-extending-types/properties/alias-override.qml
+++ b/src/quick/doc/snippets/qml/qml-extending-types/properties/alias-override.qml
@@ -1,6 +1,6 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//![0]
Rectangle {
property alias color: childRect.color
diff --git a/src/quick/doc/snippets/qml/qml-extending-types/properties/alias.qml b/src/quick/doc/snippets/qml/qml-extending-types/properties/alias.qml
index 03e288b6e3..c6adac69fa 100644
--- a/src/quick/doc/snippets/qml/qml-extending-types/properties/alias.qml
+++ b/src/quick/doc/snippets/qml/qml-extending-types/properties/alias.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
// Button.qml
-import QtQuick 2.0
+import QtQuick
Item {
property alias buttonText: textItem.text
diff --git a/src/quick/doc/snippets/qml/qml-extending-types/properties/alias/ImageViewer.qml b/src/quick/doc/snippets/qml/qml-extending-types/properties/alias/ImageViewer.qml
index 32db5bc230..2fbb13e927 100644
--- a/src/quick/doc/snippets/qml/qml-extending-types/properties/alias/ImageViewer.qml
+++ b/src/quick/doc/snippets/qml/qml-extending-types/properties/alias/ImageViewer.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
// ImageViewer.qml
-import QtQuick 2.0
+import QtQuick
Item {
id: item
diff --git a/src/quick/doc/snippets/qml/qml-extending-types/properties/alias/application.qml b/src/quick/doc/snippets/qml/qml-extending-types/properties/alias/application.qml
index 4b827450c4..697a07062d 100644
--- a/src/quick/doc/snippets/qml/qml-extending-types/properties/alias/application.qml
+++ b/src/quick/doc/snippets/qml/qml-extending-types/properties/alias/application.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
// application.qml
-import QtQuick 2.0
+import QtQuick
ImageViewer {
id: viewer
diff --git a/src/quick/doc/snippets/qml/qml-extending-types/properties/application.qml b/src/quick/doc/snippets/qml/qml-extending-types/properties/application.qml
index f880f2d555..4cda04660e 100644
--- a/src/quick/doc/snippets/qml/qml-extending-types/properties/application.qml
+++ b/src/quick/doc/snippets/qml/qml-extending-types/properties/application.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
ImageViewer {
id: viewer
diff --git a/src/quick/doc/snippets/qml/qml-extending-types/properties/property-signals.qml b/src/quick/doc/snippets/qml/qml-extending-types/properties/property-signals.qml
index faa6dfb51e..23a48c4d9f 100644
--- a/src/quick/doc/snippets/qml/qml-extending-types/properties/property-signals.qml
+++ b/src/quick/doc/snippets/qml/qml-extending-types/properties/property-signals.qml
@@ -1,6 +1,6 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//![0]
Item {
property int myNumber
diff --git a/src/quick/doc/snippets/qml/qml-extending-types/signals/Button.qml b/src/quick/doc/snippets/qml/qml-extending-types/signals/Button.qml
index f37b88ea0a..e53526f16f 100644
--- a/src/quick/doc/snippets/qml/qml-extending-types/signals/Button.qml
+++ b/src/quick/doc/snippets/qml/qml-extending-types/signals/Button.qml
@@ -1,6 +1,6 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//![0]
// Button.qml
diff --git a/src/quick/doc/snippets/qml/qml-extending-types/signals/basic.qml b/src/quick/doc/snippets/qml/qml-extending-types/signals/basic.qml
index b6fd62ce66..7bf81cc227 100644
--- a/src/quick/doc/snippets/qml/qml-extending-types/signals/basic.qml
+++ b/src/quick/doc/snippets/qml/qml-extending-types/signals/basic.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
// Button.qml
-import QtQuick 2.0
+import QtQuick
Rectangle {
id: rect
diff --git a/src/quick/doc/snippets/qml/qml-extending-types/signals/connectdynamic.qml b/src/quick/doc/snippets/qml/qml-extending-types/signals/connectdynamic.qml
index a481990144..5e6469a44b 100644
--- a/src/quick/doc/snippets/qml/qml-extending-types/signals/connectdynamic.qml
+++ b/src/quick/doc/snippets/qml/qml-extending-types/signals/connectdynamic.qml
@@ -1,6 +1,6 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//![0]
Item {
id: item
diff --git a/src/quick/doc/snippets/qml/qml-extending-types/signals/connectslots.qml b/src/quick/doc/snippets/qml/qml-extending-types/signals/connectslots.qml
index e08c16808c..6c9ad3fa03 100644
--- a/src/quick/doc/snippets/qml/qml-extending-types/signals/connectslots.qml
+++ b/src/quick/doc/snippets/qml/qml-extending-types/signals/connectslots.qml
@@ -1,6 +1,6 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//![0]
Item {
id: item
diff --git a/src/quick/doc/snippets/qml/qml-extending-types/signals/no-parameters.qml b/src/quick/doc/snippets/qml/qml-extending-types/signals/no-parameters.qml
index cc248b0c18..dca0073177 100644
--- a/src/quick/doc/snippets/qml/qml-extending-types/signals/no-parameters.qml
+++ b/src/quick/doc/snippets/qml/qml-extending-types/signals/no-parameters.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
// application.qml
-import QtQuick 2.0
+import QtQuick
Button {
width: 100; height: 100
diff --git a/src/quick/doc/snippets/qml/qml-extending-types/signals/parameters.qml b/src/quick/doc/snippets/qml/qml-extending-types/signals/parameters.qml
index 7400e84ae5..a473d9c503 100644
--- a/src/quick/doc/snippets/qml/qml-extending-types/signals/parameters.qml
+++ b/src/quick/doc/snippets/qml/qml-extending-types/signals/parameters.qml
@@ -1,6 +1,6 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//![0]
// application.qml
Button {
diff --git a/src/quick/doc/snippets/qml/rectangle/rectangle-colors.qml b/src/quick/doc/snippets/qml/rectangle/rectangle-colors.qml
index a2edc4d350..bfa106dc4a 100644
--- a/src/quick/doc/snippets/qml/rectangle/rectangle-colors.qml
+++ b/src/quick/doc/snippets/qml/rectangle/rectangle-colors.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
Item {
width: 100; height: 200
diff --git a/src/quick/doc/snippets/qml/rectangle/rectangle-gradient.qml b/src/quick/doc/snippets/qml/rectangle/rectangle-gradient.qml
index 560950f849..bdf6950189 100644
--- a/src/quick/doc/snippets/qml/rectangle/rectangle-gradient.qml
+++ b/src/quick/doc/snippets/qml/rectangle/rectangle-gradient.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
Item {
width: 100; height: 300
diff --git a/src/quick/doc/snippets/qml/rectangle/rectangle.qml b/src/quick/doc/snippets/qml/rectangle/rectangle.qml
index 14baa83bfc..552119b5e5 100644
--- a/src/quick/doc/snippets/qml/rectangle/rectangle.qml
+++ b/src/quick/doc/snippets/qml/rectangle/rectangle.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [document]
-import QtQuick 2.0
+import QtQuick
Rectangle {
width: 100
diff --git a/src/quick/doc/snippets/qml/regularexpression.qml b/src/quick/doc/snippets/qml/regularexpression.qml
index a38dec48f2..f08a55725b 100644
--- a/src/quick/doc/snippets/qml/regularexpression.qml
+++ b/src/quick/doc/snippets/qml/regularexpression.qml
@@ -1,6 +1,6 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.14
+import QtQuick
//![0]
TextInput {
id: hexNumber
diff --git a/src/quick/doc/snippets/qml/repeaters/repeater-grid-index.qml b/src/quick/doc/snippets/qml/repeaters/repeater-grid-index.qml
index 37188f2001..945641e37c 100644
--- a/src/quick/doc/snippets/qml/repeaters/repeater-grid-index.qml
+++ b/src/quick/doc/snippets/qml/repeaters/repeater-grid-index.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [document]
-import QtQuick 2.0
+import QtQuick
Rectangle {
width: 400; height: 400; color: "black"
diff --git a/src/quick/doc/snippets/qml/repeaters/repeater.qml b/src/quick/doc/snippets/qml/repeaters/repeater.qml
index cdb1f6f50a..b1cee3ea8d 100644
--- a/src/quick/doc/snippets/qml/repeaters/repeater.qml
+++ b/src/quick/doc/snippets/qml/repeaters/repeater.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [import]
-import QtQuick 2.0
+import QtQuick
//! [import]
Row {
@@ -24,7 +24,10 @@ Row {
Column {
Repeater {
model: 10
- Text { text: "I'm item " + index }
+ Text {
+ required property int index
+ text: "I'm item " + index
+ }
}
}
//! [index]
@@ -33,7 +36,10 @@ Column {
Column {
Repeater {
model: ["apples", "oranges", "pears"]
- Text { text: "Data: " + modelData }
+ Text {
+ required property string modelData
+ text: "Data: " + modelData
+ }
}
}
//! [modeldata]
diff --git a/src/quick/doc/snippets/qml/righttoleft.qml b/src/quick/doc/snippets/qml/righttoleft.qml
index fde45e6e69..dc81e88455 100644
--- a/src/quick/doc/snippets/qml/righttoleft.qml
+++ b/src/quick/doc/snippets/qml/righttoleft.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
import "righttoleft"
Column {
diff --git a/src/quick/doc/snippets/qml/righttoleft/Child.qml b/src/quick/doc/snippets/qml/righttoleft/Child.qml
index 7118a5feb0..6bedd78a62 100644
--- a/src/quick/doc/snippets/qml/righttoleft/Child.qml
+++ b/src/quick/doc/snippets/qml/righttoleft/Child.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
Rectangle {
width: 50; height: 50
diff --git a/src/quick/doc/snippets/qml/rotation.qml b/src/quick/doc/snippets/qml/rotation.qml
index d44ca5f4d6..55a6d54cbb 100644
--- a/src/quick/doc/snippets/qml/rotation.qml
+++ b/src/quick/doc/snippets/qml/rotation.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [0]
-import QtQuick 2.0
+import QtQuick
Row {
x: 10; y: 10
diff --git a/src/quick/doc/snippets/qml/rotationanimation.qml b/src/quick/doc/snippets/qml/rotationanimation.qml
index 6f3d0722c6..9e4f45b876 100644
--- a/src/quick/doc/snippets/qml/rotationanimation.qml
+++ b/src/quick/doc/snippets/qml/rotationanimation.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Item {
width: 300; height: 300
diff --git a/src/quick/doc/snippets/qml/row.qml b/src/quick/doc/snippets/qml/row.qml
index 5951ab8b8b..dd1f7cc2e8 100644
--- a/src/quick/doc/snippets/qml/row.qml
+++ b/src/quick/doc/snippets/qml/row.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [document]
-import QtQuick 2.0
+import QtQuick
Rectangle {
width: 320; height: 110
diff --git a/src/quick/doc/snippets/qml/row/row.qml b/src/quick/doc/snippets/qml/row/row.qml
index 75e8fad100..88f7aa743a 100644
--- a/src/quick/doc/snippets/qml/row/row.qml
+++ b/src/quick/doc/snippets/qml/row/row.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [document]
-import QtQuick 2.0
+import QtQuick
Row {
spacing: 2
diff --git a/src/quick/doc/snippets/qml/sequentialanimation.qml b/src/quick/doc/snippets/qml/sequentialanimation.qml
index 2098f395e7..c40d272220 100644
--- a/src/quick/doc/snippets/qml/sequentialanimation.qml
+++ b/src/quick/doc/snippets/qml/sequentialanimation.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Rectangle {
id: rect
diff --git a/src/quick/doc/snippets/qml/smoothedanimation.qml b/src/quick/doc/snippets/qml/smoothedanimation.qml
index 1cd4ee6b6f..ae9c20a7b9 100644
--- a/src/quick/doc/snippets/qml/smoothedanimation.qml
+++ b/src/quick/doc/snippets/qml/smoothedanimation.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Rectangle {
width: 800; height: 600
diff --git a/src/quick/doc/snippets/qml/splashWindow.qml b/src/quick/doc/snippets/qml/splashWindow.qml
new file mode 100644
index 0000000000..c790fa5c1f
--- /dev/null
+++ b/src/quick/doc/snippets/qml/splashWindow.qml
@@ -0,0 +1,57 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+//! [entire]
+import QtQuick
+
+Window {
+ id: mainWindow
+ title: "Main Window"
+ color: "#456"
+ property real defaultSpacing: 10
+
+ property Splash splash: Splash {
+ onTimeout: mainWindow.show()
+ }
+
+ component Splash: Window {
+ id: splash
+
+ // a splash screen has no titlebar
+ flags: Qt.SplashScreen
+ // the transparent color lets background behind the image edges show through
+ color: "transparent"
+ modality: Qt.ApplicationModal // in case another application window is showing
+ title: "Splash Window" // for the taskbar/dock, task switcher etc.
+ visible: true
+
+ // here we use the Screen attached property to center the splash window
+ //! [screen-properties]
+ x: (Screen.width - splashImage.width) / 2
+ y: (Screen.height - splashImage.height) / 2
+ //! [screen-properties]
+ width: splashImage.width
+ height: splashImage.height
+
+ property int timeoutInterval: 2000
+ signal timeout
+
+ Image {
+ id: splashImage
+ source: "images/qt-logo.png"
+ }
+
+ TapHandler {
+ onTapped: splash.timeout()
+ }
+
+ Timer {
+ interval: splash.timeoutInterval; running: true; repeat: false
+ onTriggered: {
+ splash.visible = false
+ splash.timeout()
+ }
+ }
+ }
+}
+//! [entire]
diff --git a/src/quick/doc/snippets/qml/springanimation.qml b/src/quick/doc/snippets/qml/springanimation.qml
index 6937cf8fe6..4c2c974450 100644
--- a/src/quick/doc/snippets/qml/springanimation.qml
+++ b/src/quick/doc/snippets/qml/springanimation.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Item {
width: 300; height: 300
diff --git a/src/quick/doc/snippets/qml/state-when.qml b/src/quick/doc/snippets/qml/state-when.qml
index 58e8e7dd98..98ff68594f 100644
--- a/src/quick/doc/snippets/qml/state-when.qml
+++ b/src/quick/doc/snippets/qml/state-when.qml
@@ -1,6 +1,6 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//![0]
Rectangle {
id: myRect
diff --git a/src/quick/doc/snippets/qml/state.qml b/src/quick/doc/snippets/qml/state.qml
index e3cfac72f6..c749df23b5 100644
--- a/src/quick/doc/snippets/qml/state.qml
+++ b/src/quick/doc/snippets/qml/state.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Rectangle {
id: myRect
diff --git a/src/quick/doc/snippets/qml/states.qml b/src/quick/doc/snippets/qml/states.qml
index 285f395f0b..426ce3be4f 100644
--- a/src/quick/doc/snippets/qml/states.qml
+++ b/src/quick/doc/snippets/qml/states.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![document]
-import QtQuick 2.0
+import QtQuick
//![parent begin]
Rectangle {
diff --git a/src/quick/doc/snippets/qml/states/statechangescript.qml b/src/quick/doc/snippets/qml/states/statechangescript.qml
index 074f02988d..5461861c5b 100644
--- a/src/quick/doc/snippets/qml/states/statechangescript.qml
+++ b/src/quick/doc/snippets/qml/states/statechangescript.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
Item {
//! [state and transition]
diff --git a/src/quick/doc/snippets/qml/systempalette.qml b/src/quick/doc/snippets/qml/systempalette.qml
index 102e5c28d3..c157856350 100644
--- a/src/quick/doc/snippets/qml/systempalette.qml
+++ b/src/quick/doc/snippets/qml/systempalette.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Rectangle {
SystemPalette { id: myPalette; colorGroup: SystemPalette.Active }
diff --git a/src/quick/doc/snippets/qml/tableview/cpp-tablemodel.h b/src/quick/doc/snippets/qml/tableview/cpp-tablemodel.h
index a4def6be54..1de11ed48f 100644
--- a/src/quick/doc/snippets/qml/tableview/cpp-tablemodel.h
+++ b/src/quick/doc/snippets/qml/tableview/cpp-tablemodel.h
@@ -12,7 +12,7 @@ class TableModel : public QAbstractTableModel
{
Q_OBJECT
QML_ELEMENT
- QML_ADDED_IN_MINOR_VERSION(1)
+ QML_ADDED_IN_VERSION(1, 1)
public:
int rowCount(const QModelIndex & = QModelIndex()) const override
diff --git a/src/quick/doc/snippets/qml/tableview/cpp-tablemodel.qml b/src/quick/doc/snippets/qml/tableview/cpp-tablemodel.qml
index b06783d864..6158c2f75f 100644
--- a/src/quick/doc/snippets/qml/tableview/cpp-tablemodel.qml
+++ b/src/quick/doc/snippets/qml/tableview/cpp-tablemodel.qml
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.12
+import QtQuick
import TableModel 0.1
TableView {
diff --git a/src/quick/doc/snippets/qml/tableview/editdelegate.qml b/src/quick/doc/snippets/qml/tableview/editdelegate.qml
index b320969347..c5274e5ac7 100644
--- a/src/quick/doc/snippets/qml/tableview/editdelegate.qml
+++ b/src/quick/doc/snippets/qml/tableview/editdelegate.qml
@@ -44,9 +44,9 @@ Window {
TableView.onCommit: {
display = text
- // display = text is short-hand for:
- // let index = TableView.view.modelIndex(column, row)
- // TableView.view.model.setData(index, text, Qt.DisplayRole)
+ // 'display = text' is short-hand for:
+ // let index = TableView.view.index(row, column)
+ // TableView.view.model.setData(index, "display", text)
}
}
}
diff --git a/src/quick/doc/snippets/qml/tableview/keyboard-navigation.qml b/src/quick/doc/snippets/qml/tableview/keyboard-navigation.qml
index 8af308dec5..9231b9e1a5 100644
--- a/src/quick/doc/snippets/qml/tableview/keyboard-navigation.qml
+++ b/src/quick/doc/snippets/qml/tableview/keyboard-navigation.qml
@@ -3,7 +3,7 @@
import QtQuick
import QtQuick.Controls
-import Qt.labs.qmlmodels 1.0
+import Qt.labs.qmlmodels
//![0]
ApplicationWindow {
diff --git a/src/quick/doc/snippets/qml/tableview/qml-tablemodel.qml b/src/quick/doc/snippets/qml/tableview/qml-tablemodel.qml
index be861ec28a..f0a28a4be6 100644
--- a/src/quick/doc/snippets/qml/tableview/qml-tablemodel.qml
+++ b/src/quick/doc/snippets/qml/tableview/qml-tablemodel.qml
@@ -2,8 +2,8 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.14
-import Qt.labs.qmlmodels 1.0
+import QtQuick
+import Qt.labs.qmlmodels
TableView {
anchors.fill: parent
diff --git a/src/quick/doc/snippets/qml/tableview/reusabledelegate.qml b/src/quick/doc/snippets/qml/tableview/reusabledelegate.qml
index f3ca4830dd..0f68786ac4 100644
--- a/src/quick/doc/snippets/qml/tableview/reusabledelegate.qml
+++ b/src/quick/doc/snippets/qml/tableview/reusabledelegate.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.12
+import QtQuick
//![0]
Component {
diff --git a/src/quick/doc/snippets/qml/tableview/tableviewwithheader.qml b/src/quick/doc/snippets/qml/tableview/tableviewwithheader.qml
index 08848fc1ef..40006e4e6c 100644
--- a/src/quick/doc/snippets/qml/tableview/tableviewwithheader.qml
+++ b/src/quick/doc/snippets/qml/tableview/tableviewwithheader.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.12
+import QtQuick
//![0]
TableView {
diff --git a/src/quick/doc/snippets/qml/tableview/tableviewwithprovider.qml b/src/quick/doc/snippets/qml/tableview/tableviewwithprovider.qml
index b4fbf19bec..6911d47eb5 100644
--- a/src/quick/doc/snippets/qml/tableview/tableviewwithprovider.qml
+++ b/src/quick/doc/snippets/qml/tableview/tableviewwithprovider.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.12
+import QtQuick
//![0]
TableView {
diff --git a/src/quick/doc/snippets/qml/text/onLinkActivated.qml b/src/quick/doc/snippets/qml/text/onLinkActivated.qml
index 3bb59f2f6e..25223a5d55 100644
--- a/src/quick/doc/snippets/qml/text/onLinkActivated.qml
+++ b/src/quick/doc/snippets/qml/text/onLinkActivated.qml
@@ -1,6 +1,6 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
Rectangle {
width: 700; height: 400
diff --git a/src/quick/doc/snippets/qml/text/textEditFormats.qml b/src/quick/doc/snippets/qml/text/textEditFormats.qml
index caf1a842a3..259beab672 100644
--- a/src/quick/doc/snippets/qml/text/textEditFormats.qml
+++ b/src/quick/doc/snippets/qml/text/textEditFormats.qml
@@ -1,6 +1,6 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.14
+import QtQuick
//![0]
Column {
diff --git a/src/quick/doc/snippets/qml/text/textFormats.qml b/src/quick/doc/snippets/qml/text/textFormats.qml
index c2de9b4ca8..d79bcb01bb 100644
--- a/src/quick/doc/snippets/qml/text/textFormats.qml
+++ b/src/quick/doc/snippets/qml/text/textFormats.qml
@@ -1,6 +1,6 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.14
+import QtQuick
//![0]
Column {
diff --git a/src/quick/doc/snippets/qml/textEditStatusSwitch.qml b/src/quick/doc/snippets/qml/textEditStatusSwitch.qml
new file mode 100644
index 0000000000..ad29220482
--- /dev/null
+++ b/src/quick/doc/snippets/qml/textEditStatusSwitch.qml
@@ -0,0 +1,31 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+import QtQuick
+
+//! [0]
+TextEdit {
+ id: edit
+ width: 300
+ height: 200
+ textFormat: TextEdit.MarkdownText
+ textDocument.source: "example.md"
+ wrapMode: TextEdit.WordWrap
+
+ Text {
+ anchors {
+ bottom: parent.bottom
+ right: parent.right
+ }
+ color: edit.textDocument.status === TextDocument.Loaded ? "darkolivegreen" : "tomato"
+ text:
+ switch (edit.textDocument.status) {
+ case TextDocument.Loading:
+ return qsTr("Loading ") + edit.textDocument.source
+ case TextDocument.Loaded:
+ return qsTr("Loaded ") + edit.textDocument.source
+ default:
+ return edit.textDocument.errorString
+ }
+ }
+}
+//! [0]
diff --git a/src/quick/doc/snippets/qml/texteditor.qml b/src/quick/doc/snippets/qml/texteditor.qml
index 8b667001c7..687d419ca3 100644
--- a/src/quick/doc/snippets/qml/texteditor.qml
+++ b/src/quick/doc/snippets/qml/texteditor.qml
@@ -1,6 +1,6 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//! [0]
Flickable {
diff --git a/src/quick/doc/snippets/qml/texthandling.qml b/src/quick/doc/snippets/qml/texthandling.qml
index 2aa4a97030..bf98fb71c3 100644
--- a/src/quick/doc/snippets/qml/texthandling.qml
+++ b/src/quick/doc/snippets/qml/texthandling.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [document]
-import QtQuick 2.14
+import QtQuick
//! [parent begin]
diff --git a/src/quick/doc/snippets/qml/transition-animation.qml b/src/quick/doc/snippets/qml/transition-animation.qml
new file mode 100644
index 0000000000..a0482018ad
--- /dev/null
+++ b/src/quick/doc/snippets/qml/transition-animation.qml
@@ -0,0 +1,13 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+import QtQuick
+
+
+Item {
+//![0]
+ transitions: Transition {
+ PropertyAnimation { duration: 3000 }
+ ColorAnimation { duration: 3000 }
+ }
+//![0]
+}
diff --git a/src/quick/doc/snippets/qml/transition-from-to-modified.qml b/src/quick/doc/snippets/qml/transition-from-to-modified.qml
index 2e5c074e23..1ec9edd0a1 100644
--- a/src/quick/doc/snippets/qml/transition-from-to-modified.qml
+++ b/src/quick/doc/snippets/qml/transition-from-to-modified.qml
@@ -1,6 +1,6 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
Rectangle {
id: rect
diff --git a/src/quick/doc/snippets/qml/transition-from-to.qml b/src/quick/doc/snippets/qml/transition-from-to.qml
index c282c3c16f..c9ebc3aa30 100644
--- a/src/quick/doc/snippets/qml/transition-from-to.qml
+++ b/src/quick/doc/snippets/qml/transition-from-to.qml
@@ -1,6 +1,6 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//![0]
Rectangle {
diff --git a/src/quick/doc/snippets/qml/transition-reversible.qml b/src/quick/doc/snippets/qml/transition-reversible.qml
index d5f225eb40..57acd92f89 100644
--- a/src/quick/doc/snippets/qml/transition-reversible.qml
+++ b/src/quick/doc/snippets/qml/transition-reversible.qml
@@ -1,6 +1,6 @@
// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.12
+import QtQuick
//![0]
Rectangle {
diff --git a/src/quick/doc/snippets/qml/transition.qml b/src/quick/doc/snippets/qml/transition.qml
index 97188a8181..abcb9b6e19 100644
--- a/src/quick/doc/snippets/qml/transition.qml
+++ b/src/quick/doc/snippets/qml/transition.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
-import QtQuick 2.0
+import QtQuick
Rectangle {
id: rect
diff --git a/src/quick/doc/snippets/qml/transitions-list.qml b/src/quick/doc/snippets/qml/transitions-list.qml
index de8a364744..9ad70d6ccf 100644
--- a/src/quick/doc/snippets/qml/transitions-list.qml
+++ b/src/quick/doc/snippets/qml/transitions-list.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.13
+import QtQuick
Rectangle {
id: page
diff --git a/src/quick/doc/snippets/qml/treeview/qml-customdelegate.qml b/src/quick/doc/snippets/qml/treeview/qml-customdelegate.qml
index 97574487e9..49483af572 100644
--- a/src/quick/doc/snippets/qml/treeview/qml-customdelegate.qml
+++ b/src/quick/doc/snippets/qml/treeview/qml-customdelegate.qml
@@ -1,26 +1,31 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//![0]
import QtQuick
+import QtQuick.Controls
-Window {
- width: 600
- height: 400
+ApplicationWindow {
+ width: 800
+ height: 600
visible: true
TreeView {
+ id: treeView
anchors.fill: parent
+ anchors.margins: 10
+ clip: true
+
+ selectionModel: ItemSelectionModel {}
+
// The model needs to be a QAbstractItemModel
// model: yourTreeModel
delegate: Item {
- id: treeDelegate
-
implicitWidth: padding + label.x + label.implicitWidth + padding
implicitHeight: label.implicitHeight * 1.5
- readonly property real indent: 20
+ readonly property real indentation: 20
readonly property real padding: 5
// Assigned to by TreeView:
@@ -29,24 +34,52 @@ Window {
required property bool expanded
required property int hasChildren
required property int depth
+ required property int row
+ required property int column
+ required property bool current
- TapHandler {
- onTapped: treeView.toggleExpanded(row)
+ // Rotate indicator when expanded by the user
+ // (requires TreeView to have a selectionModel)
+ property Animation indicatorAnimation: NumberAnimation {
+ target: indicator
+ property: "rotation"
+ from: expanded ? 0 : 90
+ to: expanded ? 90 : 0
+ duration: 100
+ easing.type: Easing.OutQuart
}
+ TableView.onPooled: indicatorAnimation.complete()
+ TableView.onReused: if (current) indicatorAnimation.start()
+ onExpandedChanged: indicator.rotation = expanded ? 90 : 0
- Text {
+ Rectangle {
+ id: background
+ anchors.fill: parent
+ color: row === treeView.currentRow ? palette.highlight : "black"
+ opacity: (treeView.alternatingRows && row % 2 !== 0) ? 0.3 : 0.1
+ }
+
+ Label {
id: indicator
- visible: treeDelegate.isTreeNode && treeDelegate.hasChildren
- x: padding + (treeDelegate.depth * treeDelegate.indent)
- anchors.verticalCenter: label.verticalCenter
- text: "â–¸"
- rotation: treeDelegate.expanded ? 90 : 0
+ x: padding + (depth * indentation)
+ anchors.verticalCenter: parent.verticalCenter
+ visible: isTreeNode && hasChildren
+ text: "â–¶"
+
+ TapHandler {
+ onSingleTapped: {
+ let index = treeView.index(row, column)
+ treeView.selectionModel.setCurrentIndex(index, ItemSelectionModel.NoUpdate)
+ treeView.toggleExpanded(row)
+ }
+ }
}
- Text {
+ Label {
id: label
- x: padding + (treeDelegate.isTreeNode ? (treeDelegate.depth + 1) * treeDelegate.indent : 0)
- width: treeDelegate.width - treeDelegate.padding - x
+ x: padding + (isTreeNode ? (depth + 1) * indentation : 0)
+ anchors.verticalCenter: parent.verticalCenter
+ width: parent.width - padding - x
clip: true
text: model.display
}
diff --git a/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-basic.qml b/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-basic.qml
index 5a597d2a89..f5ef24a174 100644
--- a/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-basic.qml
+++ b/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-basic.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//! [0]
ListView {
diff --git a/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-delayedbyindex.qml b/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-delayedbyindex.qml
index a12458770a..444fdd5434 100644
--- a/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-delayedbyindex.qml
+++ b/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-delayedbyindex.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
ListView {
width: 240; height: 320
diff --git a/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-intermediatemove.qml b/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-intermediatemove.qml
index 656417aba0..1f913fe5c8 100644
--- a/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-intermediatemove.qml
+++ b/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-intermediatemove.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
ListView {
width: 240; height: 320
diff --git a/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-interruptedgood.qml b/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-interruptedgood.qml
index d7e8ce60fb..79d39a49f1 100644
--- a/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-interruptedgood.qml
+++ b/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-interruptedgood.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
ListView {
width: 240; height: 320
diff --git a/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-pathanim.qml b/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-pathanim.qml
index 362aa81923..8767aab58c 100644
--- a/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-pathanim.qml
+++ b/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-pathanim.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
ListView {
width: 240; height: 320
diff --git a/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-scriptactionbad.qml b/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-scriptactionbad.qml
index faf5e76ca9..984bdc1e84 100644
--- a/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-scriptactionbad.qml
+++ b/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-scriptactionbad.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//! [0]
ListView {
diff --git a/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-scriptactiongood.qml b/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-scriptactiongood.qml
index 1cf181de3f..c0819c2103 100644
--- a/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-scriptactiongood.qml
+++ b/src/quick/doc/snippets/qml/viewtransitions/viewtransitions-scriptactiongood.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
ListView {
width: 240; height: 320
diff --git a/src/quick/doc/snippets/qml/visualparent.qml b/src/quick/doc/snippets/qml/visualparent.qml
index eeca529612..22da8a6622 100644
--- a/src/quick/doc/snippets/qml/visualparent.qml
+++ b/src/quick/doc/snippets/qml/visualparent.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//![0]
Rectangle {
diff --git a/src/quick/doc/snippets/qml/visualparent2.qml b/src/quick/doc/snippets/qml/visualparent2.qml
index 4824686273..a5957100ef 100644
--- a/src/quick/doc/snippets/qml/visualparent2.qml
+++ b/src/quick/doc/snippets/qml/visualparent2.qml
@@ -1,7 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.0
+import QtQuick
//![0]
Rectangle {
diff --git a/src/quick/doc/snippets/qml/windowActiveAttached.qml b/src/quick/doc/snippets/qml/windowActiveAttached.qml
new file mode 100644
index 0000000000..791091ef82
--- /dev/null
+++ b/src/quick/doc/snippets/qml/windowActiveAttached.qml
@@ -0,0 +1,10 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+//![entire]
+import QtQuick
+
+Text {
+ text: Window.active ? "active" : "inactive"
+}
+//![entire]
diff --git a/src/quick/doc/snippets/qml/windowPalette.qml b/src/quick/doc/snippets/qml/windowPalette.qml
new file mode 100644
index 0000000000..5a9219c638
--- /dev/null
+++ b/src/quick/doc/snippets/qml/windowPalette.qml
@@ -0,0 +1,40 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+//![entire]
+import QtQuick
+import QtQuick.Controls
+
+//![declaration-and-color]
+Window {
+ visible: true
+
+ // here we use the Window.active and Window.palette ordinary properties
+ color: active ? palette.active.window : palette.inactive.window
+//![declaration-and-color]
+
+ // colors that are not customized here come from SystemPalette
+ palette.active.window: "peachpuff"
+ palette.windowText: "brown"
+
+//![text-item]
+ Text {
+ anchors.centerIn: parent
+ // here we use the Window.active attached property and the Item.palette property
+ color: Window.active ? palette.active.windowText : palette.inactive.windowText
+ text: Window.active ? "active" : "inactive"
+ }
+//![text-item]
+
+ Button {
+ text: "Button"
+ anchors {
+ bottom: parent.bottom
+ bottomMargin: 6
+ horizontalCenter: parent.horizontalCenter
+ }
+ }
+//![closing-brace]
+}
+//![closing-brace]
+//![entire]
diff --git a/src/quick/doc/snippets/qml/windowVisibility.qml b/src/quick/doc/snippets/qml/windowVisibility.qml
new file mode 100644
index 0000000000..fcdbd5aef1
--- /dev/null
+++ b/src/quick/doc/snippets/qml/windowVisibility.qml
@@ -0,0 +1,29 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+//! [entire]
+import QtQuick
+import QtQuick.Controls
+
+Window {
+ id: win
+ flags: Qt.Window | Qt.WindowFullscreenButtonHint
+ visibility: fullscreenButton.checked ? Window.FullScreen : Window.Windowed
+
+ Button {
+ id: fullscreenButton
+ anchors {
+ right: parent.right
+ top: parent.top
+ margins: 6
+ }
+ width: height
+ checkable: true
+ Binding on checked { value: win.visibility === Window.FullScreen }
+ text: "⛶"
+ ToolTip.visible: hovered
+ ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
+ ToolTip.text: win.visibility === Window.FullScreen ? qsTr("restore") : qsTr("fill screen")
+ }
+}
+//! [entire]
diff --git a/src/quick/doc/snippets/qml/windowconstraints.qml b/src/quick/doc/snippets/qml/windowconstraints.qml
index 87424ffe79..e7cb0b747c 100644
--- a/src/quick/doc/snippets/qml/windowconstraints.qml
+++ b/src/quick/doc/snippets/qml/windowconstraints.qml
@@ -1,9 +1,9 @@
// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QtQuick 2.2
-import QtQuick.Layouts 1.2
-import QtQuick.Window 2.2
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Window
Window {
//! [binddefaultsize]
diff --git a/src/quick/doc/snippets/qmllint/config.ini b/src/quick/doc/snippets/qmllint/config.ini
deleted file mode 100644
index 29fa21438a..0000000000
--- a/src/quick/doc/snippets/qmllint/config.ini
+++ /dev/null
@@ -1,19 +0,0 @@
-[General]
-AdditionalQmlImportPaths=
-DisableDefaultImports=false
-OverwriteImportTypes=
-ResourcePath=
-
-[Warnings]
-BadSignalHandler=warning
-Deprecated=warning
-ImportFailure=warning
-InheritanceCycle=warning
-MultilineStrings=info
-PropertyAlias=warning
-RequiredProperty=warning
-TypeError=warning
-UnknownProperty=warning
-UnqualifiedAccess=warning
-UnusedImports=info
-WithStatement=warning
diff --git a/src/quick/doc/snippets/qquickrhiitem/qquickrhiitem_intro.cpp b/src/quick/doc/snippets/qquickrhiitem/qquickrhiitem_intro.cpp
new file mode 100644
index 0000000000..3a3d1585bf
--- /dev/null
+++ b/src/quick/doc/snippets/qquickrhiitem/qquickrhiitem_intro.cpp
@@ -0,0 +1,147 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QtQuick/QQuickRhiItem>
+#include <rhi/qrhi.h>
+
+//![0]
+class ExampleRhiItemRenderer : public QQuickRhiItemRenderer
+{
+public:
+ void initialize(QRhiCommandBuffer *cb) override;
+ void synchronize(QQuickRhiItem *item) override;
+ void render(QRhiCommandBuffer *cb) override;
+
+private:
+ QRhi *m_rhi = nullptr;
+ std::unique_ptr<QRhiBuffer> m_vbuf;
+ std::unique_ptr<QRhiBuffer> m_ubuf;
+ std::unique_ptr<QRhiShaderResourceBindings> m_srb;
+ std::unique_ptr<QRhiGraphicsPipeline> m_pipeline;
+ QMatrix4x4 m_viewProjection;
+ float m_angle = 0.0f;
+};
+
+class ExampleRhiItem : public QQuickRhiItem
+{
+ Q_OBJECT
+ QML_NAMED_ELEMENT(ExampleRhiItem)
+ Q_PROPERTY(float angle READ angle WRITE setAngle NOTIFY angleChanged)
+
+public:
+ QQuickRhiItemRenderer *createRenderer() override;
+
+ float angle() const { return m_angle; }
+ void setAngle(float a);
+
+signals:
+ void angleChanged();
+
+private:
+ float m_angle = 0.0f;
+};
+
+QQuickRhiItemRenderer *ExampleRhiItem::createRenderer()
+{
+ return new ExampleRhiItemRenderer;
+}
+
+void ExampleRhiItem::setAngle(float a)
+{
+ if (m_angle == a)
+ return;
+
+ m_angle = a;
+ emit angleChanged();
+ update();
+}
+
+void ExampleRhiItemRenderer::synchronize(QQuickRhiItem *rhiItem)
+{
+ ExampleRhiItem *item = static_cast<ExampleRhiItem *>(rhiItem);
+ if (item->angle() != m_angle)
+ m_angle = item->angle();
+}
+
+static QShader getShader(const QString &name)
+{
+ QFile f(name);
+ return f.open(QIODevice::ReadOnly) ? QShader::fromSerialized(f.readAll()) : QShader();
+}
+
+static float vertexData[] = {
+ 0.0f, 0.5f, 1.0f, 0.0f, 0.0f,
+ -0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
+ 0.5f, -0.5f, 0.0f, 0.0f, 1.0f,
+};
+
+void ExampleRhiItemRenderer::initialize(QRhiCommandBuffer *cb)
+{
+ if (m_rhi != rhi()) {
+ m_pipeline.reset();
+ m_rhi = rhi();
+ }
+
+ if (!m_pipeline) {
+ m_vbuf.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData)));
+ m_vbuf->create();
+
+ m_ubuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64));
+ m_ubuf->create();
+
+ m_srb.reset(m_rhi->newShaderResourceBindings());
+ m_srb->setBindings({
+ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage, m_ubuf.get()),
+ });
+ m_srb->create();
+
+ m_pipeline.reset(m_rhi->newGraphicsPipeline());
+ m_pipeline->setShaderStages({
+ { QRhiShaderStage::Vertex, getShader(QLatin1String(":/shaders/color.vert.qsb")) },
+ { QRhiShaderStage::Fragment, getShader(QLatin1String(":/shaders/color.frag.qsb")) }
+ });
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({
+ { 5 * sizeof(float) }
+ });
+ inputLayout.setAttributes({
+ { 0, 0, QRhiVertexInputAttribute::Float2, 0 },
+ { 0, 1, QRhiVertexInputAttribute::Float3, 2 * sizeof(float) }
+ });
+ m_pipeline->setVertexInputLayout(inputLayout);
+ m_pipeline->setShaderResourceBindings(m_srb.get());
+ m_pipeline->setRenderPassDescriptor(renderTarget()->renderPassDescriptor());
+ m_pipeline->create();
+
+ QRhiResourceUpdateBatch *resourceUpdates = m_rhi->nextResourceUpdateBatch();
+ resourceUpdates->uploadStaticBuffer(m_vbuf.get(), vertexData);
+ cb->resourceUpdate(resourceUpdates);
+ }
+
+ const QSize outputSize = renderTarget()->pixelSize();
+ m_viewProjection = m_rhi->clipSpaceCorrMatrix();
+ m_viewProjection.perspective(45.0f, outputSize.width() / (float) outputSize.height(), 0.01f, 1000.0f);
+ m_viewProjection.translate(0, 0, -4);
+}
+
+void ExampleRhiItemRenderer::render(QRhiCommandBuffer *cb)
+{
+ QRhiResourceUpdateBatch *resourceUpdates = m_rhi->nextResourceUpdateBatch();
+ QMatrix4x4 modelViewProjection = m_viewProjection;
+ modelViewProjection.rotate(m_angle, 0, 1, 0);
+ resourceUpdates->updateDynamicBuffer(m_ubuf.get(), 0, 64, modelViewProjection.constData());
+
+ const QColor clearColor = QColor::fromRgbF(0.4f, 0.7f, 0.0f, 1.0f);
+ cb->beginPass(renderTarget(), clearColor, { 1.0f, 0 }, resourceUpdates);
+
+ cb->setGraphicsPipeline(m_pipeline.get());
+ const QSize outputSize = renderTarget()->pixelSize();
+ cb->setViewport(QRhiViewport(0, 0, outputSize.width(), outputSize.height()));
+ cb->setShaderResources();
+ const QRhiCommandBuffer::VertexInput vbufBinding(m_vbuf.get(), 0);
+ cb->setVertexInput(0, 1, &vbufBinding);
+ cb->draw(3);
+
+ cb->endPass();
+}
+//![0]
diff --git a/src/quick/doc/snippets/qquickrhiitem/qquickrhiitem_intro.frag b/src/quick/doc/snippets/qquickrhiitem/qquickrhiitem_intro.frag
new file mode 100644
index 0000000000..d86bcf7386
--- /dev/null
+++ b/src/quick/doc/snippets/qquickrhiitem/qquickrhiitem_intro.frag
@@ -0,0 +1,10 @@
+//![0]
+#version 440
+layout(location = 0) in vec3 v_color;
+layout(location = 0) out vec4 fragColor;
+
+void main()
+{
+ fragColor = vec4(v_color, 1.0);
+}
+//![0]
diff --git a/src/quick/doc/snippets/qquickrhiitem/qquickrhiitem_intro.vert b/src/quick/doc/snippets/qquickrhiitem/qquickrhiitem_intro.vert
new file mode 100644
index 0000000000..610df304b1
--- /dev/null
+++ b/src/quick/doc/snippets/qquickrhiitem/qquickrhiitem_intro.vert
@@ -0,0 +1,15 @@
+//![0]
+#version 440
+layout(location = 0) in vec4 position;
+layout(location = 1) in vec3 color;
+layout(location = 0) out vec3 v_color;
+layout(std140, binding = 0) uniform buf {
+ mat4 mvp;
+};
+
+void main()
+{
+ v_color = color;
+ gl_Position = mvp * position;
+}
+//![0]
diff --git a/src/quick/doc/src/advtutorial.qdoc b/src/quick/doc/src/advtutorial.qdoc
index dc79abbc1e..6a00162753 100644
--- a/src/quick/doc/src/advtutorial.qdoc
+++ b/src/quick/doc/src/advtutorial.qdoc
@@ -411,7 +411,7 @@ score data to the web server. If it had returned a QML file (or a URL to a QML f
way as you did with the blocks.
An alternate way to access and submit web-based data would be to use QML types designed for this purpose. XmlListModel
-makes it very easy to fetch and display XML based data such as RSS in a QML application (see the Flickr demo for an example).
+makes it very easy to fetch and display XML based data such as RSS in a QML application.
\section2 That's It!
diff --git a/src/quick/doc/src/concepts/effects/particles.qdoc b/src/quick/doc/src/concepts/effects/particles.qdoc
index 618e736b70..e66d6f9b4b 100644
--- a/src/quick/doc/src/concepts/effects/particles.qdoc
+++ b/src/quick/doc/src/concepts/effects/particles.qdoc
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
+ \keyword Qt Quick Particles
\qmlmodule QtQuick.Particles
\title Qt Quick Particles QML Types
\ingroup qmlmodules
diff --git a/src/quick/doc/src/concepts/effects/topic.qdoc b/src/quick/doc/src/concepts/effects/topic.qdoc
index afdbf0f9a2..bd54313106 100644
--- a/src/quick/doc/src/concepts/effects/topic.qdoc
+++ b/src/quick/doc/src/concepts/effects/topic.qdoc
@@ -15,6 +15,9 @@ a useful way to subtly communicate to the user (for example, which visual
item is active, or how focus is being transferred). Over-use of visual
effects can actually detract from the user-experience.
+For more information about applying post-processing effects, such as blur, drop
+shadow, or colorize, to an \l Item, see the \l MultiEffect QML type.
+
\section1 Visual Transformation
Visual objects can be transformed. For example, they can be scaled or rotated.
diff --git a/src/quick/doc/src/concepts/input/focus.qdoc b/src/quick/doc/src/concepts/input/focus.qdoc
index 225eea7788..8c6f73024b 100644
--- a/src/quick/doc/src/concepts/input/focus.qdoc
+++ b/src/quick/doc/src/concepts/input/focus.qdoc
@@ -18,8 +18,8 @@ scope based extension to Qt's traditional keyboard focus model.
When the user presses or releases a key, the following occurs:
\list 1
\li Qt receives the key action and generates a key event.
-\li If a \l QQuickWindow is the active window, the key event
-is delivered to it.
+\li If a \l QQuickWindow is the \l{QGuiApplication::focusWindow()}{focus window}
+of the application, the key event is delivered to it.
\li The key event is delivered by the scene to the \l Item with
\e {active focus}. If no item has active focus, the key event is ignored.
\li If the \l QQuickItem with active focus accepts the key event, propagation
diff --git a/src/quick/doc/src/concepts/inputhandlers/qtquickhandlers-index.qdoc b/src/quick/doc/src/concepts/inputhandlers/qtquickhandlers-index.qdoc
index 182773b0f5..a28ce21172 100644
--- a/src/quick/doc/src/concepts/inputhandlers/qtquickhandlers-index.qdoc
+++ b/src/quick/doc/src/concepts/inputhandlers/qtquickhandlers-index.qdoc
@@ -17,6 +17,8 @@
behavioral concerns are better separated, and the behavior is built up by
finer-grained composition.
+ The \l {Qt Quick Examples - Pointer Handlers} demonstrates some use cases for these.
+
The pre-existing \l Keys attached property is similar in concept, so we
refer to the pointing-device-oriented handlers plus \c Keys together as the
set of Input Handlers. We expect to offer more attached-property use cases
@@ -70,7 +72,8 @@
PointHandler) can work only with passive grabs; others require exclusive
grabs; and others can "lurk" with passive grabs until they detect that a
gesture is being performed, and then make the transition from passive to
- exclusive grab.
+ exclusive grab. TapHandler's grabbing behavior is
+ \l {TapHandler::gesturePolicy}{configurable}.
When a grab transition is requested, \l PointerHandler::grabPermissions,
\l QQuickItem::keepMouseGrab() and \l QQuickItem::keepTouchGrab() control
diff --git a/src/quick/doc/src/concepts/layouts/qtquicklayouts-index.qdoc b/src/quick/doc/src/concepts/layouts/qtquicklayouts-index.qdoc
index b064368ce7..908a82fdcb 100644
--- a/src/quick/doc/src/concepts/layouts/qtquicklayouts-index.qdoc
+++ b/src/quick/doc/src/concepts/layouts/qtquicklayouts-index.qdoc
@@ -25,12 +25,14 @@
\list
\li \l {Qt Quick Layouts Overview}
+ \li \l {Qt Quick Responsive Layouts}
\endlist
\section1 Examples
\list
\li \l {Qt Quick Layouts - Basic Example}
+ \li \l {Qt Quick Layouts - Responsive Layout Example}
\endlist
\section1 Reference
diff --git a/src/quick/doc/src/concepts/layouts/qtquicklayouts-overview.qdoc b/src/quick/doc/src/concepts/layouts/qtquicklayouts-overview.qdoc
index 115b8faf44..6173e78f6d 100644
--- a/src/quick/doc/src/concepts/layouts/qtquicklayouts-overview.qdoc
+++ b/src/quick/doc/src/concepts/layouts/qtquicklayouts-overview.qdoc
@@ -144,11 +144,11 @@
effective preferred height is determined from the
\l{Item::implicitHeight}{implicitHeight}.
- \note If you don't specify neither preferredWidth nor implicitWidth,
- the Layout will query \l width as an ultimate value for the effective
- preferred width. However, you shouldn't rely on \l width as a source for
- the effective preferred width, as that may cause unexpected behavior.
- For instance, changing the \l{Item::width}{width} or
+ \note If you don't specify neither preferredWidth nor implicitWidth, the
+ Layout will query \l {Item::}{width} as an ultimate value for the effective
+ preferred width. However, you shouldn't rely on \l {Item::}{width} as a
+ source for the effective preferred width, as that may cause unexpected
+ behavior. For instance, changing the \l{Item::}{width} or
\l{Item::height}{height} properties won't trigger a layout rearrangement,
or the layout might use the actual width and height -- not the width and
height specified in your QML file -- when forced to do a full rebuild.
diff --git a/src/quick/doc/src/concepts/layouts/qtquicklayouts-responsive.qdoc b/src/quick/doc/src/concepts/layouts/qtquicklayouts-responsive.qdoc
new file mode 100644
index 0000000000..6287815f65
--- /dev/null
+++ b/src/quick/doc/src/concepts/layouts/qtquicklayouts-responsive.qdoc
@@ -0,0 +1,101 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \page qtquicklayouts-responsive.html
+ \title Qt Quick Responsive Layouts
+ \brief A guideline to make Qt Quick Layouts adaptive to screen size.
+
+ Layouts are a good technique to make resizable user interfaces. However,
+ this approach has its limitations as we cannot shrink and expand items
+ limitless without sacrificing usability and aesthetics. At some point, it
+ makes more sense to reorganize, remove or add certain elements. Adapting to
+ different devices (e.g. phones and tables) and screen orientations
+ (landscape or portrait) can be implemented similarly. This is what
+ we usually understand as responsive layouts and \l {Qt Quick Layouts}
+ provide various APIs to implement them.
+
+ \section1 Static hierarchy, adaptive layout
+
+ Layouts have a hierarchy, which is usually defined by the declarative QML
+ code. For some simple responsive layouts, it is sufficient to keep the
+ hierarchy unmodified and instead just tweak some of the properties that
+ affect layouting.
+
+ \section2 Declarative description
+
+ The simplest approach to change layouting is to modify layout properties
+ and \l {Layout} attached properties with small expressions. You can for
+ instance use ternary operators in order to modify the layout depending
+ on its width.
+ \l {Item} properties, such as \l {Item::visible}{Item.visible},
+ hiding or showing various parts of the interface, can be modified the same
+ way.
+
+ In the following snippet, this concept is used to change a two-column
+ layout into a single-column layout if the window width is smaller than a
+ certain value.
+
+ \snippet layouts/responsiveDeclarative.qml document
+
+ The resulting layouts look like this, depending on the width of the window.
+
+ \div {class="float-right"}
+ \inlineimage simpleProxy.png
+ \enddiv
+
+ Various levels of layouts and items can be nested but \l {Item}{Items} can only be moved within a their \l{Item::parent}{Item.parent}.
+
+ \section2 States
+
+ The same result can be achieved with \l {Qt Quick States}. The upside of
+ using states is that the \l {Layout} properties for a specific layout are
+ collected at a single point in the QML file (at least the changing ones).
+ The previously shown example can be implemented as follows and the result
+ looks and behaves the exact same.
+
+ \snippet layouts/responsiveStates.qml document
+
+ \section2 LayoutItemProxy
+
+ A third approach is the application of the \l {LayoutItemProxy}. The
+ implementation of the previously shown minimalistic example can be found in
+ the type documentation. In contrast to previously shown solutions, the
+ \l {LayoutItemProxy} enables the declaration of completely separate layouts
+ for various form factors. Especially with more complex layouts this might be
+ useful to improve and maintain a reasonable source code structure.
+
+ Note, that the \l{LayoutItemProxy} API is a technical preview and might
+ be subject to change or removal in future Qt versions.
+
+ \section1 Adaptive hierarchy, adaptive layout
+
+ More complex reconstructions of the layout might require changes to
+ the hierarchy. A small stand-alone button in a small layout might be
+ combined with other buttons and put into a box of a larger layout. An item
+ that is fully visible in one layout, might require a \l {Flickable} in
+ another, smaller layout. In this scenario, it is best to rely on the \l {LayoutItemProxy}. The \l {LayoutItemProxy} allows to move \l{Item}{Items} across various hierarchy levels and between different \l{Item::parent}{Item.parent}.
+
+ The \l {Qt Quick Layouts - Responsive Layout Example} shows a case where an
+ item is moved between different hierarchy levels, put into a \l {Flickable}
+ in one case and on the top level in another layout. The two resulting
+ layouts look as follows.
+
+ \div {class="float-right"}
+ \image qtquicklayouts-example-responsivelayouts.png
+ \enddiv
+
+
+ \section1 Useful links: Consult your design guidelines
+
+ Many design guidelines offer help and tips to create responsive layouts.
+ Implementing the respective techniques is possible with the APIs mentioned
+ above. For further information we recommend the following links:
+
+ \list
+ \li \l {https://developer.apple.com/design/human-interface-guidelines/layout}{Apple human interface guidelines}
+ \li \l {https://m3.material.io/foundations/layout/applying-layout/window-size-classes}{Material3 layouts}
+ \li \l {https://learn.microsoft.com/en-us/windows/apps/design/layout/responsive-design}{Microsoft Fluent responsive design techniques}
+ \endlist
+
+*/
diff --git a/src/quick/doc/src/concepts/modelviewsdata/cppmodels.qdoc b/src/quick/doc/src/concepts/modelviewsdata/cppmodels.qdoc
index d84ccc9176..3e8fbf7dfd 100644
--- a/src/quick/doc/src/concepts/modelviewsdata/cppmodels.qdoc
+++ b/src/quick/doc/src/concepts/modelviewsdata/cppmodels.qdoc
@@ -43,7 +43,7 @@ within the Qt install directory.
\note There is no way for the view to know that the contents of a QStringList
have changed. If the QStringList changes, it will be necessary to reset
-the model by calling QQmlContext::setContextProperty() again.
+the model by setting the view's \c model property again.
\section2 QVariantList-based Model
@@ -79,10 +79,8 @@ the ListView delegate:
\snippet models/objectlistmodel/view.qml 0
-Note the use of \c color property with qualifier.
-The properties of the object are not replicated in the \c model
-object, as they are easily available via the \c modelData
-object.
+Note the use of the \c color property. You can require existing properties
+by declaring them as \c required in a derived type.
The complete source code for this example is available in
\l {models/objectlistmodel}{examples/quick/models/objectlistmodel}
@@ -90,7 +88,7 @@ within the Qt install directory.
Note: There is no way for the view to know that the contents of a QList
has changed. If the QList changes, it is necessary to reset
-the model by calling QQmlContext::setContextProperty() again.
+the model by setting the \c model property again.
\section2 QAbstractItemModel Subclass
@@ -230,12 +228,10 @@ model:
\section2 Exposing C++ Data Models to QML
-The above examples use QQmlContext::setContextProperty() to set
+The above examples use required properties on the view to set
model values directly in QML components. An alternative to this is to
-register the C++ model class as a QML type (either
-\l{Defining QML Types from C++}{directly} from a C++ entry-point, or within
-the initialization function of a \l{Creating C++ Plugins for QML}
-{QML C++ plugin}, as shown below). This would allow the model classes to be
+register the C++ model class as a QML type (see
+\l{Defining QML Types from C++}). This allows the model classes to be
created directly as types within QML:
\table
@@ -244,16 +240,12 @@ created directly as types within QML:
\li C++
\li
\code
-class MyModelPlugin : public QQmlExtensionPlugin
+class MyModel : public QAbstractItemModel
{
Q_OBJECT
- Q_PLUGIN_METADATA(IID "org.qt-project.QmlExtension.MyModel" FILE "mymodel.json")
-public:
- void registerTypes(const char *uri)
- {
- qmlRegisterType<MyModel>(uri, 1, 0,
- "MyModel");
- }
+ QML_ELEMENT
+
+ // [...]
}
\endcode
\row
@@ -262,7 +254,6 @@ public:
\qml
MyModel {
id: myModel
- ListElement { someProperty: "some value" }
}
\endqml
@@ -270,14 +261,17 @@ MyModel {
ListView {
width: 200; height: 250
model: myModel
- delegate: Text { text: someProperty }
+ delegate: Text {
+ required property string someProperty
+ text: someProperty
+ }
}
\endqml
\endtable
-See \l {Writing QML Extensions with C++} for details on writing QML C++
-plugins.
+See \l {Writing QML Extensions with C++} for details on writing QML types
+in C++.
\section2 Changing Model Data
diff --git a/src/quick/doc/src/concepts/modelviewsdata/modelview.qdoc b/src/quick/doc/src/concepts/modelviewsdata/modelview.qdoc
index dc44407766..3aaf40f199 100644
--- a/src/quick/doc/src/concepts/modelviewsdata/modelview.qdoc
+++ b/src/quick/doc/src/concepts/modelviewsdata/modelview.qdoc
@@ -4,9 +4,9 @@
/*!
\page qtquick-modelviewsdata-modelview.html
\title Models and Views in Qt Quick
-\brief how to display and form data in Qt Quick
+\brief how to display and format data in Qt Quick
-Simply put, applications need to form data and display the data. Qt Quick has the
+Most applications need to format data and display the data. Qt Quick has the
notion of \e models, \e views, and \e delegates to display data. They modularize
the visualization of data in order to give the developer or designer control
over the different aspects of the data. A developer can swap a list view with a
@@ -21,7 +21,7 @@ types for creating models.
\li \b View - a container that displays the data. The view might
display the data in a list or a grid.
\li \b Delegate - dictates how the data should appear in the view.
-The delegate takes each data in the model and encapsulates it. The data is
+The delegate takes each unit of data in the model and encapsulates it. The data is
accessible through the delegate. The delegate can also write data
back into editable models (e.g. in a TextField's onAccepted Handler).
\endlist
@@ -58,7 +58,7 @@ To visualize data, bind the view's \c model property to a model and the
Views allow visual customization through \e decoration properties such as
the \c header, \c footer, and \c section properties. By binding an object,
usually another visual object, to these properties, the views are
- decoratable. A footer may include a \l Rectangle type showcasing borders
+ decoratable. A footer may include a \l Rectangle type showing borders
or a header that displays a logo on top of the list.
Suppose that a specific club wants to decorate its members list with its brand
@@ -117,6 +117,14 @@ To visualize data, bind the view's \c model property to a model and the
\snippet qml/listview.qml delegate
\image listview-setup.png
+ \section2 Positioning of View Delegates
+
+ The type of view will determine how the items are positioned. \l {ListView}
+ will position the items in a straight line, depending on the \l {ListView::}{orientation},
+ while a \l {GridView} can lay them out in a 2 dimentional grid. It's \b {not} recommended
+ to bind directly on \l {Item::x}{x} and \l {Item::y}{y}, since the view's layouting
+ behavior will always take precedence over any positional binding.
+
\section2 Accessing Views and Models from Delegates
The list view to which the delegate is bound is accessible from the delegate
@@ -144,26 +152,41 @@ To visualize data, bind the view's \c model property to a model and the
bind to. Here is a ListModel with two roles, \e type and \e age, and a
ListView with a delegate that binds to these roles to display their values:
- \snippet qml/qml-data-models/listmodel-listview.qml document
+ \snippet qml/qml-data-models/listmodel-listview-required.qml document
- To get finer control over which roles are accessible, and to make delegates
- more self-contained and usable outside of views,
- \l{Required Properties}{required properties} can be used. If a delegate
- contains required properties, the named roles are not provided. Instead,
- the QML engine will check if the name of a required property matches that of
- a model role. If so, that property will be bound to the corresponding value
- from the model.
+ In most cases you should use \l{Required Properties}{required properties} to
+ pass model data into your delegates. If a delegate contains required
+ properties, the QML engine will check if the name of a required property
+ matches that of a model role. If so, that property will be bound to the
+ corresponding value from the model.
- \snippet qml/qml-data-models/listmodel-listview-required.qml document
+ In rare corner cases, you may want to transfer the model properties through
+ the QML context rather than as required properties. If no required
+ properties are present in your delegate, the named roles are provided as
+ context properties:
+
+ \snippet qml/qml-data-models/listmodel-listview.qml document
+
+ Context properties are invisible to tooling and prevent the
+ \l{Qt Quick Compiler} from optimizing your code. They make it harder to
+ reason about the specific data your delegate expects. There is no way to
+ explicitly populate the QML context from QML. If your component expects
+ data to be passed via the QML context, you can only use it in places
+ where the right context is made available via native means. This can be
+ your own C++ code or the specific implementations of surrounding elements.
+ Conversely, required properties can be set in a number of ways from QML or
+ via native means. Therefore, passing data via the QML context reduces the
+ re-usability of your components.
If there is a naming clash between the model's properties and the delegate's
properties, the roles can be accessed with the qualified \e model name
- instead. For example, if a \l Text type had \e type or \e age properties,
- the text in the above example would display those property values instead of
- the \e type and \e age values from the model item. In this case, the
- properties could have been referenced as \c model.type and \c model.age
- instead to ensure the delegate displays the property values from the model
- item.
+ instead. For example, if a \l Text type had (non-required) \e type or \e age
+ properties, the text in the above example would display those property
+ values instead of the \e type and \e age values from the model item. In this
+ case, the properties could have been referenced as \c model.type and
+ \c model.age instead to ensure the delegate displays the property values from
+ the model item. For this to work, you need to require a \c model property in
+ your delegate (unless you are using context properties).
A special \e index role containing the index of the item in the model is
also available to the delegate. Note this index is set to -1 if the item is
@@ -173,12 +196,71 @@ To visualize data, bind the view's \c model property to a model and the
possible to delay delegate destruction in some views via a \c delayRemove
attached property.)
- Models that do not have named roles (such as the ListModel shown
- below) will have the data provided via the \e modelData role. The \e
- modelData role is also provided for models that have only one role. In this
- case the \e modelData role contains the same data as the named role.
+ Remember that you can use integers or arrays as model:
- \note \e model, \e index, and \e modelData roles are not accessible
+ \qml
+ Repeater {
+ model: 5
+ Text {
+ required property int modelData
+ text: modelData
+ }
+ }
+ \endqml
+
+ \qml
+ Repeater {
+ model: ["one", "two", "three"]
+ Text {
+ required property string modelData
+ text: modelData
+ }
+ }
+ \endqml
+
+ Such models provide a singular, anonymous piece of data to each instance
+ of the delegate. Accessing this piece of data is the primary reason to
+ use \e modelData, but other models also provide \e modelData.
+
+ The object provided via the \e model role has a property with an empty name.
+ This anonymous property holds the \e modelData. Furthermore, the object
+ provided via the \e model role has another property called \e modelData.
+ This property is deprecated and also holds the \e modelData.
+
+ In addition to the \e model role, a \e modelData role is provided. The
+ \e modelData role holds the same data as the \e modelData property and the
+ anonymous property of the object provided via the \e model role.
+
+ The differences between the \e model role and the various means to access
+ \e modelData are as follows:
+
+ \list
+ \li Models that do not have named roles (such as integers or an array of
+ strings) have their data provided via the \e modelData role. The
+ \e modelData role does not necessarily contain an object in this case.
+ In the case of an integer model it would contain an integer (the index
+ of the current model item). In the case of an array of strings it would
+ contain a string. The \e model role still contains an object, but
+ without any properties for named roles. \e model still contains its
+ usual \e modelData and anonymous properties, though.
+ \li If the model has only one named role, the \e modelData role contains
+ the same data as the named role. It is not necessarily an object and it
+ does not contain the named role as a named property the way it usually
+ would. The \e model role still contains an object with the named role as
+ property, and the \e modelData and anonymous properties in this case.
+ \li For models with multiple roles, the \e modelData role is only provided as
+ a required property, not as a context property. This is due to backwards
+ compatibility with older versions of Qt.
+ \endlist
+
+ The anonymous property on \e model allows you to cleanly write delegates
+ that receive both their model data and the role name they should react
+ to as properties from the outside. You can provide a model without or
+ with only one named role, and an empty string as role. Then, a binding that
+ simply accesses \c{model[role]} will do what you expect. You don't have to
+ add special code for this case.
+
+ \note The \e model, \e index, and \e modelData roles are not accessible
if the delegate contains required properties, unless it has also required
properties with matching names.
@@ -285,7 +367,11 @@ To visualize data, bind the view's \c model property to a model and the
Component {
id: itemDelegate
- Text { text: "I am item number: " + index }
+
+ Text {
+ required property int index
+ text: "I am item number: " + index
+ }
}
ListView {
@@ -320,7 +406,11 @@ To visualize data, bind the view's \c model property to a model and the
Component {
id: myDelegate
- Text { text: model.color }
+
+ Text {
+ required property var model
+ text: model.color
+ }
}
ListView {
@@ -343,6 +433,20 @@ To visualize data, bind the view's \c model property to a model and the
\l{Using C++ Models with Qt Quick Views}
article.
+ \section2 Array models
+
+ You can use JavaScript arrays and various kinds of QML lists as models.
+ The elements of the list will be made available as model and modelData
+ by the rules outlined above: Singular data like integers or strings are
+ made available as singular modelData. Structured data like JavaScript
+ objects or QObjects are made available as structured model and modelData.
+
+ The individual model roles are also made available if you request them as
+ required properties. Since we cannot know in advance what objects will
+ appear in an array, any required property in a delegate will be populated,
+ possibly with a coercion of \c undefined to the required type. The
+ individual model roles are not made available via the QML context, though.
+ They would shadow all other context properties.
\section1 Repeaters
@@ -373,8 +477,8 @@ the model.
For more details, see the \l{qtquick-modelviewsdata-modelview.html#integers-as-models}{QML Data Models} document.
-If the model is a string list, the delegate is also exposed to a read-only
-\c modelData property that holds the string. For example:
+If the model is a string list, the delegate is also exposed to the usual
+read-only \c modelData property that holds the string. For example:
\table
\row
@@ -400,6 +504,8 @@ ListView {
anchors.fill: parent
model: EditableModel {}
delegate: TextEdit {
+ required property var model
+
width: ListView.view.width
height: 30
text: model.edit
diff --git a/src/quick/doc/src/concepts/positioning/righttoleft.qdoc b/src/quick/doc/src/concepts/positioning/righttoleft.qdoc
index 722d7b747b..7c7c314fc3 100644
--- a/src/quick/doc/src/concepts/positioning/righttoleft.qdoc
+++ b/src/quick/doc/src/concepts/positioning/righttoleft.qdoc
@@ -145,6 +145,16 @@ enter either \c LTR or \c RTL as the translation for the locale.
</context>
\endcode
+Next, add the following bindings to the root QML component of your application:
+\code
+LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
+LayoutMirroring.childrenInherit: true
+\endcode
+
+The first binding ensures that the UI will be mirrored appropriately when a
+right-to-left locale is set. The second binding ensures that child items of the
+root component will also respect mirroring.
+
You can test that the layout direction works as expected by running your Qt Quick application with
the compiled translation file:
diff --git a/src/quick/doc/src/concepts/positioning/topic.qdoc b/src/quick/doc/src/concepts/positioning/topic.qdoc
index cd07d75985..dc9f647ee5 100644
--- a/src/quick/doc/src/concepts/positioning/topic.qdoc
+++ b/src/quick/doc/src/concepts/positioning/topic.qdoc
@@ -5,6 +5,7 @@
\page qtquick-positioning-topic.html
\title Important Concepts In Qt Quick - Positioning
\brief Overview of positioning concepts
+\ingroup explanations-programminglanguages
Visual items in QML can be positioned in a variety of ways. The most important
positioning-related concept is that of anchoring, a form of relative
diff --git a/src/quick/doc/src/concepts/visualcanvas/adaptations.qdoc b/src/quick/doc/src/concepts/visualcanvas/adaptations.qdoc
index c93733d819..d4c0c0eb2b 100644
--- a/src/quick/doc/src/concepts/visualcanvas/adaptations.qdoc
+++ b/src/quick/doc/src/concepts/visualcanvas/adaptations.qdoc
@@ -17,7 +17,7 @@ in form of plugins (openvg) or built-in to the Qt Quick library
From Qt 5.14 onwards, the default adaptation gains the option of rendering via
a graphics abstraction layer, the Qt Rendering Hardware Interface (RHI),
-provided by the \l QtGui module. When enabled, no direct OpenGL calls are made.
+provided by the \l [QtGui]{Qt GUI} module. When enabled, no direct OpenGL calls are made.
Rather, the scene graph renders by using the APIs provided by the abstraction
layer, which is then translated into OpenGL, Vulkan, Metal, or Direct 3D calls.
Shader handling is also unified by writing shader code once, compiling to
@@ -98,10 +98,11 @@ graphics. For more details, see
\page qtquick-visualcanvas-adaptations-software.html
The Software adaptation is an alternative renderer for \l {Qt Quick} 2 that uses the Raster paint
-engine to render the contents of the scene graph, instead of OpenGL. Consequently, some features
-and optimizations are not available. Most Qt Quick 2 applications can run without any modification,
-but any attempts to use unsupported features are ignored. By using the Software adaptation, it is
-possible to run Qt Quick 2 applications on hardware and platforms that do not have OpenGL support.
+engine to render the contents of the scene graph, instead of a hardware-accelerated 3D graphics API.
+Consequently, some features and optimizations are not available. Most Qt Quick 2 applications can run
+without any modification, but any attempts to use unsupported features are ignored.
+By using the Software adaptation, it is possible to run Qt Quick 2 applications on hardware and
+platforms that do not have \l{topics-graphics}{hardware-accelerated 3D graphics API} support.
The Software adaptation was previously known as the Qt Quick 2D Renderer. However, unlike the 2D
Renderer, this new, integrated version supports partial updates. This means that a full update
@@ -121,14 +122,9 @@ even though they are not visible.
\section2 Rendering Text
The text rendering with the Software adaptation is based on software rasterization and does not
-respond as well to transformations such as scaling, compared to when using OpenGL. The quality is
-similar to choosing \l [QML] {Text::renderType}{Text.NativeRendering} with \l [QML] {Text} items.
-
-\section2 Qt Multimedia VideoOutput
-
-The Qt Multimedia module's VideoOutput item is not supported with the Software adaptation. This
-is because VideoOutput uses the QVideoRendererControl item which requires custom QSGGeometryNode
-behavior, which is only present in the default OpenGL adaptation.
+respond as well to transformations such as scaling, compared to when using a hardware-accelerated
+3D graphics API. The quality is similar to choosing \l [QML] {Text::renderType}{Text.NativeRendering}
+with \l [QML] {Text} items.
*/
diff --git a/src/quick/doc/src/concepts/visualcanvas/scenegraph.qdoc b/src/quick/doc/src/concepts/visualcanvas/scenegraph.qdoc
index 30ccb9967e..050bf00f62 100644
--- a/src/quick/doc/src/concepts/visualcanvas/scenegraph.qdoc
+++ b/src/quick/doc/src/concepts/visualcanvas/scenegraph.qdoc
@@ -248,9 +248,9 @@ environment.
The non-threaded render loop is currently used by default on Windows with
OpenGL when not using the system's standard opengl32.dll, \macos with OpenGL,
-and Linux with some drivers. For the latter this is mostly a precautionary
-measure, as not all combinations of OpenGL drivers and windowing systems have
-been tested.
+WebAssembly, and Linux with some drivers. For the latter this is mostly a
+precautionary measure, as not all combinations of OpenGL drivers and windowing
+systems have been tested.
On macOS and OpenGL, the threaded render loop is not supported when building
with XCode 10 (10.14 SDK) or later, since this opts in to layer-backed views on
@@ -258,6 +258,10 @@ macOS 10.14. You can build with Xcode 9 (10.13 SDK) to opt out of
layer-backing, in which case the threaded render loop is available and used by
default. There is no such restriction with Metal.
+The threaded render loop is not supported on WebAssembly, since the web platform
+has limited support for using WebGL on other threads than the main thread, and
+limited support for blocking the main thread.
+
Even when using the non-threaded render loop, you should write your code as if
you are using the threaded renderer, as failing to do so will make the code
non-portable.
@@ -319,6 +323,20 @@ same time this is exactly what can cause trouble if vsync-based throttling is
not functioning as expected, because if what the render loop thinks is
happening is not matching reality, incorrect animation pacing will occur.
+\note Starting from Qt 6.5, the threaded render loop offers the possibility of
+opting in to another animation driver, based solely on the elapsed time
+(QElapsedTimer). To enable this, set the \c{QSG_USE_SIMPLE_ANIMATION_DRIVER}
+environment variable to a non-zero value. This has the benefits of not needing
+any of the infrastructure for falling back to a QTimer when there are multiple
+windows, not needing heuristics trying determine if vsync-based throttling is
+missing or broken, being compatible with any kind of temporal drifts in vsync
+throttling, and not being tied to the primary screen's refresh rate, thus
+potentially working better in multi-screen setups. It also drives render
+thread animations (the \l Animator types) correctly even if vsync-based
+throttling is broken or disabled. On the other hand, animations may be
+perceived as less smooth with this approach. With compatibility in mind, it is
+offered as an opt-in feature at the moment.
+
In summary, the \c threaded render loop is expected to provide smoother
animations with less stutter as long as the following conditions are met:
@@ -410,8 +428,10 @@ situations.
Remember however, that by design none of this helps render thread animations
(the \l Animator types). In the absence of vsync-based blocking,
-\l{Animator}{animators} will advance incorrectly, faster than expected, even
-when the workarounds are activated for regular \l{Animation}{animations}.
+\l{Animator}{animators} will advance incorrectly by default, faster than
+expected, even when the workarounds are activated for regular
+\l{Animation}{animations}. If this becomes an issue, consider using the
+alternative animation driver by setting \c{QSG_USE_SIMPLE_ANIMATION_DRIVER}.
\note Be aware that the rendering loop logic and event processing on the GUI
(main) thread is not necessarily unthrottled even if waiting for vsync is
@@ -450,23 +470,53 @@ presentation rate simply because there is no presenting of the frame happening.
This is optional, by default animations will advance based on the system timer.
-\section2 Mixing Scene Graph and the native graphics API
+\section2 Extending the Scene Graph with QRhi-based and native 3D rendering
+
+The scene graph offers three methods for integrating application-provided
+graphics commands:
+
+\list
+
+\li Issuing either \l{QRhi}-based or OpenGL, Vulkan, Metal, Direct3D commands
+directly before or after the scene graph's own rendering. This in effect
+prepends or appends a set of draw calls into the main render pass. No additional
+render target is used.
+
+\li Rendering to a texture and creating a textured node in the scene graph. This
+involves an additional render pass and render target.
+
+\li Issuing draw calls inline with the scene graph's own rendering by
+instantiating a QSGRenderNode subclass in the scene graph. This is similar to
+the first approach but the custom draw calls are effectively injected into the
+scene graph's command stream.
+
+\endlist
-The scene graph offers two methods for integrating application-provided
-graphics commands: by issuing OpenGL, Vulkan, Metal, etc. commands directly,
-and by creating a textured node in the scene graph.
+\section3 Underlay/overlay mode
By connecting to the \l QQuickWindow::beforeRendering() and \l
-QQuickWindow::afterRendering() signals, applications can make OpenGL calls
-directly into the same context as the scene graph is rendering to. With APIs
-like Vulkan or Metal, applications can query native objects, such as, the scene
-graph's command buffer, via QSGRendererInterface, and record commands to it as
-they see fit. As the signal names indicate, the user can then render content
-either under a Qt Quick scene or over it. The benefit of integrating in this
-manner is that no extra framebuffer nor memory is needed to perform the
-rendering, and a possibly expensive texturing step is eliminated. The downside
-is that Qt Quick decides when to call the signals and this is the only time the
-OpenGL application is allowed to draw.
+QQuickWindow::afterRendering() signals, applications can make \l QRhi or native
+3D API calls directly into the same context as the scene graph is rendering to.
+With APIs like Vulkan or Metal, applications can query native objects, such as,
+the scene graph's command buffer, via QSGRendererInterface, and record commands
+to it as they see fit. As the signal names indicate, the user can then render
+content either under a Qt Quick scene or over it. The benefit of integrating in
+this manner is that no extra render targets are needed to perform the rendering,
+and a possibly expensive texturing step is eliminated. The downside is that the
+custom rendering can only be issued either at the beginning or at the end of Qt
+Quick's own rendering. Using QSGRenderNode instead of the QQuickWindow signals
+can lift that restriction somewhat, but in either case care must be taken when
+it comes to 3D content and depth buffer usage since relying on depth testing and
+rendering with depth write enabled can easily create situations where the custom
+content and the Qt Quick content's depth buffer usage conflict with each other.
+
+From Qt 6.6 the \l QRhi APIs are considered semi-public, i.e. offered to the
+applications and documented, albeit with a limited compatibility guarantee. This
+allows creating portable, cross-platform 2D/3D rendering code by using the same
+graphics and shader abstractions the scene graph itself uses.
+
+The \l {Scene Graph - RHI Under QML} example gives an example on how to
+implement the underlay/overlay approach using \l QRhi.
The \l {Scene Graph - OpenGL Under QML} example gives an example on
how to use these signals using OpenGL.
@@ -480,36 +530,58 @@ how to use these signals using Metal.
The \l {Scene Graph - Vulkan Under QML} example gives an example on
how to use these signals using Vulkan.
-The other alternative, only available for OpenGL currently, is to create a
-QQuickFramebufferObject, render into it, and let it be displayed in the scene
-graph as a texture. The \l {Scene Graph - Rendering FBOs} example shows how
-this can be done.
-
-Graphics APIs other than OpenGL can also follow this approach, even though
-QQuickFramebufferObject does not currently support them. Creating and rendering
-to a texture directly with the underlying API, followed by wrapping and using
-this resource in a Qt Quick scene in a custom QQuickItem, is demonstrated in
-the \l {Scene Graph - Metal Texture Import} example. That example uses Metal,
-the concepts however apply to all other graphics APIs as well.
-
-\warning Starting with Qt 6.0, direct usage of the underlying graphics API must
-be enclosed by a call to \l QQuickWindow::beginExternalCommands() and \l
+Starting with Qt 6.0, direct usage of the underlying graphics API must be
+enclosed by a call to \l QQuickWindow::beginExternalCommands() and \l
QQuickWindow::endExternalCommands(). This concept may be familiar from \l
QPainter::beginNativePainting(), and serves a similar purpose: it allows the Qt
Quick Scene Graph to recognize that any cached state and assumptions about the
state within the currently recorded render pass, if there is one, are now
invalid, because the application code may have altered it by working directly
-with the underlying graphics API.
+with the underlying graphics API. This is not applicable and necessary when
+using \l QRhi.
+
+When mixing custom OpenGL rendering with the scene graph, it is important the
+application does not leave the OpenGL context in a state with buffers bound,
+attributes enabled, special values in the z-buffer or stencil-buffer or similar.
+Doing so can result in unpredictable behavior.
+
+The custom rendering code must be thread aware in the sense that it should not
+assume being executed on the GUI (main) thread of the application. When
+connecting to the \l QQuickWindow signals, the application should use
+Qt::DirectConnection and understand that the connected slots are invoked on the
+scene graph's dedicated render thread, if there is one.
+
+\section3 The texture-based approach
+
+The texture-based alternative is the most flexible approach when the application
+needs to have a "flattened", 2D image of some custom 3D rendering within the Qt
+Quick scene. This also allows using a dedicated depth/stencil buffer that is
+independent of the buffers used by the main render pass.
+
+When using OpenGL, the legacy convenience class QQuickFramebufferObject can be
+used to achieve this. QRhi-based custom renderers and graphics APIs other than
+OpenGL can also follow this approach, even though QQuickFramebufferObject does
+not currently support them. Creating and rendering to a texture directly with
+the underlying API, followed by wrapping and using this resource in a Qt Quick
+scene in a custom QQuickItem, is demonstrated in the following examples:
+
+\l {Scene Graph - RHI Texture Item} example.
+
+\l {Scene Graph - Vulkan Texture Import} example.
+
+\l {Scene Graph - Metal Texture Import} example.
-\warning When mixing OpenGL content with scene graph rendering, it is
-important the application does not leave the OpenGL context in a state
-with buffers bound, attributes enabled, special values in the z-buffer
-or stencil-buffer or similar. Doing so can result in unpredictable
-behavior.
+\section3 The inline approach
-\warning The custom rendering code must be thread aware in the sense that it
-should not assume being executed on the GUI (main) thread of the application.
+Using \l QSGRenderNode the custom draw calls are injected not at the beginning
+or the end of the recording of the scene graph's render pass, but rather during
+the scene graph's rendering process. This is achieved by creating a custom \l
+QQuickItem based by an instance of \l QSGRenderNode, a scene graph node that
+exists specifically to allow issuing graphics commands either via \l QRhi or a
+native 3D API such as OpenGL, Vulkan, Metal, or Direct 3D.
+The \l {Scene Graph - Custom QSGRenderNode} example gives a demonstration of
+this approach.
\section2 Custom Items using QPainter
@@ -1138,7 +1210,7 @@ with multiple windows.
From Qt 6.0 onwards, the default adaptation always renders via a graphics
abstraction layer, the Qt Rendering Hardware Interface (RHI), provided by the
- \l QtGui module. This means that, unlike Qt 5, no direct OpenGL calls are made
+ \l [QtGui]{Qt GUI} module. This means that, unlike Qt 5, no direct OpenGL calls are made
by the scene graph. Rather, it records resource and draw commands by using the
RHI APIs, which then translate the command stream into OpenGL, Vulkan, Metal,
or Direct 3D calls. Shader handling is also unified by writing shader code
@@ -1155,7 +1227,7 @@ with multiple windows.
\row
\li \c QSG_RHI_BACKEND
- \li \c vulkan, \c metal, \c opengl, \c d3d11
+ \li \c vulkan, \c metal, \c opengl, \c d3d11, \c d3d12
\li Requests the specific RHI backend. By default the targeted graphics API
is chosen based on the platform, unless overridden by this variable or the
equivalent C++ APIs. The defaults are currently Direct3D 11 for Windows,
@@ -1196,8 +1268,8 @@ with multiple windows.
\endcode
See QSGRendererInterface::GraphicsApi. The enum values \c OpenGL, \c Vulkan,
- \c Metal, \c Direct3D11 are equivalent in effect to running with \c
- QSG_RHI_BACKEND set to the equivalent string key.
+ \c Metal, \c Direct3D11, \c Direct3D12 are equivalent in effect to running
+ with \c QSG_RHI_BACKEND set to the equivalent string key.
All QRhi backends will choose the system default GPU adapter or physical
device, unless overridden by \c{QSG_RHI_PREFER_SOFTWARE_RENDERER} or a
@@ -1209,6 +1281,6 @@ with multiple windows.
as environment variables are available as C++ APIs in
QQuickGraphicsConfiguration. For example, setting \c QSG_RHI_DEBUG_LAYER and
calling
- \l{QQuickGraphicsConfiguration::setDebugLayerEnabled()}{setDebugLayerEnabled(true)}
+ \l{QQuickGraphicsConfiguration::setDebugLayer()}{setDebugLayer(true)}
are equivalent.
*/
diff --git a/src/quick/doc/src/dynamicview-tutorial.qdoc b/src/quick/doc/src/dynamicview-tutorial.qdoc
index 3a3f73a533..bb6c18b90c 100644
--- a/src/quick/doc/src/dynamicview-tutorial.qdoc
+++ b/src/quick/doc/src/dynamicview-tutorial.qdoc
@@ -30,6 +30,7 @@ directory.
\nextpage QML Dynamic View Ordering Tutorial 2 - Dragging View Items
\example tutorials/dynamicview/dynamicview1
+\examplecategory {User Interface Components}
We begin our application by defining a ListView, a model which will provide data to the view, and a
delegate which provides a template for constructing items in the view.
diff --git a/src/quick/doc/src/examples.qdoc b/src/quick/doc/src/examples.qdoc
index d49b718a26..84f2986722 100644
--- a/src/quick/doc/src/examples.qdoc
+++ b/src/quick/doc/src/examples.qdoc
@@ -6,7 +6,6 @@
\page qtquick-codesamples.html
\title Qt Quick Examples and Tutorials
\brief Building UIs with QML
-\ingroup all-examples
\ingroup qtquick
\keyword qtquick-samples
@@ -14,7 +13,7 @@ Qt includes several examples to demonstrate a particular usage. The examples
run as applications or as non-GUI examples in Qt Creator. Qt tutorials show
the step-by-step information and give insight to particular code snippets.
-This page lists the \l{Qt QML} and \l{Qt Quick} examples, however, many other
+This page lists the \l{Qt Qml} and \l{Qt Quick} examples, however, many other
\l{All Modules}{Qt modules} contain examples related to their QML API.
\section1 Running the Examples and Demos
@@ -60,7 +59,7 @@ steps such as use cases and introductory material. For more information about Qt
\b{Beginning with QML and Qt Quick}
\list
\li \l{First Steps with QML}
- \li \l{Getting Started Programming with Qt Quick}{Qt Quick Text Editor}
+ \li \l{Getting Started Programming with Qt Quick}{Alarms Application}
\li \l{Qt Design Studio: Your First UI}{Qt Design Studio Tutorials}
\li \l{QML Advanced Tutorial}{SameGame}
\endlist
@@ -81,11 +80,12 @@ steps such as use cases and introductory material. For more information about Qt
\section2 Extending QML
-The following tutorials show how a QML-based application can be combined with
-C++ code using the \l{Qt QML} module. For information about extending QML, visit
-the \l{Writing QML Extensions with C++} page.
-
-\annotatedlist{qmlextendingexamples}
+The following tutorials show how a QML-based application can be combined and
+exteneded with C++ code using the \l{Qt Qml} module.
+\list
+ \li \l {Writing QML Extensions with C++}
+ \li \l {Writing advanced QML Extensions with C++}
+\endlist
\section1 Examples
@@ -98,20 +98,18 @@ Creator.
\b{QML Types and Controls}
\list
\li \l{Qt Quick Controls - Gallery}{Controls Gallery}
- \li \l{Calendar Example}
\li \l{tableview/gameoflife}{TableView}
\li \l{Qt Quick Examples - Text}{Text and Fonts}
- \li \l{Qt Quick Examples - Toggle Switch}{Custom Toggle Switch}
\endlist
\enddiv
\div {class="doc-column"}
\b{Layouts and Views}
\list
\li \l{Qt Quick Layouts - Basic Example}
+ \li \l{Qt Quick Layouts - Responsive Layout Example}
\li \l{Qt Quick Examples - Positioners}{Positioners}
\li \l{Qt Quick Examples - Views}{Views}
\li \l{Qt Quick Examples - Window and Screen}{Windows and Screen}
- \li \l{Qt Quick Examples - Right to Left}{Right-to-Left and Text Layout}
\endlist
\enddiv
\div {class="doc-column"}
@@ -121,6 +119,15 @@ Creator.
\li \l{Qt Quick Examples - Animation}{Animation}
\li \l{Qt Quick Examples - Canvas}{Canvas API}
\li \l{Qt Quick Examples - Shader Effects}{Shader Effects}
+ \li \l{Qt Quick Examples - MultiEffect Test Bed}{MultiEffect}
+ \li \l{Qt Quick Examples - MultiEffect Item Switcher}{MultiEffect-based Item Transitions}
+ \endlist
+ \enddiv
+ \div {class="doc-column"}
+ \b{Shapes}
+ \list
+ \li \l{Qt Quick Examples - Shapes}{Shapes}
+ \li \l{Weather Forecast Example}{Weather Forecast}
\endlist
\enddiv
\enddiv
@@ -130,15 +137,13 @@ Creator.
\b{Keyboard, Focus, and Touch}
\list
\li \l{Qt Quick Examples - Key Interaction}{Key Interaction}
- \li \l{Qt Quick Examples - MouseArea}{MouseArea}
+ \li \l{Qt Quick Examples - Pointer Handlers}{Pointer Handlers}
\endlist
\enddiv
\div {class="doc-column"}
\b{System and Events}
\list
- \li \l{Qt Quick Examples - Threading}{Threading}
\li \l{Qt Quick Examples - Accessibility}{Accessibility}
- \li \l{Qt Quick Examples - externaldraganddrop}{External Drag and Drop}
\li \l{Qt Quick Examples - Drag and Drop}{Drag and Drop}
\li \l{Qt Quick Examples - Item Variable Refresh Rate}{Item Variable Refresh Rate}
\endlist
@@ -147,16 +152,37 @@ Creator.
\b{Scene Graph}
\list
\li \l{Scene Graph - Custom Material}{Custom Material}
+ \li \l{Scene Graph - RHI Under QML}{Portable QRhi-based 3D rendering as a scene underlay}
+ \li \l{Scene Graph - RHI Texture Item}{Displaying a QRhi-rendered image in a QQuickItem}
+ \li \l{Scene Graph - Custom QSGRenderNode}{Implementing a QRhi-based QSGRenderNode}
+ \li \l{QQuickRenderControl RHI Example}{Redirecting Qt Quick rendering into a QRhiTexture}
\li \l{Scene Graph - Two Texture Providers}{Texture Providers and Materials}
\li \l{Scene Graph - Custom Geometry}{Custom Geometry}
\li \l{Scene Graph - Graph}{Graph}
- \li \l{Scene Graph - OpenGL Under QML}{OpenGL Under QML}
- \li \l{Scene Graph - Direct3D 11 Under QML}{Direct3D 11 Under QML}
- \li \l{Scene Graph - Vulkan Under QML}{Vulkan Under QML}
- \li \l{Scene Graph - Vulkan Texture Import}{Vulkan Texture Import}
- \li \l{Scene Graph - Metal Under QML}{Metal Under QML}
- \li \l{Scene Graph - Metal Texture Import}{Metal Texture Import}
- \li \l{Scene Graph - Rendering FBOs}{Rendering to OpenGL FBOs}
+ \endlist
+ \enddiv
+\enddiv
+
+\div {class="multi-column"}
+ \div {class="doc-column"}
+ \b{Extending the Scene Graph using native 3D APIs}
+ \list
+ \li \l{Scene Graph - Vulkan Under QML}{Vulkan-based 3D rendering as a scene underlay}
+ \li \l{Scene Graph - Vulkan Texture Import}{Implementing a custom QQuickItem that displays a native Vulkan image}
+ \li \l{Scene Graph - Metal Under QML}{Metal-based 3D rendering as a scene underlay}
+ \li \l{Scene Graph - Metal Texture Import}{Implementing a custom QQuickItem that displays a native Metal texture}
+ \li \l{Scene Graph - Direct3D 11 Under QML}{Direct3D 11-based rendering as a scene underlay}
+ \li \l{Scene Graph - OpenGL Under QML}{OpenGL-based rendering as a scene underlay}
+ \li \l{QQuickRenderControl OpenGL Example}{Redirecting Qt Quick rendering into an OpenGL texture}
+ \li \l{QQuickRenderControl D3D11 Example}{Redirecting Qt Quick rendering into a Direct 3D texture}
+ \endlist
+ \enddiv
+ \div {class="doc-column"}
+ \b{Adding Qt Quick scenes in QWidget applications}
+ \list
+ \li \l{Qt Quick Widgets Example}{Using QQuickWidget}
+ \li \l{QQuickWidget - QQuickView Comparison Example}{QQuickWidget - QQuickView Comparison (OpenGL)}
+ \li \l{Qt Quick Examples - Embedded in Widgets}{Embedding a QQuickView as Native Window}
\endlist
\enddiv
\enddiv
diff --git a/src/quick/doc/src/guidelines/qtquick-bestpractices.qdoc b/src/quick/doc/src/guidelines/qtquick-bestpractices.qdoc
index 2e2fc05aac..1934be6f26 100644
--- a/src/quick/doc/src/guidelines/qtquick-bestpractices.qdoc
+++ b/src/quick/doc/src/guidelines/qtquick-bestpractices.qdoc
@@ -6,6 +6,7 @@
\title Best Practices for QML and Qt Quick
\brief Lists best practices for working with QML and Qt Quick.
\ingroup best-practices
+\ingroup explanations-programminglanguages
Despite all of the benefits that QML and Qt Quick offer, they can be
challenging in certain situations. The following sections elaborate on some of
@@ -28,11 +29,16 @@ options that align with the latest UI design trends. If these UI controls do not
satisfy your application's needs, only then it is recommended to create a
custom control.
+You can use the controls when you design UIs in Qt Design Studio. In addition,
+it provides timeline-based animations, visual effects, layouts, and a
+live-preview for prototyping applications.
\section2 Related Information
\list
\li \l{Qt Quick Controls}
+\li \l{Customizing Qt Quick Controls}
\li \l{Qt Quick}
+\li \l{Qt Design Studio Manual}
\endlist
\omit
@@ -88,12 +94,17 @@ qt_add_qml_module(my_module
All QML files listed under \c {QML_FILES} will automatically get compiled \l {Ahead-of-Time Compilation}{ahead of time}.
+You should keep the QML files in the same directory as the CMakeLists.txt with
+the qt_add_qml_module. Otherwise their \l{The Implicit Import}{implicit imports}
+will be different from the \l{QML Modules} they belong to. This is a frequent
+source of mistakes.
+
\section2 Related Information
\list
\li \l{The Qt Resource System}
\endlist
-\section1 Separate UI from Logic
+\section1 Separate UI from Business Logic
One of the key goals that most application developers want to achieve is to
create a maintainable application. One of the ways to achieve this goal is
@@ -109,8 +120,8 @@ reasons why an application's UI should be written in QML:
\li JavaScript can easily be used in QML to respond to events.
\endlist
-Being a strongly typed language, C++ is best suited for an application's logic.
-Typically, such code performs tasks such as complex calculations
+Being a strongly typed language, C++ is best suited for an application's
+business logic. Typically, such code performs tasks such as complex calculations
or data processing, which are faster in C++ than QML.
Qt offers various approaches to integrate QML and C++ code in an application.
@@ -154,6 +165,22 @@ see \l {Choosing the Correct Integration Method Between C++ and QML}.
\li \l{Qt Quick Controls - Chat Tutorial}{Chat application tutorial}
\endlist
+\section1 Using Qt Design Studio
+
+Qt Design Studio uses UI files that have the filename extension \e {.ui.qml}
+to separate the visual parts of the UI from the UI logic you implement in
+\e {.qml} files. You should edit UI files only in the \uicontrol {2D} view in
+Qt Design Studio. If you use some other tool to add code that Qt Design Studio
+does not support, it displays error messages. Fix the errors to enable visual
+editing of the UI files again. Typically, you should move the unsupported code
+to a \e {.qml} file.
+
+\section2 Related Information
+
+\list
+ \li \l{Qt Design Studio: UI Files}
+\endlist
+
\section1 Using Qt Quick Layouts
Qt offers Qt Quick Layouts to arrange Qt Quick items visually in a layout.
@@ -228,7 +255,7 @@ property MyMenu optionsMenu
\section1 Performance
For information on performance in QML and Qt Quick,
-see \l {Performance Considerations And Suggestions}.
+see \l {QML Performance Considerations And Suggestions}.
\section1 Prefer Declarative Bindings Over Imperative Assignments
diff --git a/src/quick/doc/src/guidelines/qtquick-tool-qmllint.qdoc b/src/quick/doc/src/guidelines/qtquick-tool-qmllint.qdoc
deleted file mode 100644
index 0a9d6dbc58..0000000000
--- a/src/quick/doc/src/guidelines/qtquick-tool-qmllint.qdoc
+++ /dev/null
@@ -1,140 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
-
-/*!
-\page qtquick-tool-qmllint.html
-\title qmllint
-\brief A tool for verifying the syntax of QML files and warning about
-anti-patterns.
-
-\e qmllint is a tool shipped with Qt, that verifies the syntatic validity of
-QML files.
-It also warns about some QML anti-patterns. If you want to disable a specific
-warning type, you can find the appropriate flag for doing so by passing
-\c{--help} on the command line.
-
-By default, some issues will result in warnings that will be printed and result
-in a non-zero exit code.
-Minor issues however (such as unused imports) are just informational messages
-by default and will not affect the exit code.
-qmllint is very configurable and allows for disabling warnings or changing how
-they are treated.
-Users may freely turn any issue into a warning, informational message, or
-disable them outright.
-
-qmllint warns about:
-\list
- \li Unqualified accesses of properties
- \li Usage of signal handlers without a matching signal
- \li Usage of with statements in QML
- \li Issues related to compiling QML code
- \li Unused imports
- \li Deprecated components and properties
- \li And many other things
-\endlist
-
-\note In order for qmllint to work properly, it requires type information.
-That information is provided by QML modules in the import paths.
-The current directory, as well as the import paths for Qt's built-in types,
-are used as import paths by default.
-To add more import paths not included in the default,
-add them via the \c{-I} flag.
-
-To get an overview and explanation of all available command line options, run \c{qmllint --help}.
-
-\section2 Compiler warnings
-
-qmllint can warn you about code that cannot be compiled by \l{qmlsc}.
-
-These warnigs are not enabled by default. In order to enable them specify
-\c{--compiler warning} or adjust your settings file accordingly.
-
-\section2 Marking components and properties as deprecated
-
-qmllint allows you to mark both properties and components as deprecated:
-
-\code
-@Deprecated { reason: "Use NewCustomText instead" }
-Text {
- @Deprecated { reason: "Use newProperty instead" }
- property int oldProperty
- property int newProperty
- Component.onCompleted: console.log(oldProperty); // Warning: XY.qml:8:40: Property "oldProperty" is deprecated (Reason: Use newProperty instead)
-}
-\endcode
-
-Deprecation warnings for components will be shown every time the component is created.
-
-\section2 Disabling warnings inline
-
-You may at any point disable warnings temporarily in a file using \c{// qmllint
-disable}.
-
-You can do this at the end of a line when a single line produces warnings:
-
-\code
-Item {
- property string foo
- Item {
- property string bar: foo // qmllint disable unqualified
- }
-}
-\endcode
-
-Alternatively you can disable comments for a block of lines by putting the
-comment in a line only containing \c{// qmllint disable}, ending the block with
-\c{// qmllint enable}:
-
-\code
-Item {
- property string foo
- Item {
- // qmllint disable unqualified
- property string bar: foo
- property string bar2: foo
- // qmllint enable unqualified
- }
-}
-\endcode
-
-qmllint interprets all single line comments starting with \c {qmllint} as
-directives. Thus you may not start a comment that way unless you wish to enable
-or disable warnings.
-
-\note As done in the examples above it is preferable to explicitly specify the
-warning or a list of warnings you want to disable instead of disabling all
-warnings. This can be done by simply listing warning categories after \c{qmllint disable} (the names are
-the same as the options listed in \c{--help}).
-
-\section2 Settings
-
-In addition to passing command-line options, you can also
-configure qmllint via a settings file.
-The command line \c{--write-defaults} will generate one for you.
-
-Setting files are named \c{.qmllint.ini} and look like this:
-
-\quotefile qmllint/config.ini
-
-Warning levels may be set to \c{info}, \c{warning} or \c{disable} just as with
-command line options.
-
-qmllint will automatically look for a settings file at the location of the qml
-file that is being linted.
-It also looks through all parent directories to find this file and
-automatically applies the settings therein. You can disable this behavior by
-using \c{--ignore-settings}.
-You may always override these defaults by specifying command line parameters
-that take precedence over the warning levels in settings.
-
-\section2 Scripting
-
-qmllint can write or output JSON via the \c{--json <file>} option which will return valid JSON
-with warning messages, file and line location of warnings, and their severity
-level. Use the special filename '-' to write to stdout instead of a file.
-This can be used to more easily integrate qmllint in your pre-commit hooks or
-CI testing.
-
-\sa {Type Description Files}{qmltypes}
-\sa {QtQuick Tools and Utilities}{qtquick-tools-and-utilities}
-*/
diff --git a/src/quick/doc/src/guidelines/qtquick-toolsnutilities.qdoc b/src/quick/doc/src/guidelines/qtquick-toolsnutilities.qdoc
index 50b929e0ed..17f6c31238 100644
--- a/src/quick/doc/src/guidelines/qtquick-toolsnutilities.qdoc
+++ b/src/quick/doc/src/guidelines/qtquick-toolsnutilities.qdoc
@@ -24,6 +24,12 @@ UIs using simple drag-n-drop gestures that most designers are familiar with.
It offers UI elements from the Qt Quick and Qt Quick Controls modules, as well
as integration for custom UI elements.
+\section1 Qt Quick Effect Maker (QQEM)
+
+\l{\QQEM} is a tool for creating shader effects for Qt Quick with
+high productivity and performance. You can run Qt Quick Effect Maker as a
+standalone tool.
+
\section1 QML Debugger
The \l{Qt Creator: QML Debugger}{QML Debugger} is a very useful utility that
@@ -69,7 +75,7 @@ tests are setup, UI tests are a lot easier to run.
\section1 qmllint
-\l{qtquick-tool-qmllint.html}{qmllint} is a tool shipped with Qt, that verifies
+\l{qmllint Reference}{qmllint} is a tool shipped with Qt, that verifies
the syntatic validity of QML files. It also warns about some QML anti-patterns.
If you want to disable a specific warning type, you can find the appropriate
flag for doing so by passing \c{--help} on the command line.
@@ -98,7 +104,17 @@ all the available options.
The Qt Quick Compiler consist of two components:
\list
- \li \l {QML Type Compiler}
- \li \l {QML Script Compiler}
+ \li \l {QML type compiler}
+ \li \l {QML script compiler}
\endlist
+
+\section1 \QMLLS
+
+\l{\QMLLS Reference}{\QMLLS} is a tool shipped with Qt that helps you edit
+QML code in your favorite (LSP-compatible) editor.
+
+\section1 SVG to QML converter [tech preview]
+
+The \l{svgtoqml} tool converts an SVG document to a QML file that can be used as a component.
+
*/
diff --git a/src/quick/doc/src/includes/item.qdocinc b/src/quick/doc/src/includes/item.qdocinc
index 4cb2652bbf..733589be5d 100644
--- a/src/quick/doc/src/includes/item.qdocinc
+++ b/src/quick/doc/src/includes/item.qdocinc
@@ -3,6 +3,9 @@
The following properties of the item are used in the mapping:
\l x, \l y, \l scale, \l rotation, \l transformOrigin, and \l [QML]{Item::}{transform}.
+If the items are part of different scenes, the mapping includes
+the relative position of the two scenes.
+
//! [mapping]
@@ -13,3 +16,4 @@ accept the event if you reimplement this function. If you don't accept the
event, call \c event->ignore().
//! [accepting-events]
+
diff --git a/src/quick/doc/src/includes/layout.qdocinc b/src/quick/doc/src/includes/layout.qdocinc
index 723fbc515a..a36f05ee4f 100644
--- a/src/quick/doc/src/includes/layout.qdocinc
+++ b/src/quick/doc/src/includes/layout.qdocinc
@@ -14,5 +14,7 @@
\li \l{Layout::rightMargin}{Layout.rightMargin}
\li \l{Layout::topMargin}{Layout.topMargin}
\li \l{Layout::bottomMargin}{Layout.bottomMargin}
+\li \l{Layout::horizontalStretchFactor}{Layout.horizontalStretchFactor}
+\li \l{Layout::verticalStretchFactor}{Layout.verticalStretchFactor}
//! [attached-properties]
diff --git a/src/quick/doc/src/internal/deliverMatchingPointsToItem.puml b/src/quick/doc/src/internal/deliverMatchingPointsToItem.puml
new file mode 100644
index 0000000000..6a0fa04717
--- /dev/null
+++ b/src/quick/doc/src/internal/deliverMatchingPointsToItem.puml
@@ -0,0 +1,14 @@
+\startuml
+partition "deliverMatchingPointsToItem" {
+ :localizePointerEvent;
+ :QQuickItemPrivate::handlePointerEvent;
+ repeat :loop over extra->pointerHandlers;
+ :QQuickPointerHandler::handlePointerEvent;
+ :wantsPointerEvent;
+ repeat :loop over QEventPoints;
+ :wantsEventPoint;
+ repeat while (more?) is (yes)
+ :handlePointerEventImpl;
+ repeat while (more?) is (yes)
+}
+\enduml
diff --git a/src/quick/doc/src/internal/ideal-pointer-event-delivery-single-drag.dox b/src/quick/doc/src/internal/ideal-pointer-event-delivery-single-drag.dox
new file mode 100644
index 0000000000..4a8b8eef43
--- /dev/null
+++ b/src/quick/doc/src/internal/ideal-pointer-event-delivery-single-drag.dox
@@ -0,0 +1,242 @@
+/*! \internal
+ \page qq-ideal-pointer-event-delivery-single-drag Dragging one DragHandler with one touchpoint
+ \tableofcontents
+
+ <a href="https://doc.qt.io/qt-6/qtquick-input-topic.html">Multi-touch</a>
+ is intended to be a strong feature in Qt Quick, so let's run this example
+ on a touchscreen:
+
+ \snippet pointerHandlers/pinchAndDragHandlers.qml entire
+
+ The intended behavior is that we have three Rectangles that can be dragged,
+ and you can alternatively perform a pinch gesture on the parent Rectangle
+ to scale and rotate it. The object instances involved look like this:
+
+ \dotfile pinchAndDragHandlers.dot "pinch and drag handlers"
+
+ (In these diagrams, â„š is a shortcut for QQuick, to save space.
+ ResizeItemToWindow comes from \c qtdeclarative/tools/qml/ResizeItemToWindow.qml
+ which is a resource in the
+ <a href="https://doc.qt.io/qt-6/qtquick-qml-runtime.html">qml executable</a>
+ which wraps our top-level Rectangle into a Window.)
+
+ \section qq-ideal-pointer-event-delivery-press-draghandler-prep Touch press on a DragHandler: preparation
+
+ Let's start with the scenario that you attempt to drag one Rectangle
+ with one finger. A QTouchEvent arrives, it contains a single QEventPoint
+ representing the single finger, and we have to decide which items and
+ handlers we're going to visit.
+
+ \image html pinchAndDragHandlers-singlePressPrep.svg "touch press event delivery: preparation"
+
+ Since Qt 5.8 (change ccc5c54602821761a2f1a42c4bc473afd53439c9), we stopped
+ doing ad-hoc recursive delivery of touch and mouse events: we wanted to
+ ensure that delivery is deterministic (in spite of what user code may do to
+ the parent hierarchy during delivery), so we first build a list of Items to
+ visit, in QQuickDeliveryAgentPrivate::pointerTargets() (which is recursive
+ itself). This is somewhat expensive, but fortunately we only need to do
+ that when handling the event that begins a gesture, such as a press event.
+ A press event is a pointer event in which \e any QEventPoint has the
+ \c Pressed QEventPoint::state.
+
+ But how do we decide which items are relevant and need to be visited?
+ First, the QEventPoint must fall within the item's bounds, so we need to
+ call QQuickItem::mapFromScene() to localize from the scene (window)
+ coordinates to item coordinates, and then QQuickItem::contains() to check
+ whether it's inside. (QQuickItem::contains() is virtual so that QQuickShape
+ can be non-rectangular; also, any Item can have a
+ QQuickItem::containmentMask() to declare non-rectangular bounds.) Then, we
+ call QQuickItemPrivate::anyPointerHandlerWants() which calls
+ QQuickPointerHandler::wantsEventPoint() on each of the item's pointer
+ handlers: if any handler wants the eventpoint, we need to visit that item.
+ Otherwise, if QQuickItem::acceptTouchEvents() returns false, we do \e not
+ need to visit that item. Thus, pointerTargets() pre-visits all items in the
+ scene to build the list of potential targets.
+
+ \section qq-ideal-pointer-event-delivery-press-draghandler Touch press on a DragHandler: delivery to targets
+
+ After building the list, we are prepared to begin actual delivery of the
+ press event. QQuickDeliveryAgentPrivate::deliverPressOrReleaseEvent() loops
+ over \c targetItems and calls
+ QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem(). (Parent-item
+ filtering could have intercepted it before that; but as stated, we're
+ neglecting that complication for now.)
+
+ \image html pinchAndDragHandlers-singlePressDelivery.svg "touch press event actual delivery"
+
+ For each item in \c targetItems, again we need to call mapFromScene() and
+ set QEventPoint::position() to item-local coordinates:
+ QQuickDeliveryAgentPrivate::localizePointerEvent() takes care of that.
+ We always let Pointer Handlers handle the event before the Item itself
+ (because this allows a handler to override or augment behavior that a C++
+ QQuickItem subclass has in its QQuickItem::touchEvent() function: another
+ complication that we're neglecting for now). The implementation for that
+ is in QQuickItemPrivate::handlePointerEvent(): it simply loops over any
+ handlers that are found in the list QQuickItemPrivate::ExtraData::pointerHandlers
+ and calls QQuickPointerHandler::handlePointerEvent() on each of those.
+ That's not a virtual function; but it calls wantsPointerEvent()
+ again, and then handlePointerEventImpl() which \e is virtual.
+
+ PinchHandler would get the event first if it was relevant, because
+ pointerTargets() was a preorder traversal, so parents come before children
+ in the \c targetItems list. But QQuickMultiPointHandler::wantsPointerEvent()
+ has already returned false, because QQuickMultiPointHandler::eligiblePoints()
+ has only found one point, and QQuickMultiPointHandler::minimumPointCount()
+ is 2 by default. (You might be wondering, what if the user presses a
+ second finger later to start the pinch gesture? We'll get to that below.)
+ So it can be skipped; next in \c targetItems should be a Rectangle whose
+ bounds contain QEventPoint::position() \e and that has a DragHandler.
+ By default, one point is enough for a DragHandler: its inherited
+ QQuickMultiPointHandler::wantsPointerEvent() has already returned \c true,
+ and QQuickMultiPointHandlerPrivate::currentPoints is already storing
+ information about the QEventPoints that it wants to handle.
+ (QEventPoint::id() is guaranteed to remain constant during one gesture:
+ once pressed, the same finger keeps manipulating the same QEventPoint.
+ In \c currentPoints[0], QQuickHandlerPoint::id() remembers it.)
+ So QQuickDragHandler::handlePointerEventImpl() can immediately iterate
+ \c currentPoints, find the QEventPoint that has the same ID; and then
+ it calls QQuickPointerHandler::setPassiveGrab(), because DragHandler
+ needs to monitor the position of that point. The drag gesture will not
+ begin until the point is dragged a distance in pixels greater than
+ QStyleHints::startDragDistance(), and it's not appropriate for DragHandler
+ to take the exclusive grab of that touchpoint until it's sure the user
+ really means to drag. (What if the same Rectangle also had a TapHandler?
+ The user could either drag, or tap without dragging; but at the time of the
+ press, it's ambiguous, so both handlers would need their own grabs, to express
+ interest in monitoring that touchpoint.) But without any grab, the handler
+ would not be visited again when the next event occurs: a touchpoint
+ movement or release.
+
+ We've omitted details about the meaning of QEventPoint::accepted() and
+ QEvent::accepted() flags. Pointer handlers need to take grabs explicitly:
+ that helps to remove ambiguity about the consequences of the \c accepted flags.
+
+ \section qq-ideal-pointer-event-delivery-move-draghandler Touch move on a DragHandler: delivery to grabber
+
+ \image html pinchAndDragHandlers-drag-one-rect.png "dragging one rectangle via touch"
+
+ So we're done handling the press; now let's try to start dragging.
+
+ Let's say the user's finger quickly moves far enough on the touchscreen to
+ generate a single QTouchEvent with a delta greater than the drag threshold.
+
+ Again, QGuiApplicationPrivate::processTouchEvent() handles the next QPA
+ touch event (QWindowSystemInterfacePrivate::TouchEvent). For each
+ touchpoint (for each finger being held down), it calls
+ QPointingDevicePrivate::pointById() to retrieve the
+ QPointingDevicePrivate::EventPointData that was stored previously when the
+ press event was delivered, and updates its stored state, including the
+ QEventPoint instance, to be current for this incoming move event.
+ QEventPoint::globalPressPosition() is not updated though: it continues to
+ hold the position at which the press occurred; therefore, any object that
+ ends up handing the moving point can check to see how far it moved since
+ press. Likewise, QEventPoint::pressTimestamp() holds the time at which it
+ was pressed.
+
+ \image html pinchAndDragHandlers-singleMoveDelivery.svg "touch move event delivery"
+
+ Another QTouchEvent instance is stack-allocated, with type
+ QEvent::TouchUpdate, in which \c point(0) is a QEventPoint (with state
+ QEventPoint::Updated) with QEventPoint::scenePosition() being the current
+ finger position in the window, while QEventPoint::scenePressPosition()
+ remembers where it was pressed during the previous press event. It's sent
+ to the application via QGuiApplication::sendSpontaneousEvent(), then to
+ â‘  QQuickWindow::event(), which dispatches to â‘¡ QQuickDeliveryAgent::event().
+ â‘¢ QQuickDeliveryAgentPrivate::deliverPointerEvent() calls
+ â‘£ QQuickDeliveryAgentPrivate::deliverUpdatedPoints(), which (among other
+ things) iterates the QEventPoints, and for each of those, iterates the
+ passive grabbers in QPointingDevicePrivate::EventPointData::passiveGrabbers
+ and calls ⑤ QQuickDeliveryAgentPrivate::deliverToPassiveGrabbers(). It uses
+ ⑥ QQuickDeliveryAgentPrivate::localizePointerEvent() to ⑦ map
+ QEventPoint::position() to the passive-grabbing DragHandler's parent item's
+ coordinate system, and calls ⑧ QQuickPointerHandler::handlePointerEvent().
+ ⑨ QQuickMultiPointHandler::wantsPointerEvent() returns \c true because all
+ the same QQuickMultiPointHandlerPrivate::currentPoints still exist in this
+ QTouchEvent (with no points left over); so
+ â‘© QQuickDragHandler::handlePointerEventImpl() is called. For each point, it
+ calculates the movement delta \c (scenePosition() - scenePressPosition());
+ ⑪ QQuickPointerHandlerPrivate::dragOverThreshold() checks whether it's moved
+ far enough to activate dragging. (If multiple points were being dragged,
+ handlePointerEventImpl() would also check whether they are all being
+ dragged in approximately the same direction.) It did move far enough, and
+ now DragHandler knows it should take responsibility for this gesture:
+ apparently the user is really trying to drag its parent item, the
+ Rectangle. It calls â‘« QQuickMultiPointHandler::grabPoints() to try to take
+ the exclusive grab. Nothing is interfering with that, so the attempt
+ succeeds and returns \c true; therefore, it's ok to call
+ ⑬ QQuickPointerHandler::setActive(), which triggers
+ â‘­ QQuickDragHandler::onActiveChanged(), which updates some internal state
+ etc.; and then â‘® emits the activeChanged() signal. In our example, the
+ Rectangle has a binding to ⑯ change color when the DragHandler becomes
+ active. And since by default, DragHandler's \c target is the same as its
+ \c parent, QQuickDragHandler::handlePointerEventImpl() ends with a call to
+ â‘° QQuickMultiPointHandler::moveTarget(). That uses QMetaProperty::write()
+ to ⑱ change the Rectangle's \c x and \c y properties; the reason we do it
+ that way is in case property value interceptors (BoundaryRule or Behavior)
+ are in use. And so the Rectangle moves as far as the finger is dragged.
+
+ \section qq-ideal-pointer-event-delivery-release-draghandler Touch release: delivery to grabber
+
+ Now let's say there's a QPA event with
+ QWindowSystemInterface::TouchPoint::state being QEventPoint::Released.
+
+ \image html pinchAndDragHandlers-singleReleaseDelivery.svg "touch release event delivery"
+
+ It's processed like the touch move: the persistent QEventPoint is updated
+ with current values again, and another QTouchEvent instance is
+ stack-allocated, with type QEvent::TouchEnd, and â‘  sent to the window and â‘¡
+ the delivery agent. When it gets to â‘¢ QQuickDeliveryAgentPrivate::deliverPointerEvent(),
+ â‘£ deliverUpdatedPoints() is called first (same as for the move). As usual,
+ the event is not given to the item or its handlers until it's been ⑤
+ localized to the item's coordinate system.
+
+ â‘¥ QQuickPointerHandler::handlePointerEvent() calls
+ ⑦ QQuickMultiPointHandler::wantsPointerEvent(), which is able to see that the
+ point it's tracking (in QQuickMultiPointHandlerPrivate::currentPoints) is
+ no longer eligible because it's been released; so it calls
+ ⑧ QQuickPointerHandler::setActive() with \c false immediately, which calls
+ ⑨ onActiveChanged() and emits the ⑩ activeChanged signal. (Thus our Rectangle
+ ⑪ changes its color again.) QQuickPointerHandler::handlePointerEvent() then
+ calls QPointerEvent::setExclusiveGrabber() with \c nullptr to give up its
+ exclusive grab. â‘« QPointingDevice::grabChanged() is emitted.
+ QQuickDeliveryAgentPrivate::onGrabChanged() handles that signal, and calls
+ ⑬ QQuickDragHandler::onGrabChanged(), which has minimal consequences in this
+ case. (QQuickPointerHandler::onGrabChanged() calls
+ QQuickPointerHandler::setActive() with \c false again: it's a failsafe that
+ some other scenarios rely on.)
+
+ \section qq-ideal-pointer-event-delivery-touch-summary Touch delivery activity diagrams
+
+ So let's generalize the functionality we've covered so far.
+ As we'll see later, mouse events are treated a bit differently;
+ but ideally it would be the same: a mouse event is just a QPointerEvent
+ that comes from a different device, containing only one QEventPoint,
+ just like our single-finger touch event.
+
+ A begin (press) event goes to deliverPressOrReleaseEvent(), and then
+ if the QEventPoints weren't all accepted, it goes to deliverUpdatedPoints().
+
+ An update (move) event goes to deliverUpdatedPoints() only.
+
+ An end (release) event goes to deliverUpdatedPoints() and then
+ deliverPressOrReleaseEvent().
+
+ \startuml
+ !include ideal-pointer-event-delivery.puml
+ \enduml
+
+ QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem() is called from
+ two places (deliverPressOrReleaseEvent() and deliverUpdatedPoints()), so
+ it's shown in a separate activity diagram:
+
+ \startuml
+ !include deliverMatchingPointsToItem.puml
+ \enduml
+
+ In conclusion, we've seen the details of touch event dispatching for one
+ short drag gesture to one DragHandler. In practice, Pointer Handlers are
+ still not the most common way to handle pointer events (even if we'd like
+ to end up there eventually): there are a lot of legacy QQuickItem
+ subclasses that do their event handling by overriding virtual functions
+ rather than by having handlers added. But let's save that for later.
+*/
diff --git a/src/quick/doc/src/internal/ideal-pointer-event-delivery.dox b/src/quick/doc/src/internal/ideal-pointer-event-delivery.dox
new file mode 100644
index 0000000000..15d96b0e10
--- /dev/null
+++ b/src/quick/doc/src/internal/ideal-pointer-event-delivery.dox
@@ -0,0 +1,22 @@
+/*! \internal
+ \mainpage Qt Quick Pointer Event Delivery
+
+ QPointerEvent instances are stack-allocated in
+ QGuiApplicationPrivate::processMouseEvent(),
+ QGuiApplicationPrivate::processTouchEvent() etc., and sent to the
+ application via QCoreApplication::sendSpontaneousEvent() (taking QWindow
+ and QEvent pointer arguments). If the window is a QQuickWindow, the QTouchEvent
+ arrives to QQuickWindow::event() which then dispatches to the
+ QQuickDeliveryAgent. QQuickDeliveryAgent contains much of the event
+ delivery code. In fact, it's quite complex, for legacy reasons such as
+ touch->mouse synthesis in three possible layers, handling events in
+ QQuickItem subclasses in C++, event compression, multiple kinds of event
+ filtering, drag-and-drop, dealing with popup menus and so on; but let's
+ start with the ideal case: no synthesis, no filtering, no popups, and all
+ events are handled in
+ <a href="https://doc.qt.io/qt-6/qtquickhandlers-index.html">Pointer Handlers</a>.
+
+ Here are some "ideal" scenarios:
+
+ - \subpage qq-ideal-pointer-event-delivery-single-drag
+*/
diff --git a/src/quick/doc/src/internal/ideal-pointer-event-delivery.puml b/src/quick/doc/src/internal/ideal-pointer-event-delivery.puml
new file mode 100644
index 0000000000..1d730b6f34
--- /dev/null
+++ b/src/quick/doc/src/internal/ideal-pointer-event-delivery.puml
@@ -0,0 +1,55 @@
+\startuml
+start
+:QGuiApplicationPrivate::process[Mouse|Touch|Tablet]Event();
+:QGuiApplication::sendSpontaneousEvent();
+:â„šWindow::event();
+:â„šDeliveryAgent::event();
+partition â„šDeliveryAgentPrivate::deliverPointerEvent() {
+ if (isBeginEvent()) then (yes)
+ partition "deliverPressOrReleaseEvent()" {
+ repeat :loop over QEventPoints;
+ :â„šDeliveryAgentPrivate::pointerTargets();
+ repeat while (more?) is (yes)
+ ->no;
+ repeat :loop over targetItems;
+ :**deliverMatchingPointsToItem()**;
+ repeat while (more?) is (yes)
+ ->no;
+ }
+ endif
+ if (allUpdatedPointsAccepted()) then (no)
+ partition "deliverUpdatedPoints()" {
+ repeat :loop over exclusive grabbers;
+ if (grabber type?) then (pointer handler)
+ :â„šPointerHandler::handlePointerEvent();
+ else (Item)
+ :**deliverMatchingPointsToItem()**;
+ endif
+ repeat while (more grabbers?) is (yes)
+ repeat :loop over QEventPoints;
+ partition "deliverToPassiveGrabbers()" {
+ repeat :loop over passive grabbers;
+ if (grabber type?) then (pointer handler)
+ :â„šPointerHandler::handlePointerEvent();
+ else (Item)
+ :QCoreApplication::sendEvent(item, event);
+ endif
+ repeat while (more grabbers?) is (yes)
+ }
+ repeat while (more points?) is (yes)
+ }
+ endif
+ if (isEndEvent()) then (yes)
+ partition "deliverPressOrReleaseEvent()" {
+ repeat :loop over QEventPoints;
+ :â„šDeliveryAgentPrivate::pointerTargets();
+ repeat while (more?) is (yes)
+ ->no;
+ repeat :loop over targetItems;
+ :**deliverMatchingPointsToItem()**;
+ repeat while (more?) is (yes)
+ }
+ endif
+}
+stop
+\enduml
diff --git a/src/quick/doc/src/internal/pinchAndDragHandlers-singleMoveDelivery.svg b/src/quick/doc/src/internal/pinchAndDragHandlers-singleMoveDelivery.svg
new file mode 100644
index 0000000000..3a69414d83
--- /dev/null
+++ b/src/quick/doc/src/internal/pinchAndDragHandlers-singleMoveDelivery.svg
@@ -0,0 +1,851 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+
+<!-- Pages: 1 -->
+
+<svg
+ width="737.57623pt"
+ height="332.46246pt"
+ viewBox="0 0 737.65964 332.46246"
+ version="1.1"
+ id="svg806"
+ sodipodi:docname="pinchAndDragHandlers-singleMoveDelivery.svg"
+ inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs810">
+ <marker
+ style="overflow:visible"
+ id="marker1576"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Arrow5"
+ markerWidth="5.8874259"
+ markerHeight="6.6094756"
+ viewBox="0 0 5.8874262 6.6094758"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ transform="scale(0.5)"
+ style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
+ d="m 6,0 c -3,1 -7,3 -9,5 0,0 0,-4 2,-5 -2,-1 -2,-5 -2,-5 2,2 6,4 9,5 z"
+ id="path1574" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="marker1576-4"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Arrow5"
+ markerWidth="5.8874259"
+ markerHeight="6.6094756"
+ viewBox="0 0 5.8874262 6.6094758"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ transform="scale(0.5)"
+ style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
+ d="m 6,0 c -3,1 -7,3 -9,5 0,0 0,-4 2,-5 -2,-1 -2,-5 -2,-5 2,2 6,4 9,5 z"
+ id="path1574-4" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="Arrow5"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Arrow5"
+ markerWidth="5.8874264"
+ markerHeight="6.6094756"
+ viewBox="0 0 5.8874262 6.6094758"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ transform="scale(0.5)"
+ style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
+ d="m 6,0 c -3,1 -7,3 -9,5 0,0 0,-4 2,-5 -2,-1 -2,-5 -2,-5 2,2 6,4 9,5 z"
+ id="path8893" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="marker1576-4-5"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Arrow5"
+ markerWidth="5.8874259"
+ markerHeight="6.6094756"
+ viewBox="0 0 5.8874262 6.6094758"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ transform="scale(0.5)"
+ style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
+ d="m 6,0 c -3,1 -7,3 -9,5 0,0 0,-4 2,-5 -2,-1 -2,-5 -2,-5 2,2 6,4 9,5 z"
+ id="path1574-4-3" />
+ </marker>
+ </defs>
+ <sodipodi:namedview
+ id="namedview808"
+ pagecolor="#ffffff"
+ bordercolor="#111111"
+ borderopacity="1"
+ inkscape:showpageshadow="0"
+ inkscape:pageopacity="0"
+ inkscape:pagecheckerboard="1"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:document-units="pt"
+ showgrid="false"
+ inkscape:zoom="2.7028887"
+ inkscape:cx="512.78471"
+ inkscape:cy="236.59872"
+ inkscape:window-width="3840"
+ inkscape:window-height="2139"
+ inkscape:window-x="3840"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg806" />
+ <g
+ id="graph0"
+ class="graph"
+ transform="translate(4,328.48129)">
+ <polygon
+ fill="#ffffff"
+ stroke="none"
+ points="615.07,4 -4,4 -4,-328 615.07,-328 "
+ id="polygon654"
+ transform="matrix(1.1915609,0,0,1,0.76624365,0)" />
+ <!-- llfkmbbephlg -->
+ <g
+ id="node1"
+ class="node"
+ style="stroke-width:1.00011;stroke-dasharray:none">
+ <title
+ id="title656">llfkmbbephlg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="250.78,-288 474.53,-288 474.53,-324 262.78,-324 262.78,-328 250.78,-328 "
+ id="polygon658"
+ style="stroke-width:1.00011;stroke-dasharray:none" />
+ <polyline
+ fill="none"
+ stroke="#000000"
+ points="250.78,-324 262.78,-324"
+ id="polyline660"
+ style="stroke-width:1.00011;stroke-dasharray:none" />
+ <text
+ text-anchor="middle"
+ x="362.66"
+ y="-300.95001"
+ font-family="Times, serif"
+ font-size="6pt"
+ id="text662"
+ style="stroke-width:1.00011;stroke-dasharray:none">ResizeItemToWindow_QMLTYPE_1</text>
+ </g>
+ <!-- llfkmbbeijgg -->
+ <g
+ id="node2"
+ class="node">
+ <title
+ id="title665">llfkmbbeijgg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="322.78,-216 402.53,-216 402.53,-252 322.78,-252 "
+ id="polygon667" />
+ <text
+ text-anchor="middle"
+ x="362.66"
+ y="-229.32001"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text669">â„šRootItem</text>
+ </g>
+ <!-- llfkmbbephlg&#45;&gt;llfkmbbeijgg -->
+ <g
+ id="edge1"
+ class="edge">
+ <title
+ id="title672">llfkmbbephlg-&gt;llfkmbbeijgg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 362.66,-274.81 c 0,7.77 0,15.86 0,22.71"
+ id="path674" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="362.66,-286.8 358.66,-280.8 362.66,-274.8 366.66,-280.8 "
+ id="polygon676" />
+ </g>
+ <!-- llfkmbbepmbg -->
+ <g
+ id="node3"
+ class="node">
+ <title
+ id="title679">llfkmbbepmbg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="245.66,-144 327.66,-144 327.66,-180 245.66,-180 "
+ id="polygon681" />
+ <text
+ text-anchor="middle"
+ x="286.66"
+ y="-157.32001"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text683">â„šRectangle</text>
+ </g>
+ <!-- llfkmbbeijgg&#45;&gt;llfkmbbepmbg -->
+ <g
+ id="edge2"
+ class="edge">
+ <title
+ id="title686">llfkmbbeijgg-&gt;llfkmbbepmbg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 334.28,-206.87 c -9.61,8.86 -20.1,18.52 -28.79,26.52"
+ id="path688" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="343.21,-215.09 336.09,-213.96 334.38,-206.96 341.51,-208.08 "
+ id="polygon690" />
+ </g>
+ <!-- llfkmbclplkg -->
+ <g
+ id="node11"
+ class="node">
+ <title
+ id="title693">llfkmbclplkg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="345.8,-156.44 439.66,-144 533.52,-156.44 533.43,-176.56 345.89,-176.56 "
+ id="polygon695" />
+ <text
+ text-anchor="middle"
+ x="439.66"
+ y="-157.32001"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text697">â„šDeliveryAgent</text>
+ </g>
+ <!-- llfkmbbeijgg&#45;&gt;llfkmbclplkg -->
+ <g
+ id="edge10"
+ class="edge">
+ <title
+ id="title700">llfkmbbeijgg-&gt;llfkmbclplkg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 391.18,-207.07 c 11.28,10.25 23.79,21.62 33.27,30.25"
+ id="path702" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="382.36,-215.09 384.11,-208.09 391.24,-207.02 389.49,-214.01 "
+ id="polygon704" />
+ </g>
+ <!-- llfkmbodpifg -->
+ <g
+ id="node4"
+ class="node">
+ <title
+ id="title707">llfkmbodpifg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="177.32,-95.56 88.66,-108 0,-95.56 0.08,-75.44 177.23,-75.44 "
+ id="polygon709" />
+ <text
+ text-anchor="middle"
+ x="88.660004"
+ y="-85.330002"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text711">â„šPinchHandler</text>
+ </g>
+ <!-- llfkmbbepmbg&#45;&gt;llfkmbodpifg -->
+ <g
+ id="edge3"
+ class="edge">
+ <title
+ id="title714">llfkmbbepmbg-&gt;llfkmbodpifg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 233.33,-142.15 c -34.69,12.27 -78.95,27.92 -109.33,38.66"
+ id="path716" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="244.78,-146.2 237.79,-147.97 233.47,-142.2 240.46,-140.42 "
+ id="polygon718" />
+ </g>
+ <!-- llfkmbnfping -->
+ <g
+ id="node5"
+ class="node">
+ <title
+ id="title721">llfkmbnfping</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="195.66,-72 277.66,-72 277.66,-108 195.66,-108 "
+ id="polygon723" />
+ <text
+ text-anchor="middle"
+ x="236.66"
+ y="-85.330002"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text725">â„šRectangle</text>
+ </g>
+ <!-- llfkmbbepmbg&#45;&gt;llfkmbnfping -->
+ <g
+ id="edge4"
+ class="edge">
+ <title
+ id="title728">llfkmbbepmbg-&gt;llfkmbnfping</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 266.73,-133.1 c -6.04,8.45 -12.48,17.48 -17.85,25"
+ id="path730" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="273.78,-142.96 267.03,-140.41 266.8,-133.2 273.54,-135.76 "
+ id="polygon732" />
+ </g>
+ <!-- llfkmbbdfibg -->
+ <g
+ id="node7"
+ class="node">
+ <title
+ id="title735">llfkmbbdfibg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="295.66,-72 377.66,-72 377.66,-108 295.66,-108 "
+ id="polygon737" />
+ <text
+ text-anchor="middle"
+ x="336.66"
+ y="-85.330002"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text739">â„šRectangle</text>
+ </g>
+ <!-- llfkmbbepmbg&#45;&gt;llfkmbbdfibg -->
+ <g
+ id="edge6"
+ class="edge">
+ <title
+ id="title742">llfkmbbepmbg-&gt;llfkmbbdfibg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 306.59,-133.1 c 6.03,8.45 12.48,17.48 17.85,25"
+ id="path744" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="299.54,-142.96 299.77,-135.76 306.52,-133.2 306.28,-140.41 "
+ id="polygon746" />
+ </g>
+ <!-- llfkmbodeang -->
+ <g
+ id="node9"
+ class="node">
+ <title
+ id="title749">llfkmbodeang</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="440.66,-72 522.66,-72 522.66,-108 440.66,-108 "
+ id="polygon751" />
+ <text
+ text-anchor="middle"
+ x="481.66"
+ y="-85.330002"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text753">â„šRectangle</text>
+ </g>
+ <!-- llfkmbbepmbg&#45;&gt;llfkmbodeang -->
+ <g
+ id="edge8"
+ class="edge">
+ <title
+ id="title756">llfkmbbepmbg-&gt;llfkmbodeang</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 340.14,-141.8 c 31.61,11.35 71.02,25.49 100.22,35.98"
+ id="path758" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="328.86,-145.85 333.16,-140.06 340.16,-141.8 335.86,-147.59 "
+ id="polygon760" />
+ </g>
+ <!-- llfkmbbfjjgg -->
+ <g
+ id="node6"
+ class="node">
+ <title
+ id="title763">llfkmbbfjjgg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="233.07,-23.56 147.66,-36 62.25,-23.56 62.33,-3.44 232.99,-3.44 "
+ id="polygon765" />
+ <text
+ text-anchor="middle"
+ x="147.66"
+ y="-13.33"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text767">â„šDragHandler</text>
+ </g>
+ <!-- llfkmbnfping&#45;&gt;llfkmbbfjjgg -->
+ <g
+ id="edge5"
+ class="edge">
+ <title
+ id="title770">llfkmbnfping-&gt;llfkmbbfjjgg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 204.77,-63.92 c -12.86,10.12 -27.25,21.44 -38.37,30.18"
+ id="path772" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="213.95,-71.14 206.76,-70.57 204.52,-63.72 211.71,-64.29 "
+ id="polygon774" />
+ </g>
+ <!-- llfkmbbfibdg -->
+ <g
+ id="node8"
+ class="node">
+ <title
+ id="title777">llfkmbbfibdg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="422.07,-23.56 336.66,-36 251.25,-23.56 251.33,-3.44 421.99,-3.44 "
+ id="polygon779" />
+ <text
+ text-anchor="middle"
+ x="336.66"
+ y="-13.33"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text781">â„šDragHandler</text>
+ </g>
+ <!-- llfkmbbdfibg&#45;&gt;llfkmbbfibdg -->
+ <g
+ id="edge7"
+ class="edge">
+ <title
+ id="title784">llfkmbbdfibg-&gt;llfkmbbfibdg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 336.66,-58.81 c 0,7.77 0,15.86 0,22.71"
+ id="path786" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="336.66,-70.8 332.66,-64.8 336.66,-58.8 340.66,-64.8 "
+ id="polygon788" />
+ </g>
+ <!-- llfkmbcknglg -->
+ <g
+ id="node10"
+ class="node">
+ <title
+ id="title791">llfkmbcknglg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="611.07,-23.56 525.66,-36 440.25,-23.56 440.33,-3.44 610.99,-3.44 "
+ id="polygon793" />
+ <text
+ text-anchor="middle"
+ x="525.65997"
+ y="-13.33"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text795">â„šDragHandler</text>
+ </g>
+ <!-- llfkmbodeang&#45;&gt;llfkmbcknglg -->
+ <g
+ id="edge9"
+ class="edge">
+ <title
+ id="title798">llfkmbodeang-&gt;llfkmbcknglg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 499.32,-60.9 c 5.62,8.94 11.63,18.5 16.49,26.23"
+ id="path800" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="493.01,-70.93 492.82,-63.72 499.4,-60.77 499.59,-67.98 "
+ id="polygon802" />
+ </g>
+ <g
+ id="g11866"
+ class="edge"
+ transform="rotate(-90,623.51573,-276.02262)">
+ <title
+ id="title11860">llfkmbbephlg-&gt;llfkmbbeijgg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 362.66,-274.81 c 0,7.77 0,15.86 0,22.71"
+ id="path11862" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="366.66,-280.8 362.66,-286.8 358.66,-280.8 362.66,-274.8 "
+ id="polygon11864" />
+ </g>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 708.8157,-33.802688 c 0,-1.93521 -13.67346,-3.50801 -30.51031,-3.50801 -16.83686,0 -30.51503,1.5728 -30.51503,3.50801 0,0 0,31.5378501 0,31.5378501 0,1.93522003 13.67817,3.50801 30.51503,3.50801 16.83685,0 30.51031,-1.57278997 30.51031,-3.50801 0,0 0,-31.5378501 0,-31.5378501"
+ id="path12337"
+ style="stroke-width:0.568213" />
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 708.8157,-33.802688 c 0,1.92838 -13.67346,3.50116 -30.51031,3.50116 -16.83686,0 -30.51503,-1.57278 -30.51503,-3.50116"
+ id="path12339"
+ style="stroke-width:0.568213" />
+ <text
+ text-anchor="middle"
+ x="678.23846"
+ y="-14.011026"
+ font-family="Times, serif"
+ font-size="6pt"
+ id="text12341"
+ style="stroke-width:0.681807">currentPoints</text>
+ <g
+ id="g14256"
+ class="edge"
+ transform="rotate(-90,507.65517,-314.66065)">
+ <title
+ id="title14250">llfkmbbephlg-&gt;llfkmbbeijgg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 362.66,-274.81 c 0,7.77 0,15.86 0,22.71"
+ id="path14252" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="362.66,-286.8 358.66,-280.8 362.66,-274.8 366.66,-280.8 "
+ id="polygon14254" />
+ </g>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 631.59317,-188.30128 c 0,-1.93521 -13.67346,-3.50801 -30.51031,-3.50801 -16.83686,0 -30.51503,1.5728 -30.51503,3.50801 0,0 0,31.53785 0,31.53785 0,1.93522 13.67817,3.50801 30.51503,3.50801 16.83685,0 30.51031,-1.57279 30.51031,-3.50801 0,0 0,-31.53785 0,-31.53785"
+ id="path14258"
+ style="stroke-width:0.568213" />
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 631.59317,-188.30128 c 0,1.92838 -13.67346,3.50116 -30.51031,3.50116 -16.83686,0 -30.51503,-1.57278 -30.51503,-3.50116"
+ id="path14260"
+ style="stroke-width:0.568213" />
+ <text
+ text-anchor="middle"
+ x="601.01593"
+ y="-168.50961"
+ font-family="Times, serif"
+ font-size="6pt"
+ id="text14262"
+ style="stroke-width:0.681807">targetItems</text>
+ <g
+ id="g3826"
+ class="edge"
+ transform="rotate(180,500.1871,-52.761609)">
+ <title
+ id="title3820">llfkmbodeang-&gt;llfkmbcknglg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 499.32,-60.9 c 5.62,8.94 11.63,18.5 16.49,26.23"
+ id="path3822" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="492.82,-63.72 499.4,-60.77 499.59,-67.98 493.01,-70.93 "
+ id="polygon3824" />
+ </g>
+ </g>
+ <g
+ id="g2388"
+ transform="translate(166.85425,118.15234)">
+ <rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.869398;stroke-linecap:square;stroke-dasharray:0.869398, 0.869398;stroke-opacity:1"
+ id="rect981"
+ width="115.87793"
+ height="32.582088"
+ x="-181.46954"
+ y="-115.9365"
+ ry="0"
+ transform="matrix(1,0,-0.22867874,0.97350194,0,0)" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9pt;line-height:125%;font-family:Times, serif;-inkscape-font-specification:'Times, serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ x="-149.44366"
+ y="-93.742676"
+ id="text1897"><tspan
+ sodipodi:role="line"
+ id="tspan1895"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9pt;font-family:Times, serif;-inkscape-font-specification:'Times, serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="-149.44366"
+ y="-93.742676">QGuiApplication</tspan></text>
+ </g>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576)"
+ d="m 508.49821,147.40834 c -5.01114,-15.80414 29.88553,-12.14178 19.22974,-0.30829"
+ id="path2661"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576-4)"
+ d="m 529.10578,290.07508 c 2.22687,-20.59627 43.00062,-73.20365 2.77101,-64.44971"
+ id="path9238"
+ sodipodi:nodetypes="cc" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="450.67688"
+ y="193.66904"
+ id="text9282"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="450.67688"
+ y="193.66904"
+ id="tspan9280">⑦ mapFromScene()</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="497.35483"
+ y="100.48878"
+ id="text9334"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="497.35483"
+ y="100.48878"
+ id="tspan9332">â‘¢ deliverPointerEvent()</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="497.35483"
+ y="111.44315"
+ id="tspan1575">â‘£ deliverUpdatedPoints()</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="497.35483"
+ y="122.39753"
+ id="tspan1577">⑤ deliverToPassiveGrabbers()</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="497.35483"
+ y="133.3519"
+ id="tspan9365">â‘¥ localizePointerEvent()</tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576-4)"
+ d="m 443.986,186.10711 c 3.61207,29.57315 14.20183,7.52734 15.4725,29.56066"
+ id="path9427"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576)"
+ d="m 546.28994,291.07097 c -3.52959,-16.19952 30.87947,-9.32879 19.17598,1.46963"
+ id="path14423"
+ sodipodi:nodetypes="cc" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="557.57617"
+ y="233.58138"
+ id="text14441"><tspan
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="557.57617"
+ y="233.58138"
+ id="tspan14459"
+ sodipodi:role="line">â‘© handlePointerEventImpl()</tspan><tspan
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="557.57617"
+ y="244.53575"
+ sodipodi:role="line"
+ id="tspan2285">⑪ dragOverThreshold()</tspan><tspan
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="557.57617"
+ y="255.49011"
+ sodipodi:role="line"
+ id="tspan2287">â‘« grabPoints()</tspan><tspan
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="557.57617"
+ y="266.44449"
+ sodipodi:role="line"
+ id="tspan2289">⑬ setActive()</tspan><tspan
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="557.57617"
+ y="277.39886"
+ sodipodi:role="line"
+ id="tspan2603">â‘° moveTarget()</tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.03579;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.03579, 1.03579;stroke-dashoffset:0;stroke-opacity:1;marker-start:url(#Arrow5)"
+ d="M 249.32923,23.153194 H 125.81655"
+ id="path1311" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="129.35161"
+ y="18.770849"
+ id="text1315"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="129.35161"
+ y="18.770849"
+ id="tspan1317">â‘  event(QTouchEvent*)</tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.03579;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.03579, 1.03579;stroke-dashoffset:0;stroke-opacity:1;marker-start:url(#Arrow5)"
+ d="M 480.13219,146.11428 434.01617,42.608119"
+ id="path2865"
+ sodipodi:nodetypes="cc" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="224.50352"
+ y="-380.62888"
+ id="text2981"
+ transform="rotate(65.571486)"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="224.50352"
+ y="-380.62888"
+ id="tspan2979">â‘¡ event(QTouchEvent*)</tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576-4-5)"
+ d="m 442.97658,187.15842 c -50.02267,78.60672 40.30509,72.83709 29.70231,109.51513"
+ id="path9238-5"
+ sodipodi:nodetypes="cc" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:end;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:end;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="496.79193"
+ y="226.98381"
+ id="text9304-6"
+ transform="rotate(6.9725705)"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:end;text-anchor:end;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="496.79193"
+ y="226.98381"
+ id="tspan9302"> ⑧ handlePointerEvent()</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:end;text-anchor:end;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="496.79193"
+ y="237.93819"
+ id="tspan2397">⑨ wantsPointerEvent()</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:end;text-anchor:end;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="496.79193"
+ y="247.93932"
+ id="tspan2371" /></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="639.57428"
+ y="275.21478"
+ id="text2293"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="639.57428"
+ y="275.21478"
+ id="tspan2291">â‘­ onActiveChanged()</tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576-4)"
+ d="m 625.08309,266.11561 c 33.3115,8.38416 -25.30249,21.02634 -26.36628,32.66492"
+ id="path2399"
+ sodipodi:nodetypes="cc" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="537.05072"
+ y="198.02666"
+ id="text2575"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="537.05072"
+ y="198.02666"
+ id="tspan2573">⑮ activeChanged Ⓢ</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="537.05072"
+ y="208.98103"
+ id="tspan4018">⑯ setColor()</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="537.05072"
+ y="219.93539"
+ id="tspan2601">⑱ setX() / setY()</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#808080;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="text3431"
+ transform="rotate(-38.038218,476.61944,239.40311)"><textPath
+ xlink:href="#path9238-5"
+ id="textPath3609"><tspan
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#808080;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ id="tspan3429">QTouchEvent*</tspan></textPath></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="text3832"
+ transform="rotate(58.431196)"
+ x="474.64865"
+ y="-273.09793"><tspan
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ id="tspan3828">target</tspan></text>
+ <circle
+ id="path4083"
+ style="fill:#000000;stroke:#e6a11b;stroke-width:0.750085"
+ cx="536.95331"
+ cy="217.47479"
+ r="0.0092422031" />
+ <circle
+ id="path4109"
+ style="fill:#000000;stroke:#e6a11b;stroke-width:0.750085"
+ cx="550.58368"
+ cy="218.91232"
+ r="0.0092422031" />
+ <circle
+ id="path4135"
+ style="fill:#000000;stroke:#e6a11b;stroke-width:0.750085"
+ cx="584.84467"
+ cy="-85.462952"
+ r="0.0092422031" />
+</svg>
diff --git a/src/quick/doc/src/internal/pinchAndDragHandlers-singlePressDelivery.svg b/src/quick/doc/src/internal/pinchAndDragHandlers-singlePressDelivery.svg
new file mode 100644
index 0000000000..e2f982fe5c
--- /dev/null
+++ b/src/quick/doc/src/internal/pinchAndDragHandlers-singlePressDelivery.svg
@@ -0,0 +1,714 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+
+<!-- Pages: 1 -->
+
+<svg
+ width="715.88171pt"
+ height="331.96246pt"
+ viewBox="0 0 715.96267 331.96246"
+ version="1.1"
+ id="svg806"
+ sodipodi:docname="pinchAndDragHandlers-singlePressDelivery.svg"
+ inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs810">
+ <marker
+ style="overflow:visible"
+ id="marker1576"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Arrow5"
+ markerWidth="5.8874259"
+ markerHeight="6.6094756"
+ viewBox="0 0 5.8874262 6.6094758"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ transform="scale(0.5)"
+ style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
+ d="m 6,0 c -3,1 -7,3 -9,5 0,0 0,-4 2,-5 -2,-1 -2,-5 -2,-5 2,2 6,4 9,5 z"
+ id="path1574" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="marker1576-4"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Arrow5"
+ markerWidth="5.8874259"
+ markerHeight="6.6094756"
+ viewBox="0 0 5.8874262 6.6094758"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ transform="scale(0.5)"
+ style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
+ d="m 6,0 c -3,1 -7,3 -9,5 0,0 0,-4 2,-5 -2,-1 -2,-5 -2,-5 2,2 6,4 9,5 z"
+ id="path1574-4" />
+ </marker>
+ </defs>
+ <sodipodi:namedview
+ id="namedview808"
+ pagecolor="#ffffff"
+ bordercolor="#111111"
+ borderopacity="1"
+ inkscape:showpageshadow="0"
+ inkscape:pageopacity="0"
+ inkscape:pagecheckerboard="1"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:document-units="pt"
+ showgrid="false"
+ inkscape:zoom="2.7028887"
+ inkscape:cx="482.07682"
+ inkscape:cy="213.29032"
+ inkscape:window-width="3840"
+ inkscape:window-height="2139"
+ inkscape:window-x="3840"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg806" />
+ <g
+ id="graph0"
+ class="graph"
+ transform="translate(3.9999999,327.98119)">
+ <polygon
+ fill="#ffffff"
+ stroke="none"
+ points="615.07,4 -4,4 -4,-328 615.07,-328 "
+ id="polygon654"
+ transform="matrix(1.1565133,0,0,1,0.62605332,0)" />
+ <!-- llfkmbbephlg -->
+ <g
+ id="node1"
+ class="node"
+ style="stroke-width:1.00011;stroke-dasharray:none">
+ <title
+ id="title656">llfkmbbephlg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="262.78,-328 250.78,-328 250.78,-288 474.53,-288 474.53,-324 262.78,-324 "
+ id="polygon658"
+ style="stroke-width:1.00011;stroke-dasharray:none" />
+ <polyline
+ fill="none"
+ stroke="#000000"
+ points="250.78,-324 262.78,-324"
+ id="polyline660"
+ style="stroke-width:1.00011;stroke-dasharray:none" />
+ <text
+ text-anchor="middle"
+ x="362.66"
+ y="-300.95001"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text662"
+ style="stroke-width:1.00011;stroke-dasharray:none">ResizeItemToWindow_QMLTYPE_1</text>
+ </g>
+ <!-- llfkmbbeijgg -->
+ <g
+ id="node2"
+ class="node">
+ <title
+ id="title665">llfkmbbeijgg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="402.53,-252 322.78,-252 322.78,-216 402.53,-216 "
+ id="polygon667" />
+ <text
+ text-anchor="middle"
+ x="362.66"
+ y="-229.32001"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text669">â„šRootItem</text>
+ </g>
+ <!-- llfkmbbephlg&#45;&gt;llfkmbbeijgg -->
+ <g
+ id="edge1"
+ class="edge">
+ <title
+ id="title672">llfkmbbephlg-&gt;llfkmbbeijgg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 362.66,-274.81 c 0,7.77 0,15.86 0,22.71"
+ id="path674" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="362.66,-274.8 366.66,-280.8 362.66,-286.8 358.66,-280.8 "
+ id="polygon676" />
+ </g>
+ <!-- llfkmbbepmbg -->
+ <g
+ id="node3"
+ class="node">
+ <title
+ id="title679">llfkmbbepmbg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="327.66,-180 245.66,-180 245.66,-144 327.66,-144 "
+ id="polygon681" />
+ <text
+ text-anchor="middle"
+ x="286.66"
+ y="-157.32001"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text683">â„šRectangle</text>
+ </g>
+ <!-- llfkmbbeijgg&#45;&gt;llfkmbbepmbg -->
+ <g
+ id="edge2"
+ class="edge">
+ <title
+ id="title686">llfkmbbeijgg-&gt;llfkmbbepmbg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 334.28,-206.87 c -9.61,8.86 -20.1,18.52 -28.79,26.52"
+ id="path688" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="334.38,-206.96 341.51,-208.08 343.21,-215.09 336.09,-213.96 "
+ id="polygon690" />
+ </g>
+ <!-- llfkmbclplkg -->
+ <g
+ id="node11"
+ class="node">
+ <title
+ id="title693">llfkmbclplkg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="533.43,-176.56 345.89,-176.56 345.8,-156.44 439.66,-144 533.52,-156.44 "
+ id="polygon695" />
+ <text
+ text-anchor="middle"
+ x="439.66"
+ y="-157.32001"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text697">â„šDeliveryAgent</text>
+ </g>
+ <!-- llfkmbbeijgg&#45;&gt;llfkmbclplkg -->
+ <g
+ id="edge10"
+ class="edge">
+ <title
+ id="title700">llfkmbbeijgg-&gt;llfkmbclplkg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 391.18,-207.07 c 11.28,10.25 23.79,21.62 33.27,30.25"
+ id="path702" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="391.24,-207.02 389.49,-214.01 382.36,-215.09 384.11,-208.09 "
+ id="polygon704" />
+ </g>
+ <!-- llfkmbodpifg -->
+ <g
+ id="node4"
+ class="node">
+ <title
+ id="title707">llfkmbodpifg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="0.08,-75.44 177.23,-75.44 177.32,-95.56 88.66,-108 0,-95.56 "
+ id="polygon709" />
+ <text
+ text-anchor="middle"
+ x="88.660004"
+ y="-85.330002"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text711">â„šPinchHandler</text>
+ </g>
+ <!-- llfkmbbepmbg&#45;&gt;llfkmbodpifg -->
+ <g
+ id="edge3"
+ class="edge">
+ <title
+ id="title714">llfkmbbepmbg-&gt;llfkmbodpifg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 233.33,-142.15 c -34.69,12.27 -78.95,27.92 -109.33,38.66"
+ id="path716" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="233.47,-142.2 240.46,-140.42 244.78,-146.2 237.79,-147.97 "
+ id="polygon718" />
+ </g>
+ <!-- llfkmbnfping -->
+ <g
+ id="node5"
+ class="node">
+ <title
+ id="title721">llfkmbnfping</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="277.66,-108 195.66,-108 195.66,-72 277.66,-72 "
+ id="polygon723" />
+ <text
+ text-anchor="middle"
+ x="236.66"
+ y="-85.330002"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text725">â„šRectangle</text>
+ </g>
+ <!-- llfkmbbepmbg&#45;&gt;llfkmbnfping -->
+ <g
+ id="edge4"
+ class="edge">
+ <title
+ id="title728">llfkmbbepmbg-&gt;llfkmbnfping</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 266.73,-133.1 c -6.04,8.45 -12.48,17.48 -17.85,25"
+ id="path730" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="266.8,-133.2 273.54,-135.76 273.78,-142.96 267.03,-140.41 "
+ id="polygon732" />
+ </g>
+ <!-- llfkmbbdfibg -->
+ <g
+ id="node7"
+ class="node">
+ <title
+ id="title735">llfkmbbdfibg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="377.66,-108 295.66,-108 295.66,-72 377.66,-72 "
+ id="polygon737" />
+ <text
+ text-anchor="middle"
+ x="336.66"
+ y="-85.330002"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text739">â„šRectangle</text>
+ </g>
+ <!-- llfkmbbepmbg&#45;&gt;llfkmbbdfibg -->
+ <g
+ id="edge6"
+ class="edge">
+ <title
+ id="title742">llfkmbbepmbg-&gt;llfkmbbdfibg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 306.59,-133.1 c 6.03,8.45 12.48,17.48 17.85,25"
+ id="path744" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="306.52,-133.2 306.28,-140.41 299.54,-142.96 299.77,-135.76 "
+ id="polygon746" />
+ </g>
+ <!-- llfkmbodeang -->
+ <g
+ id="node9"
+ class="node">
+ <title
+ id="title749">llfkmbodeang</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="522.66,-108 440.66,-108 440.66,-72 522.66,-72 "
+ id="polygon751" />
+ <text
+ text-anchor="middle"
+ x="481.66"
+ y="-85.330002"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text753">â„šRectangle</text>
+ </g>
+ <!-- llfkmbbepmbg&#45;&gt;llfkmbodeang -->
+ <g
+ id="edge8"
+ class="edge">
+ <title
+ id="title756">llfkmbbepmbg-&gt;llfkmbodeang</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 340.14,-141.8 c 31.61,11.35 71.02,25.49 100.22,35.98"
+ id="path758" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="340.16,-141.8 335.86,-147.59 328.86,-145.85 333.16,-140.06 "
+ id="polygon760" />
+ </g>
+ <!-- llfkmbbfjjgg -->
+ <g
+ id="node6"
+ class="node">
+ <title
+ id="title763">llfkmbbfjjgg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="62.33,-3.44 232.99,-3.44 233.07,-23.56 147.66,-36 62.25,-23.56 "
+ id="polygon765" />
+ <text
+ text-anchor="middle"
+ x="147.66"
+ y="-13.33"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text767">â„šDragHandler</text>
+ </g>
+ <!-- llfkmbnfping&#45;&gt;llfkmbbfjjgg -->
+ <g
+ id="edge5"
+ class="edge">
+ <title
+ id="title770">llfkmbnfping-&gt;llfkmbbfjjgg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 204.77,-63.92 c -12.86,10.12 -27.25,21.44 -38.37,30.18"
+ id="path772" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="204.52,-63.72 211.71,-64.29 213.95,-71.14 206.76,-70.57 "
+ id="polygon774" />
+ </g>
+ <!-- llfkmbbfibdg -->
+ <g
+ id="node8"
+ class="node">
+ <title
+ id="title777">llfkmbbfibdg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="251.33,-3.44 421.99,-3.44 422.07,-23.56 336.66,-36 251.25,-23.56 "
+ id="polygon779" />
+ <text
+ text-anchor="middle"
+ x="336.66"
+ y="-13.33"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text781">â„šDragHandler</text>
+ </g>
+ <!-- llfkmbbdfibg&#45;&gt;llfkmbbfibdg -->
+ <g
+ id="edge7"
+ class="edge">
+ <title
+ id="title784">llfkmbbdfibg-&gt;llfkmbbfibdg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 336.66,-58.81 c 0,7.77 0,15.86 0,22.71"
+ id="path786" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="336.66,-58.8 340.66,-64.8 336.66,-70.8 332.66,-64.8 "
+ id="polygon788" />
+ </g>
+ <!-- llfkmbcknglg -->
+ <g
+ id="node10"
+ class="node">
+ <title
+ id="title791">llfkmbcknglg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="440.33,-3.44 610.99,-3.44 611.07,-23.56 525.66,-36 440.25,-23.56 "
+ id="polygon793" />
+ <text
+ text-anchor="middle"
+ x="525.65997"
+ y="-13.33"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text795">â„šDragHandler</text>
+ </g>
+ <!-- llfkmbodeang&#45;&gt;llfkmbcknglg -->
+ <g
+ id="edge9"
+ class="edge">
+ <title
+ id="title798">llfkmbodeang-&gt;llfkmbcknglg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 499.32,-60.9 c 5.62,8.94 11.63,18.5 16.49,26.23"
+ id="path800" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="499.4,-60.77 499.59,-67.98 493.01,-70.93 492.82,-63.72 "
+ id="polygon802" />
+ </g>
+ <g
+ id="g11866"
+ class="edge"
+ transform="rotate(-90,623.51573,-276.02262)">
+ <title
+ id="title11860">llfkmbbephlg-&gt;llfkmbbeijgg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 362.66,-274.81 c 0,7.77 0,15.86 0,22.71"
+ id="path11862" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="358.66,-280.8 362.66,-274.8 366.66,-280.8 362.66,-286.8 "
+ id="polygon11864" />
+ </g>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 708.8157,-33.802688 c 0,-1.93521 -13.67346,-3.50801 -30.51031,-3.50801 -16.83686,0 -30.51503,1.5728 -30.51503,3.50801 0,0 0,31.5378501 0,31.5378501 0,1.93522003 13.67817,3.50801 30.51503,3.50801 16.83685,0 30.51031,-1.57278997 30.51031,-3.50801 0,0 0,-31.5378501 0,-31.5378501"
+ id="path12337"
+ style="stroke-width:0.568213" />
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 708.8157,-33.802688 c 0,1.92838 -13.67346,3.50116 -30.51031,3.50116 -16.83686,0 -30.51503,-1.57278 -30.51503,-3.50116"
+ id="path12339"
+ style="stroke-width:0.568213" />
+ <text
+ text-anchor="middle"
+ x="678.23846"
+ y="-14.011026"
+ font-family="Times, serif"
+ font-size="6pt"
+ id="text12341"
+ style="stroke-width:0.681807">currentPoints</text>
+ <g
+ id="g14256"
+ class="edge"
+ transform="rotate(-90,507.65517,-314.66065)">
+ <title
+ id="title14250">llfkmbbephlg-&gt;llfkmbbeijgg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 362.66,-274.81 c 0,7.77 0,15.86 0,22.71"
+ id="path14252" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="362.66,-274.8 366.66,-280.8 362.66,-286.8 358.66,-280.8 "
+ id="polygon14254" />
+ </g>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 631.59317,-188.30128 c 0,-1.93521 -13.67346,-3.50801 -30.51031,-3.50801 -16.83686,0 -30.51503,1.5728 -30.51503,3.50801 0,0 0,31.53785 0,31.53785 0,1.93522 13.67817,3.50801 30.51503,3.50801 16.83685,0 30.51031,-1.57279 30.51031,-3.50801 0,0 0,-31.53785 0,-31.53785"
+ id="path14258"
+ style="stroke-width:0.568213" />
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 631.59317,-188.30128 c 0,1.92838 -13.67346,3.50116 -30.51031,3.50116 -16.83686,0 -30.51503,-1.57278 -30.51503,-3.50116"
+ id="path14260"
+ style="stroke-width:0.568213" />
+ <text
+ text-anchor="middle"
+ x="601.01593"
+ y="-168.50961"
+ font-family="Times, serif"
+ font-size="6pt"
+ id="text14262"
+ style="stroke-width:0.681807">targetItems</text>
+ </g>
+ <g
+ id="g2388"
+ transform="translate(166.85425,117.65224)">
+ <rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.869398;stroke-linecap:square;stroke-dasharray:0.869398, 0.869398;stroke-opacity:1"
+ id="rect981"
+ width="115.87793"
+ height="32.582088"
+ x="-181.46954"
+ y="-115.9365"
+ ry="0"
+ transform="matrix(1,0,-0.22867874,0.97350194,0,0)" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9pt;line-height:125%;font-family:Times, serif;-inkscape-font-specification:'Times, serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ x="-149.44366"
+ y="-93.742676"
+ id="text1897"><tspan
+ sodipodi:role="line"
+ id="tspan1895"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9pt;font-family:Times, serif;-inkscape-font-specification:'Times, serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="-149.44366"
+ y="-93.742676">QGuiApplication</tspan></text>
+ </g>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576)"
+ d="m 534.3515,175.55291 c 8.9308,13.96865 -25.72468,19.46301 -18.49471,5.27481"
+ id="path2661"
+ sodipodi:nodetypes="cc" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#e6a11b;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="113.75787"
+ y="213.93288"
+ id="text8866"
+ transform="rotate(-11.181322)"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#e6a11b;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="113.75787"
+ y="213.93288"
+ id="tspan8864">skipped</tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#e6a11b;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576-4)"
+ d="M 248.80679,166.05103 C 128.29267,210.33716 109.36613,183.17227 102.95996,215.73635"
+ id="path2625"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#e6a11b;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576-4)"
+ d="m 205.26622,258.16221 c -30.99608,41.44745 -40.03963,1.76839 -45.06759,29.20422"
+ id="path9198"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#e6a11b;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576-4)"
+ d="m 325.15207,257.83821 c -2.27262,29.75722 -30.19879,12.70432 -28.21604,34.22384"
+ id="path9218"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576-4)"
+ d="m 515.92916,258.31316 c 8.69611,26.1658 28.09925,12.12573 28.29307,30.37142"
+ id="path9238"
+ sodipodi:nodetypes="cc" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="412.41052"
+ y="194.7211"
+ id="text9282"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="412.41052"
+ y="194.7211"
+ id="tspan9280">â‘¢ mapFromScene()</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="527.64844"
+ y="266.12436"
+ id="text9304"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="527.64844"
+ y="266.12436"
+ id="tspan11558">⑤ handlePointerEvent()</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="539.94745"
+ y="184.11032"
+ id="text9334"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="539.94745"
+ y="184.11032"
+ id="tspan9332">â‘  deliverMatchingPointsToItem()</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="539.94745"
+ y="195.0647"
+ id="tspan9365">â‘¡ localizePointerEvent()</tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#e6a11b;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576-4)"
+ d="m 371.17602,176.60114 c -19.44733,11.33075 -28.42572,-6.22235 -36.44805,-6.86652"
+ id="path9367"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#e6a11b;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576-4)"
+ d="m 371.17602,176.60114 c -14.82664,21.37092 -94.59207,21.74646 -97.17351,38.08234"
+ id="path9387"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#e6a11b;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576-4)"
+ d="m 371.17602,176.60114 c -14.82664,21.37092 4.12412,22.17486 1.54268,38.51074"
+ id="path9407"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576-4)"
+ d="m 371.17602,176.60114 c -14.82664,21.37092 90.86392,22.23065 88.28248,38.56653"
+ id="path9427"
+ sodipodi:nodetypes="cc" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#e6a11b;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="362.56143"
+ y="141.40547"
+ id="text11290"
+ transform="rotate(10.069153)"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#e6a11b;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="362.56143"
+ y="141.40547"
+ id="tspan11288">skipped</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="459.80051"
+ y="208.66859"
+ id="text11424"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="459.80051"
+ y="208.66859"
+ id="tspan11422">â‘£ handlePointerEvent()</tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576)"
+ d="m 553.17544,291.61876 c -3.52959,-16.19952 30.87947,-9.32879 19.17598,1.46963"
+ id="path14423"
+ sodipodi:nodetypes="cc" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="577.46771"
+ y="276.84311"
+ id="text14441"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="577.46771"
+ y="276.84311"
+ id="tspan14439">â‘¥ handlePointerEventImpl()</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="577.46771"
+ y="287.79749"
+ id="tspan14459">⑦ setPassiveGrab()</tspan></text>
+</svg>
diff --git a/src/quick/doc/src/internal/pinchAndDragHandlers-singlePressPrep.svg b/src/quick/doc/src/internal/pinchAndDragHandlers-singlePressPrep.svg
new file mode 100644
index 0000000000..4cec17b399
--- /dev/null
+++ b/src/quick/doc/src/internal/pinchAndDragHandlers-singlePressPrep.svg
@@ -0,0 +1,648 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Pages: 1 -->
+
+<svg
+ width="695.15735pt"
+ height="331.96246pt"
+ viewBox="0 0 695.23596 331.96246"
+ version="1.1"
+ id="svg806"
+ sodipodi:docname="pinchAndDragHandlers-singlePressPrep.svg"
+ inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs810">
+ <marker
+ style="overflow:visible"
+ id="Arrow5"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Arrow5"
+ markerWidth="5.8874264"
+ markerHeight="6.6094756"
+ viewBox="0 0 5.8874262 6.6094758"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ transform="scale(0.5)"
+ style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
+ d="m 6,0 c -3,1 -7,3 -9,5 0,0 0,-4 2,-5 -2,-1 -2,-5 -2,-5 2,2 6,4 9,5 z"
+ id="path8893" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="marker1576"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Arrow5"
+ markerWidth="5.8874259"
+ markerHeight="6.6094756"
+ viewBox="0 0 5.8874262 6.6094758"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ transform="scale(0.5)"
+ style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
+ d="m 6,0 c -3,1 -7,3 -9,5 0,0 0,-4 2,-5 -2,-1 -2,-5 -2,-5 2,2 6,4 9,5 z"
+ id="path1574" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="marker1576-4"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Arrow5"
+ markerWidth="5.8874259"
+ markerHeight="6.6094756"
+ viewBox="0 0 5.8874262 6.6094758"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ transform="scale(0.5)"
+ style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
+ d="m 6,0 c -3,1 -7,3 -9,5 0,0 0,-4 2,-5 -2,-1 -2,-5 -2,-5 2,2 6,4 9,5 z"
+ id="path1574-4" />
+ </marker>
+ </defs>
+ <sodipodi:namedview
+ id="namedview808"
+ pagecolor="#ffffff"
+ bordercolor="#111111"
+ borderopacity="1"
+ inkscape:showpageshadow="0"
+ inkscape:pageopacity="0"
+ inkscape:pagecheckerboard="1"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:document-units="pt"
+ showgrid="false"
+ inkscape:zoom="2.6661199"
+ inkscape:cx="434.33906"
+ inkscape:cy="270.61799"
+ inkscape:window-width="3840"
+ inkscape:window-height="2139"
+ inkscape:window-x="3840"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg806" />
+ <g
+ id="graph0"
+ class="graph"
+ transform="translate(3.9999998,327.9812)">
+ <polygon
+ fill="#ffffff"
+ stroke="none"
+ points="615.07,-328 615.07,4 -4,4 -4,-328 "
+ id="polygon654"
+ transform="matrix(1.1230329,0,0,1,0.4921318,0)" />
+ <!-- llfkmbbephlg -->
+ <g
+ id="node1"
+ class="node"
+ style="stroke-width:1.00011;stroke-dasharray:none">
+ <title
+ id="title656">llfkmbbephlg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="250.78,-288 474.53,-288 474.53,-324 262.78,-324 262.78,-328 250.78,-328 "
+ id="polygon658"
+ style="stroke-width:1.00011;stroke-dasharray:none" />
+ <polyline
+ fill="none"
+ stroke="#000000"
+ points="250.78,-324 262.78,-324"
+ id="polyline660"
+ style="stroke-width:1.00011;stroke-dasharray:none" />
+ <text
+ text-anchor="middle"
+ x="362.66"
+ y="-300.95001"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text662"
+ style="stroke-width:1.00011;stroke-dasharray:none">ResizeItemToWindow_QMLTYPE_1</text>
+ </g>
+ <!-- llfkmbbeijgg -->
+ <g
+ id="node2"
+ class="node">
+ <title
+ id="title665">llfkmbbeijgg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="402.53,-252 322.78,-252 322.78,-216 402.53,-216 "
+ id="polygon667" />
+ <text
+ text-anchor="middle"
+ x="362.66"
+ y="-229.32001"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text669">â„šRootItem</text>
+ </g>
+ <!-- llfkmbbephlg&#45;&gt;llfkmbbeijgg -->
+ <g
+ id="edge1"
+ class="edge">
+ <title
+ id="title672">llfkmbbephlg-&gt;llfkmbbeijgg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 362.66,-274.81 c 0,7.77 0,15.86 0,22.71"
+ id="path674" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="362.66,-274.8 366.66,-280.8 362.66,-286.8 358.66,-280.8 "
+ id="polygon676" />
+ </g>
+ <!-- llfkmbbepmbg -->
+ <g
+ id="node3"
+ class="node">
+ <title
+ id="title679">llfkmbbepmbg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="327.66,-180 245.66,-180 245.66,-144 327.66,-144 "
+ id="polygon681" />
+ <text
+ text-anchor="middle"
+ x="286.66"
+ y="-157.32001"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text683">â„šRectangle</text>
+ </g>
+ <!-- llfkmbbeijgg&#45;&gt;llfkmbbepmbg -->
+ <g
+ id="edge2"
+ class="edge">
+ <title
+ id="title686">llfkmbbeijgg-&gt;llfkmbbepmbg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 334.28,-206.87 c -9.61,8.86 -20.1,18.52 -28.79,26.52"
+ id="path688" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="334.38,-206.96 341.51,-208.08 343.21,-215.09 336.09,-213.96 "
+ id="polygon690" />
+ </g>
+ <!-- llfkmbclplkg -->
+ <g
+ id="node11"
+ class="node">
+ <title
+ id="title693">llfkmbclplkg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="345.89,-176.56 345.8,-156.44 439.66,-144 533.52,-156.44 533.43,-176.56 "
+ id="polygon695" />
+ <text
+ text-anchor="middle"
+ x="439.66"
+ y="-157.32001"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text697">â„šDeliveryAgent</text>
+ </g>
+ <!-- llfkmbbeijgg&#45;&gt;llfkmbclplkg -->
+ <g
+ id="edge10"
+ class="edge">
+ <title
+ id="title700">llfkmbbeijgg-&gt;llfkmbclplkg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 391.18,-207.07 c 11.28,10.25 23.79,21.62 33.27,30.25"
+ id="path702" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="391.24,-207.02 389.49,-214.01 382.36,-215.09 384.11,-208.09 "
+ id="polygon704" />
+ </g>
+ <!-- llfkmbodpifg -->
+ <g
+ id="node4"
+ class="node">
+ <title
+ id="title707">llfkmbodpifg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="177.23,-75.44 177.32,-95.56 88.66,-108 0,-95.56 0.08,-75.44 "
+ id="polygon709" />
+ <text
+ text-anchor="middle"
+ x="88.660004"
+ y="-85.330002"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text711">â„šPinchHandler</text>
+ </g>
+ <!-- llfkmbbepmbg&#45;&gt;llfkmbodpifg -->
+ <g
+ id="edge3"
+ class="edge">
+ <title
+ id="title714">llfkmbbepmbg-&gt;llfkmbodpifg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 233.33,-142.15 c -34.69,12.27 -78.95,27.92 -109.33,38.66"
+ id="path716" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="233.47,-142.2 240.46,-140.42 244.78,-146.2 237.79,-147.97 "
+ id="polygon718" />
+ </g>
+ <!-- llfkmbnfping -->
+ <g
+ id="node5"
+ class="node">
+ <title
+ id="title721">llfkmbnfping</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="277.66,-108 195.66,-108 195.66,-72 277.66,-72 "
+ id="polygon723" />
+ <text
+ text-anchor="middle"
+ x="236.66"
+ y="-85.330002"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text725">â„šRectangle</text>
+ </g>
+ <!-- llfkmbbepmbg&#45;&gt;llfkmbnfping -->
+ <g
+ id="edge4"
+ class="edge">
+ <title
+ id="title728">llfkmbbepmbg-&gt;llfkmbnfping</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 266.73,-133.1 c -6.04,8.45 -12.48,17.48 -17.85,25"
+ id="path730" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="266.8,-133.2 273.54,-135.76 273.78,-142.96 267.03,-140.41 "
+ id="polygon732" />
+ </g>
+ <!-- llfkmbbdfibg -->
+ <g
+ id="node7"
+ class="node">
+ <title
+ id="title735">llfkmbbdfibg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="377.66,-108 295.66,-108 295.66,-72 377.66,-72 "
+ id="polygon737" />
+ <text
+ text-anchor="middle"
+ x="336.66"
+ y="-85.330002"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text739">â„šRectangle</text>
+ </g>
+ <!-- llfkmbbepmbg&#45;&gt;llfkmbbdfibg -->
+ <g
+ id="edge6"
+ class="edge">
+ <title
+ id="title742">llfkmbbepmbg-&gt;llfkmbbdfibg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 306.59,-133.1 c 6.03,8.45 12.48,17.48 17.85,25"
+ id="path744" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="306.52,-133.2 306.28,-140.41 299.54,-142.96 299.77,-135.76 "
+ id="polygon746" />
+ </g>
+ <!-- llfkmbodeang -->
+ <g
+ id="node9"
+ class="node">
+ <title
+ id="title749">llfkmbodeang</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="522.66,-108 440.66,-108 440.66,-72 522.66,-72 "
+ id="polygon751" />
+ <text
+ text-anchor="middle"
+ x="481.66"
+ y="-85.330002"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text753">â„šRectangle</text>
+ </g>
+ <!-- llfkmbbepmbg&#45;&gt;llfkmbodeang -->
+ <g
+ id="edge8"
+ class="edge">
+ <title
+ id="title756">llfkmbbepmbg-&gt;llfkmbodeang</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 340.14,-141.8 c 31.61,11.35 71.02,25.49 100.22,35.98"
+ id="path758" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="340.16,-141.8 335.86,-147.59 328.86,-145.85 333.16,-140.06 "
+ id="polygon760" />
+ </g>
+ <!-- llfkmbbfjjgg -->
+ <g
+ id="node6"
+ class="node">
+ <title
+ id="title763">llfkmbbfjjgg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="232.99,-3.44 233.07,-23.56 147.66,-36 62.25,-23.56 62.33,-3.44 "
+ id="polygon765" />
+ <text
+ text-anchor="middle"
+ x="147.66"
+ y="-13.33"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text767">â„šDragHandler</text>
+ </g>
+ <!-- llfkmbnfping&#45;&gt;llfkmbbfjjgg -->
+ <g
+ id="edge5"
+ class="edge">
+ <title
+ id="title770">llfkmbnfping-&gt;llfkmbbfjjgg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 204.77,-63.92 c -12.86,10.12 -27.25,21.44 -38.37,30.18"
+ id="path772" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="204.52,-63.72 211.71,-64.29 213.95,-71.14 206.76,-70.57 "
+ id="polygon774" />
+ </g>
+ <!-- llfkmbbfibdg -->
+ <g
+ id="node8"
+ class="node">
+ <title
+ id="title777">llfkmbbfibdg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="421.99,-3.44 422.07,-23.56 336.66,-36 251.25,-23.56 251.33,-3.44 "
+ id="polygon779" />
+ <text
+ text-anchor="middle"
+ x="336.66"
+ y="-13.33"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text781">â„šDragHandler</text>
+ </g>
+ <!-- llfkmbbdfibg&#45;&gt;llfkmbbfibdg -->
+ <g
+ id="edge7"
+ class="edge">
+ <title
+ id="title784">llfkmbbdfibg-&gt;llfkmbbfibdg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 336.66,-58.81 c 0,7.77 0,15.86 0,22.71"
+ id="path786" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="336.66,-58.8 340.66,-64.8 336.66,-70.8 332.66,-64.8 "
+ id="polygon788" />
+ </g>
+ <!-- llfkmbcknglg -->
+ <g
+ id="node10"
+ class="node">
+ <title
+ id="title791">llfkmbcknglg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="610.99,-3.44 611.07,-23.56 525.66,-36 440.25,-23.56 440.33,-3.44 "
+ id="polygon793" />
+ <text
+ text-anchor="middle"
+ x="525.65997"
+ y="-13.33"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text795">â„šDragHandler</text>
+ </g>
+ <!-- llfkmbodeang&#45;&gt;llfkmbcknglg -->
+ <g
+ id="edge9"
+ class="edge">
+ <title
+ id="title798">llfkmbodeang-&gt;llfkmbcknglg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 499.32,-60.9 c 5.62,8.94 11.63,18.5 16.49,26.23"
+ id="path800" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="499.4,-60.77 499.59,-67.98 493.01,-70.93 492.82,-63.72 "
+ id="polygon802" />
+ </g>
+ </g>
+ <g
+ id="g2388"
+ transform="translate(166.85425,117.65225)">
+ <rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.869398;stroke-linecap:square;stroke-dasharray:0.869398, 0.869398;stroke-opacity:1"
+ id="rect981"
+ width="115.87793"
+ height="32.582088"
+ x="-181.46954"
+ y="-115.9365"
+ ry="0"
+ transform="matrix(1,0,-0.22867874,0.97350194,0,0)" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9pt;line-height:125%;font-family:Times, serif;-inkscape-font-specification:'Times, serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ x="-149.44366"
+ y="-93.742676"
+ id="text1897"><tspan
+ sodipodi:role="line"
+ id="tspan1895"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9pt;font-family:Times, serif;-inkscape-font-specification:'Times, serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="-149.44366"
+ y="-93.742676">QGuiApplication</tspan></text>
+ </g>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.03579;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.03579, 1.03579;stroke-dashoffset:0;stroke-opacity:1;marker-start:url(#Arrow5)"
+ d="M 250.09544,22.1843 H 126.58276"
+ id="path1311" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="130.11781"
+ y="17.801956"
+ id="text1315"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="130.11781"
+ y="17.801956"
+ id="tspan1317">â‘  event(QTouchEvent*)</tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.03579;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.03579, 1.03579;stroke-dashoffset:0;stroke-opacity:1;marker-start:url(#Arrow5)"
+ d="M 480.8984,145.14539 434.78238,41.639225"
+ id="path2865"
+ sodipodi:nodetypes="cc" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="223.93822"
+ y="-381.72717"
+ id="text2981"
+ transform="rotate(65.571486)"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="223.93822"
+ y="-381.72717"
+ id="tspan2979">â‘¡ event(QTouchEvent*)</tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576)"
+ d="m 534.3515,175.55292 c 8.9308,13.96865 -25.72468,19.46301 -18.49471,5.27481"
+ id="path2661"
+ sodipodi:nodetypes="cc" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="540.16193"
+ y="178.55621"
+ id="text8573"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="540.16193"
+ y="178.55621"
+ id="tspan8587">â‘¢ deliverPointerEvent()</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="540.16193"
+ y="189.51059"
+ id="tspan8591">â‘£ deliverPressOrReleaseEvent()</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="540.16193"
+ y="200.46495"
+ id="tspan8595">⑤ pointerTargets()</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:end;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:end;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="187.83101"
+ y="197.08606"
+ id="text8866"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:end;text-anchor:end;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="187.83101"
+ y="197.08606"
+ id="tspan8864">â‘¥ wantsEventPoint()</tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576-4)"
+ d="m 414.44327,180.9263 c -120.51412,44.28613 -305.07714,2.24598 -311.48331,34.81006"
+ id="path2625"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576-4)"
+ d="M 414.44327,180.9263 C 293.92915,225.21243 166.6048,254.80236 160.19863,287.36644"
+ id="path9198"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576-4)"
+ d="M 414.44327,180.9263 C 293.92915,225.21243 303.3422,259.49798 296.93603,292.06206"
+ id="path9218"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576-4)"
+ d="m 414.44327,180.9263 c 8.69611,26.1658 156.27171,32.20621 129.77896,107.75829"
+ id="path9238"
+ sodipodi:nodetypes="cc" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:end;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:end;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="180.63849"
+ y="263.64062"
+ id="text9260"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:end;text-anchor:end;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="162.79994"
+ y="275.0322"
+ id="tspan9258">⑦ wantsEventPoint()</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;text-anchor:end;text-align:end"
+ x="295.72836"
+ y="286.02438"
+ id="text9282"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;text-anchor:end;text-align:end"
+ x="203.01468"
+ y="286.02438"
+ id="tspan9280">⑧ wantsEventPoint()</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="550.57092"
+ y="278.21912"
+ id="text9304"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="550.57092"
+ y="278.21912"
+ id="tspan9302">⑨ wantsEventPoint()</tspan></text>
+ <g
+ id="node34"
+ class="node"
+ transform="matrix(0.68180684,0,0,0.68180684,-606.60021,386.57842)">
+ <title
+ id="title12335">llncgjfhndpg</title>
+ </g>
+</svg>
diff --git a/src/quick/doc/src/internal/pinchAndDragHandlers-singleReleaseDelivery.svg b/src/quick/doc/src/internal/pinchAndDragHandlers-singleReleaseDelivery.svg
new file mode 100644
index 0000000000..d96bf4ff10
--- /dev/null
+++ b/src/quick/doc/src/internal/pinchAndDragHandlers-singleReleaseDelivery.svg
@@ -0,0 +1,825 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Pages: 1 -->
+
+<svg
+ width="737.57623pt"
+ height="332.46246pt"
+ viewBox="0 0 737.65964 332.46246"
+ version="1.1"
+ id="svg806"
+ sodipodi:docname="pinchAndDragHandlers-singleReleaseDelivery.svg"
+ inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs810">
+ <marker
+ style="overflow:visible"
+ id="marker1576"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Arrow5"
+ markerWidth="5.8874259"
+ markerHeight="6.6094756"
+ viewBox="0 0 5.8874262 6.6094758"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ transform="scale(0.5)"
+ style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
+ d="m 6,0 c -3,1 -7,3 -9,5 0,0 0,-4 2,-5 -2,-1 -2,-5 -2,-5 2,2 6,4 9,5 z"
+ id="path1574" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="marker1576-4"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Arrow5"
+ markerWidth="5.8874259"
+ markerHeight="6.6094756"
+ viewBox="0 0 5.8874262 6.6094758"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ transform="scale(0.5)"
+ style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
+ d="m 6,0 c -3,1 -7,3 -9,5 0,0 0,-4 2,-5 -2,-1 -2,-5 -2,-5 2,2 6,4 9,5 z"
+ id="path1574-4" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="Arrow5"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Arrow5"
+ markerWidth="5.8874264"
+ markerHeight="6.6094756"
+ viewBox="0 0 5.8874262 6.6094758"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ transform="scale(0.5)"
+ style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
+ d="m 6,0 c -3,1 -7,3 -9,5 0,0 0,-4 2,-5 -2,-1 -2,-5 -2,-5 2,2 6,4 9,5 z"
+ id="path8893" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="marker1576-4-5"
+ refX="0"
+ refY="0"
+ orient="auto-start-reverse"
+ inkscape:stockid="Arrow5"
+ markerWidth="5.8874259"
+ markerHeight="6.6094756"
+ viewBox="0 0 5.8874262 6.6094758"
+ inkscape:isstock="true"
+ inkscape:collect="always"
+ preserveAspectRatio="xMidYMid">
+ <path
+ transform="scale(0.5)"
+ style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
+ d="m 6,0 c -3,1 -7,3 -9,5 0,0 0,-4 2,-5 -2,-1 -2,-5 -2,-5 2,2 6,4 9,5 z"
+ id="path1574-4-3" />
+ </marker>
+ </defs>
+ <sodipodi:namedview
+ id="namedview808"
+ pagecolor="#ffffff"
+ bordercolor="#111111"
+ borderopacity="1"
+ inkscape:showpageshadow="0"
+ inkscape:pageopacity="0"
+ inkscape:pagecheckerboard="1"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:document-units="pt"
+ showgrid="false"
+ inkscape:zoom="2.7028887"
+ inkscape:cx="472.64247"
+ inkscape:cy="236.96869"
+ inkscape:window-width="3840"
+ inkscape:window-height="2139"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg806" />
+ <g
+ id="graph0"
+ class="graph"
+ transform="translate(4,328.48129)">
+ <polygon
+ fill="#ffffff"
+ stroke="none"
+ points="615.07,4 -4,4 -4,-328 615.07,-328 "
+ id="polygon654"
+ transform="matrix(1.1915609,0,0,1,0.76624365,0)" />
+ <!-- llfkmbbephlg -->
+ <g
+ id="node1"
+ class="node"
+ style="stroke-width:1.00011;stroke-dasharray:none">
+ <title
+ id="title656">llfkmbbephlg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="250.78,-288 474.53,-288 474.53,-324 262.78,-324 262.78,-328 250.78,-328 "
+ id="polygon658"
+ style="stroke-width:1.00011;stroke-dasharray:none" />
+ <polyline
+ fill="none"
+ stroke="#000000"
+ points="250.78,-324 262.78,-324"
+ id="polyline660"
+ style="stroke-width:1.00011;stroke-dasharray:none" />
+ <text
+ text-anchor="middle"
+ x="362.66"
+ y="-300.95001"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text662"
+ style="stroke-width:1.00011;stroke-dasharray:none">ResizeItemToWindow_QMLTYPE_1</text>
+ </g>
+ <!-- llfkmbbeijgg -->
+ <g
+ id="node2"
+ class="node">
+ <title
+ id="title665">llfkmbbeijgg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="322.78,-216 402.53,-216 402.53,-252 322.78,-252 "
+ id="polygon667" />
+ <text
+ text-anchor="middle"
+ x="362.66"
+ y="-229.32001"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text669">â„šRootItem</text>
+ </g>
+ <!-- llfkmbbephlg&#45;&gt;llfkmbbeijgg -->
+ <g
+ id="edge1"
+ class="edge">
+ <title
+ id="title672">llfkmbbephlg-&gt;llfkmbbeijgg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 362.66,-274.81 c 0,7.77 0,15.86 0,22.71"
+ id="path674" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="362.66,-286.8 358.66,-280.8 362.66,-274.8 366.66,-280.8 "
+ id="polygon676" />
+ </g>
+ <!-- llfkmbbepmbg -->
+ <g
+ id="node3"
+ class="node">
+ <title
+ id="title679">llfkmbbepmbg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="245.66,-144 327.66,-144 327.66,-180 245.66,-180 "
+ id="polygon681" />
+ <text
+ text-anchor="middle"
+ x="286.66"
+ y="-157.32001"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text683">â„šRectangle</text>
+ </g>
+ <!-- llfkmbbeijgg&#45;&gt;llfkmbbepmbg -->
+ <g
+ id="edge2"
+ class="edge">
+ <title
+ id="title686">llfkmbbeijgg-&gt;llfkmbbepmbg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 334.28,-206.87 c -9.61,8.86 -20.1,18.52 -28.79,26.52"
+ id="path688" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="343.21,-215.09 336.09,-213.96 334.38,-206.96 341.51,-208.08 "
+ id="polygon690" />
+ </g>
+ <!-- llfkmbclplkg -->
+ <g
+ id="node11"
+ class="node">
+ <title
+ id="title693">llfkmbclplkg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="345.8,-156.44 439.66,-144 533.52,-156.44 533.43,-176.56 345.89,-176.56 "
+ id="polygon695" />
+ <text
+ text-anchor="middle"
+ x="439.66"
+ y="-157.32001"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text697">â„šDeliveryAgent</text>
+ </g>
+ <!-- llfkmbbeijgg&#45;&gt;llfkmbclplkg -->
+ <g
+ id="edge10"
+ class="edge">
+ <title
+ id="title700">llfkmbbeijgg-&gt;llfkmbclplkg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 391.18,-207.07 c 11.28,10.25 23.79,21.62 33.27,30.25"
+ id="path702" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="382.36,-215.09 384.11,-208.09 391.24,-207.02 389.49,-214.01 "
+ id="polygon704" />
+ </g>
+ <!-- llfkmbodpifg -->
+ <g
+ id="node4"
+ class="node">
+ <title
+ id="title707">llfkmbodpifg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="177.32,-95.56 88.66,-108 0,-95.56 0.08,-75.44 177.23,-75.44 "
+ id="polygon709" />
+ <text
+ text-anchor="middle"
+ x="88.660004"
+ y="-85.330002"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text711">â„šPinchHandler</text>
+ </g>
+ <!-- llfkmbbepmbg&#45;&gt;llfkmbodpifg -->
+ <g
+ id="edge3"
+ class="edge">
+ <title
+ id="title714">llfkmbbepmbg-&gt;llfkmbodpifg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 233.33,-142.15 c -34.69,12.27 -78.95,27.92 -109.33,38.66"
+ id="path716" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="244.78,-146.2 237.79,-147.97 233.47,-142.2 240.46,-140.42 "
+ id="polygon718" />
+ </g>
+ <!-- llfkmbnfping -->
+ <g
+ id="node5"
+ class="node">
+ <title
+ id="title721">llfkmbnfping</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="195.66,-72 277.66,-72 277.66,-108 195.66,-108 "
+ id="polygon723" />
+ <text
+ text-anchor="middle"
+ x="236.66"
+ y="-85.330002"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text725">â„šRectangle</text>
+ </g>
+ <!-- llfkmbbepmbg&#45;&gt;llfkmbnfping -->
+ <g
+ id="edge4"
+ class="edge">
+ <title
+ id="title728">llfkmbbepmbg-&gt;llfkmbnfping</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 266.73,-133.1 c -6.04,8.45 -12.48,17.48 -17.85,25"
+ id="path730" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="273.78,-142.96 267.03,-140.41 266.8,-133.2 273.54,-135.76 "
+ id="polygon732" />
+ </g>
+ <!-- llfkmbbdfibg -->
+ <g
+ id="node7"
+ class="node">
+ <title
+ id="title735">llfkmbbdfibg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="295.66,-72 377.66,-72 377.66,-108 295.66,-108 "
+ id="polygon737" />
+ <text
+ text-anchor="middle"
+ x="336.66"
+ y="-85.330002"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text739">â„šRectangle</text>
+ </g>
+ <!-- llfkmbbepmbg&#45;&gt;llfkmbbdfibg -->
+ <g
+ id="edge6"
+ class="edge">
+ <title
+ id="title742">llfkmbbepmbg-&gt;llfkmbbdfibg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 306.59,-133.1 c 6.03,8.45 12.48,17.48 17.85,25"
+ id="path744" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="299.54,-142.96 299.77,-135.76 306.52,-133.2 306.28,-140.41 "
+ id="polygon746" />
+ </g>
+ <!-- llfkmbodeang -->
+ <g
+ id="node9"
+ class="node">
+ <title
+ id="title749">llfkmbodeang</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="440.66,-72 522.66,-72 522.66,-108 440.66,-108 "
+ id="polygon751" />
+ <text
+ text-anchor="middle"
+ x="481.66"
+ y="-85.330002"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text753">â„šRectangle</text>
+ </g>
+ <!-- llfkmbbepmbg&#45;&gt;llfkmbodeang -->
+ <g
+ id="edge8"
+ class="edge">
+ <title
+ id="title756">llfkmbbepmbg-&gt;llfkmbodeang</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 340.14,-141.8 c 31.61,11.35 71.02,25.49 100.22,35.98"
+ id="path758" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="328.86,-145.85 333.16,-140.06 340.16,-141.8 335.86,-147.59 "
+ id="polygon760" />
+ </g>
+ <!-- llfkmbbfjjgg -->
+ <g
+ id="node6"
+ class="node">
+ <title
+ id="title763">llfkmbbfjjgg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="233.07,-23.56 147.66,-36 62.25,-23.56 62.33,-3.44 232.99,-3.44 "
+ id="polygon765" />
+ <text
+ text-anchor="middle"
+ x="147.66"
+ y="-13.33"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text767">â„šDragHandler</text>
+ </g>
+ <!-- llfkmbnfping&#45;&gt;llfkmbbfjjgg -->
+ <g
+ id="edge5"
+ class="edge">
+ <title
+ id="title770">llfkmbnfping-&gt;llfkmbbfjjgg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 204.77,-63.92 c -12.86,10.12 -27.25,21.44 -38.37,30.18"
+ id="path772" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="213.95,-71.14 206.76,-70.57 204.52,-63.72 211.71,-64.29 "
+ id="polygon774" />
+ </g>
+ <!-- llfkmbbfibdg -->
+ <g
+ id="node8"
+ class="node">
+ <title
+ id="title777">llfkmbbfibdg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="422.07,-23.56 336.66,-36 251.25,-23.56 251.33,-3.44 421.99,-3.44 "
+ id="polygon779" />
+ <text
+ text-anchor="middle"
+ x="336.66"
+ y="-13.33"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text781">â„šDragHandler</text>
+ </g>
+ <!-- llfkmbbdfibg&#45;&gt;llfkmbbfibdg -->
+ <g
+ id="edge7"
+ class="edge">
+ <title
+ id="title784">llfkmbbdfibg-&gt;llfkmbbfibdg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 336.66,-58.81 c 0,7.77 0,15.86 0,22.71"
+ id="path786" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="336.66,-70.8 332.66,-64.8 336.66,-58.8 340.66,-64.8 "
+ id="polygon788" />
+ </g>
+ <!-- llfkmbcknglg -->
+ <g
+ id="node10"
+ class="node">
+ <title
+ id="title791">llfkmbcknglg</title>
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="611.07,-23.56 525.66,-36 440.25,-23.56 440.33,-3.44 610.99,-3.44 "
+ id="polygon793" />
+ <text
+ text-anchor="middle"
+ x="525.65997"
+ y="-13.33"
+ font-family="Times, serif"
+ font-size="9pt"
+ id="text795">â„šDragHandler</text>
+ </g>
+ <!-- llfkmbodeang&#45;&gt;llfkmbcknglg -->
+ <g
+ id="edge9"
+ class="edge">
+ <title
+ id="title798">llfkmbodeang-&gt;llfkmbcknglg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 499.32,-60.9 c 5.62,8.94 11.63,18.5 16.49,26.23"
+ id="path800" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="493.01,-70.93 492.82,-63.72 499.4,-60.77 499.59,-67.98 "
+ id="polygon802" />
+ </g>
+ <g
+ id="g11866"
+ class="edge"
+ transform="rotate(-90,623.51573,-276.02262)">
+ <title
+ id="title11860">llfkmbbephlg-&gt;llfkmbbeijgg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 362.66,-274.81 c 0,7.77 0,15.86 0,22.71"
+ id="path11862" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="366.66,-280.8 362.66,-286.8 358.66,-280.8 362.66,-274.8 "
+ id="polygon11864" />
+ </g>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 708.8157,-33.802688 c 0,-1.93521 -13.67346,-3.50801 -30.51031,-3.50801 -16.83686,0 -30.51503,1.5728 -30.51503,3.50801 0,0 0,31.5378501 0,31.5378501 0,1.93522003 13.67817,3.50801 30.51503,3.50801 16.83685,0 30.51031,-1.57278997 30.51031,-3.50801 0,0 0,-31.5378501 0,-31.5378501"
+ id="path12337"
+ style="stroke-width:0.568213" />
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 708.8157,-33.802688 c 0,1.92838 -13.67346,3.50116 -30.51031,3.50116 -16.83686,0 -30.51503,-1.57278 -30.51503,-3.50116"
+ id="path12339"
+ style="stroke-width:0.568213" />
+ <text
+ text-anchor="middle"
+ x="678.23846"
+ y="-14.011026"
+ font-family="Times, serif"
+ font-size="6pt"
+ id="text12341"
+ style="stroke-width:0.681807">currentPoints</text>
+ <g
+ id="g14256"
+ class="edge"
+ transform="rotate(-90,507.65517,-314.66065)">
+ <title
+ id="title14250">llfkmbbephlg-&gt;llfkmbbeijgg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 362.66,-274.81 c 0,7.77 0,15.86 0,22.71"
+ id="path14252" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="362.66,-286.8 358.66,-280.8 362.66,-274.8 366.66,-280.8 "
+ id="polygon14254" />
+ </g>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 631.59317,-188.30128 c 0,-1.93521 -13.67346,-3.50801 -30.51031,-3.50801 -16.83686,0 -30.51503,1.5728 -30.51503,3.50801 0,0 0,31.53785 0,31.53785 0,1.93522 13.67817,3.50801 30.51503,3.50801 16.83685,0 30.51031,-1.57279 30.51031,-3.50801 0,0 0,-31.53785 0,-31.53785"
+ id="path14258"
+ style="stroke-width:0.568213" />
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 631.59317,-188.30128 c 0,1.92838 -13.67346,3.50116 -30.51031,3.50116 -16.83686,0 -30.51503,-1.57278 -30.51503,-3.50116"
+ id="path14260"
+ style="stroke-width:0.568213" />
+ <text
+ text-anchor="middle"
+ x="601.01593"
+ y="-168.50961"
+ font-family="Times, serif"
+ font-size="6pt"
+ id="text14262"
+ style="stroke-width:0.681807">targetItems</text>
+ <g
+ id="g3826"
+ class="edge"
+ transform="rotate(180,500.1871,-52.761609)">
+ <title
+ id="title3820">llfkmbodeang-&gt;llfkmbcknglg</title>
+ <path
+ fill="none"
+ stroke="#000000"
+ d="m 499.32,-60.9 c 5.62,8.94 11.63,18.5 16.49,26.23"
+ id="path3822" />
+ <polygon
+ fill="none"
+ stroke="#000000"
+ points="492.82,-63.72 499.4,-60.77 499.59,-67.98 493.01,-70.93 "
+ id="polygon3824" />
+ </g>
+ </g>
+ <g
+ id="g2388"
+ transform="translate(166.85425,118.15234)">
+ <rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.869398;stroke-linecap:square;stroke-dasharray:0.869398, 0.869398;stroke-opacity:1"
+ id="rect981"
+ width="115.87793"
+ height="32.582088"
+ x="-181.46954"
+ y="-115.9365"
+ ry="0"
+ transform="matrix(1,0,-0.22867874,0.97350194,0,0)" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9pt;line-height:125%;font-family:Times, serif;-inkscape-font-specification:'Times, serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
+ x="-149.44366"
+ y="-93.742676"
+ id="text1897"><tspan
+ sodipodi:role="line"
+ id="tspan1895"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9pt;font-family:Times, serif;-inkscape-font-specification:'Times, serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="-149.44366"
+ y="-93.742676">QGuiApplication</tspan></text>
+ </g>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576)"
+ d="m 508.49821,147.40834 c -5.01114,-15.80414 29.88553,-12.14178 19.22974,-0.30829"
+ id="path2661"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576-4)"
+ d="m 529.10578,290.07508 c 2.22687,-20.59627 43.00062,-73.20365 2.77101,-64.44971"
+ id="path9238"
+ sodipodi:nodetypes="cc" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="495.45242"
+ y="108.62244"
+ id="text9334"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="495.45242"
+ y="108.62244"
+ id="tspan9332">â‘¢ deliverPointerEvent()</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="495.45242"
+ y="119.57681"
+ id="tspan1575">â‘£ deliverUpdatedPoints()</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="495.45242"
+ y="130.53119"
+ id="tspan9365">⑤ localizePointerEvent()</tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576)"
+ d="m 546.28994,291.07097 c -3.52959,-16.19952 30.87947,-9.32879 19.17598,1.46963"
+ id="path14423"
+ sodipodi:nodetypes="cc" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="553.95435"
+ y="276.75214"
+ id="text14441"><tspan
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="553.95435"
+ y="276.75214"
+ sodipodi:role="line"
+ id="tspan2603">⑧ setActive() </tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.03579;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.03579, 1.03579;stroke-dashoffset:0;stroke-opacity:1;marker-start:url(#Arrow5)"
+ d="M 249.32923,23.153194 H 125.81655"
+ id="path1311" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="129.35161"
+ y="18.770849"
+ id="text1315"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="129.35161"
+ y="18.770849"
+ id="tspan1317">â‘  event(QTouchEvent*)</tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.03579;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.03579, 1.03579;stroke-dashoffset:0;stroke-opacity:1;marker-start:url(#Arrow5)"
+ d="M 480.13219,146.11428 434.01617,42.608119"
+ id="path2865"
+ sodipodi:nodetypes="cc" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="224.50352"
+ y="-380.62888"
+ id="text2981"
+ transform="rotate(65.571486)"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="224.50352"
+ y="-380.62888"
+ id="tspan2979">â‘¡ event(QTouchEvent*)</tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576-4-5)"
+ d="m 442.97658,187.15842 c -50.02267,78.60672 40.30509,72.83709 29.70231,109.51513"
+ id="path9238-5"
+ sodipodi:nodetypes="cc" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:end;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:end;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="490.02765"
+ y="220.77097"
+ id="text9304-6"
+ transform="rotate(6.9725705)"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:end;text-anchor:end;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="490.02765"
+ y="220.77097"
+ id="tspan9302"> â‘¥ handlePointerEvent()</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:end;text-anchor:end;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="490.02765"
+ y="231.72534"
+ id="tspan2397">⑦ wantsPointerEvent()</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:end;text-anchor:end;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="490.02765"
+ y="242.6797"
+ id="tspan1147">⑬ onGrabChanged()</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:end;text-anchor:end;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="490.02765"
+ y="252.68085"
+ id="tspan2371" /></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="635.83441"
+ y="282.59363"
+ id="text2293"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="635.83441"
+ y="282.59363"
+ id="tspan2291">⑨ onActiveChanged()</tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576-4)"
+ d="m 621.84617,274.48471 c 33.3115,8.38416 -22.06557,12.65724 -23.12936,24.29582"
+ id="path2399"
+ sodipodi:nodetypes="cc" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="553.40424"
+ y="227.66728"
+ id="text2575"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="553.40424"
+ y="227.66728"
+ id="tspan2573">⑩ activeChanged Ⓢ</tspan><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="553.40424"
+ y="238.62166"
+ id="tspan2601">⑪ setColor()</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#808080;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="text3431"
+ transform="rotate(-38.038218,476.61944,239.40311)"><textPath
+ xlink:href="#path9238-5"
+ id="textPath3609"><tspan
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#808080;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ id="tspan3429">QTouchEvent*</tspan></textPath></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="text3832"
+ transform="rotate(58.431196)"
+ x="474.64865"
+ y="-273.09793"><tspan
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ id="tspan3828">target</tspan></text>
+ <circle
+ id="path4083"
+ style="fill:#000000;stroke:#e6a11b;stroke-width:0.750085"
+ cx="536.95331"
+ cy="217.47479"
+ r="0.0092422031" />
+ <circle
+ id="path4109"
+ style="fill:#000000;stroke:#e6a11b;stroke-width:0.750085"
+ cx="550.58368"
+ cy="218.91232"
+ r="0.0092422031" />
+ <circle
+ id="path4135"
+ style="fill:#000000;stroke:#e6a11b;stroke-width:0.750085"
+ cx="584.84467"
+ cy="-85.462952"
+ r="0.0092422031" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#1c1be6;stroke-width:1.00011;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.00011, 1.00011;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1576-4)"
+ d="m 588.30987,191.03752 c -32.55329,-6.31366 -64.31079,-1.46976 -66.079,-13.41654"
+ id="path1091"
+ sodipodi:nodetypes="cc" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="592.49213"
+ y="194.76711"
+ id="text1121"><tspan
+ sodipodi:role="line"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6pt;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1c1be6;fill-opacity:1;stroke:none;stroke-width:0.750085px"
+ x="592.49213"
+ y="194.76711"
+ id="tspan1119">⑫ grabChanged Ⓢ</tspan></text>
+</svg>
diff --git a/src/quick/doc/src/internal/pinchAndDragHandlers.dot b/src/quick/doc/src/internal/pinchAndDragHandlers.dot
new file mode 100644
index 0000000000..f1b5182a0d
--- /dev/null
+++ b/src/quick/doc/src/internal/pinchAndDragHandlers.dot
@@ -0,0 +1,30 @@
+digraph {
+ llfkmbbephlg[label="ResizeItemToWindow_QMLTYPE_1", shape="tab"]
+ llfkmbbephlg -> llfkmbbeijgg[arrowtail=odiamond, dir=back]
+
+ llfkmbbeijgg[label="â„šRootItem", shape="box"]
+
+ llfkmbbepmbg[label="â„šRectangle", shape="box"]
+ llfkmbbeijgg -> llfkmbbepmbg[arrowtail=odiamond, dir=back]
+
+ llfkmbodpifg[label="â„šPinchHandler", shape="house"]
+ llfkmbbepmbg -> llfkmbodpifg[arrowtail=odiamond, dir=back]
+ llfkmbnfping[label="â„šRectangle", shape="box"]
+ llfkmbbepmbg -> llfkmbnfping[arrowtail=odiamond, dir=back]
+
+ llfkmbbfjjgg[label="â„šDragHandler", shape="house"]
+ llfkmbnfping -> llfkmbbfjjgg[arrowtail=odiamond, dir=back]
+ llfkmbbdfibg[label="â„šRectangle", shape="box"]
+ llfkmbbepmbg -> llfkmbbdfibg[arrowtail=odiamond, dir=back]
+
+ llfkmbbfibdg[label="â„šDragHandler", shape="house"]
+ llfkmbbdfibg -> llfkmbbfibdg[arrowtail=odiamond, dir=back]
+ llfkmbodeang[label="â„šRectangle", shape="box"]
+ llfkmbbepmbg -> llfkmbodeang[arrowtail=odiamond, dir=back]
+
+ llfkmbcknglg[label="â„šDragHandler", shape="house"]
+ llfkmbodeang -> llfkmbcknglg[arrowtail=odiamond, dir=back]
+ llfkmbclplkg[label="â„šDeliveryAgent", shape="invhouse"]
+ llfkmbbeijgg -> llfkmbclplkg[arrowtail=odiamond, dir=back]
+
+}
diff --git a/src/quick/doc/src/internal/pinchAndDragHandlers.svg b/src/quick/doc/src/internal/pinchAndDragHandlers.svg
new file mode 100644
index 0000000000..674315d5d2
--- /dev/null
+++ b/src/quick/doc/src/internal/pinchAndDragHandlers.svg
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Pages: 1 -->
+<svg width="619pt" height="332pt"
+ viewBox="0.00 0.00 619.07 332.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 328)">
+<polygon fill="white" stroke="none" points="-4,4 -4,-328 615.07,-328 615.07,4 -4,4"/>
+<!-- llfkmbbephlg -->
+<g id="node1" class="node">
+<title>llfkmbbephlg</title>
+<polygon fill="none" stroke="black" points="474.53,-324 262.78,-324 262.78,-328 250.78,-328 250.78,-288 474.53,-288 474.53,-324"/>
+<polyline fill="none" stroke="black" points="250.78,-324 262.78,-324"/>
+<text text-anchor="middle" x="362.66" y="-300.95" font-family="Times,serif" font-size="9pt">ResizeItemToWindow_QMLTYPE_1</text>
+</g>
+<!-- llfkmbbeijgg -->
+<g id="node2" class="node">
+<title>llfkmbbeijgg</title>
+<polygon fill="none" stroke="black" points="402.53,-252 322.78,-252 322.78,-216 402.53,-216 402.53,-252"/>
+<text text-anchor="middle" x="362.66" y="-229.32" font-family="Times,serif" font-size="9pt">â„šRootItem</text>
+</g>
+<!-- llfkmbbephlg&#45;&gt;llfkmbbeijgg -->
+<g id="edge1" class="edge">
+<title>llfkmbbephlg&#45;&gt;llfkmbbeijgg</title>
+<path fill="none" stroke="black" d="M362.66,-274.81C362.66,-267.04 362.66,-258.95 362.66,-252.1"/>
+<polygon fill="none" stroke="black" points="362.66,-274.8 366.66,-280.8 362.66,-286.8 358.66,-280.8 362.66,-274.8"/>
+</g>
+<!-- llfkmbbepmbg -->
+<g id="node3" class="node">
+<title>llfkmbbepmbg</title>
+<polygon fill="none" stroke="black" points="327.66,-180 245.66,-180 245.66,-144 327.66,-144 327.66,-180"/>
+<text text-anchor="middle" x="286.66" y="-157.32" font-family="Times,serif" font-size="9pt">â„šRectangle</text>
+</g>
+<!-- llfkmbbeijgg&#45;&gt;llfkmbbepmbg -->
+<g id="edge2" class="edge">
+<title>llfkmbbeijgg&#45;&gt;llfkmbbepmbg</title>
+<path fill="none" stroke="black" d="M334.28,-206.87C324.67,-198.01 314.18,-188.35 305.49,-180.35"/>
+<polygon fill="none" stroke="black" points="334.38,-206.96 341.51,-208.08 343.21,-215.09 336.09,-213.96 334.38,-206.96"/>
+</g>
+<!-- llfkmbclplkg -->
+<g id="node11" class="node">
+<title>llfkmbclplkg</title>
+<polygon fill="none" stroke="black" points="345.8,-156.44 439.66,-144 533.52,-156.44 533.43,-176.56 345.89,-176.56 345.8,-156.44"/>
+<text text-anchor="middle" x="439.66" y="-157.32" font-family="Times,serif" font-size="9pt">â„šDeliveryAgent</text>
+</g>
+<!-- llfkmbbeijgg&#45;&gt;llfkmbclplkg -->
+<g id="edge10" class="edge">
+<title>llfkmbbeijgg&#45;&gt;llfkmbclplkg</title>
+<path fill="none" stroke="black" d="M391.18,-207.07C402.46,-196.82 414.97,-185.45 424.45,-176.82"/>
+<polygon fill="none" stroke="black" points="391.24,-207.02 389.49,-214.01 382.36,-215.09 384.11,-208.09 391.24,-207.02"/>
+</g>
+<!-- llfkmbodpifg -->
+<g id="node4" class="node">
+<title>llfkmbodpifg</title>
+<polygon fill="none" stroke="black" points="177.32,-95.56 88.66,-108 0,-95.56 0.08,-75.44 177.23,-75.44 177.32,-95.56"/>
+<text text-anchor="middle" x="88.66" y="-85.33" font-family="Times,serif" font-size="9pt">â„šPinchHandler</text>
+</g>
+<!-- llfkmbbepmbg&#45;&gt;llfkmbodpifg -->
+<g id="edge3" class="edge">
+<title>llfkmbbepmbg&#45;&gt;llfkmbodpifg</title>
+<path fill="none" stroke="black" d="M233.33,-142.15C198.64,-129.88 154.38,-114.23 124,-103.49"/>
+<polygon fill="none" stroke="black" points="233.47,-142.2 240.46,-140.42 244.78,-146.2 237.79,-147.97 233.47,-142.2"/>
+</g>
+<!-- llfkmbnfping -->
+<g id="node5" class="node">
+<title>llfkmbnfping</title>
+<polygon fill="none" stroke="black" points="277.66,-108 195.66,-108 195.66,-72 277.66,-72 277.66,-108"/>
+<text text-anchor="middle" x="236.66" y="-85.33" font-family="Times,serif" font-size="9pt">â„šRectangle</text>
+</g>
+<!-- llfkmbbepmbg&#45;&gt;llfkmbnfping -->
+<g id="edge4" class="edge">
+<title>llfkmbbepmbg&#45;&gt;llfkmbnfping</title>
+<path fill="none" stroke="black" d="M266.73,-133.1C260.69,-124.65 254.25,-115.62 248.88,-108.1"/>
+<polygon fill="none" stroke="black" points="266.8,-133.2 273.54,-135.76 273.78,-142.96 267.03,-140.41 266.8,-133.2"/>
+</g>
+<!-- llfkmbbdfibg -->
+<g id="node7" class="node">
+<title>llfkmbbdfibg</title>
+<polygon fill="none" stroke="black" points="377.66,-108 295.66,-108 295.66,-72 377.66,-72 377.66,-108"/>
+<text text-anchor="middle" x="336.66" y="-85.33" font-family="Times,serif" font-size="9pt">â„šRectangle</text>
+</g>
+<!-- llfkmbbepmbg&#45;&gt;llfkmbbdfibg -->
+<g id="edge6" class="edge">
+<title>llfkmbbepmbg&#45;&gt;llfkmbbdfibg</title>
+<path fill="none" stroke="black" d="M306.59,-133.1C312.62,-124.65 319.07,-115.62 324.44,-108.1"/>
+<polygon fill="none" stroke="black" points="306.52,-133.2 306.28,-140.41 299.54,-142.96 299.77,-135.76 306.52,-133.2"/>
+</g>
+<!-- llfkmbodeang -->
+<g id="node9" class="node">
+<title>llfkmbodeang</title>
+<polygon fill="none" stroke="black" points="522.66,-108 440.66,-108 440.66,-72 522.66,-72 522.66,-108"/>
+<text text-anchor="middle" x="481.66" y="-85.33" font-family="Times,serif" font-size="9pt">â„šRectangle</text>
+</g>
+<!-- llfkmbbepmbg&#45;&gt;llfkmbodeang -->
+<g id="edge8" class="edge">
+<title>llfkmbbepmbg&#45;&gt;llfkmbodeang</title>
+<path fill="none" stroke="black" d="M340.14,-141.8C371.75,-130.45 411.16,-116.31 440.36,-105.82"/>
+<polygon fill="none" stroke="black" points="340.16,-141.8 335.86,-147.59 328.86,-145.85 333.16,-140.06 340.16,-141.8"/>
+</g>
+<!-- llfkmbbfjjgg -->
+<g id="node6" class="node">
+<title>llfkmbbfjjgg</title>
+<polygon fill="none" stroke="black" points="233.07,-23.56 147.66,-36 62.25,-23.56 62.33,-3.44 232.99,-3.44 233.07,-23.56"/>
+<text text-anchor="middle" x="147.66" y="-13.33" font-family="Times,serif" font-size="9pt">â„šDragHandler</text>
+</g>
+<!-- llfkmbnfping&#45;&gt;llfkmbbfjjgg -->
+<g id="edge5" class="edge">
+<title>llfkmbnfping&#45;&gt;llfkmbbfjjgg</title>
+<path fill="none" stroke="black" d="M204.77,-63.92C191.91,-53.8 177.52,-42.48 166.4,-33.74"/>
+<polygon fill="none" stroke="black" points="204.52,-63.72 211.71,-64.29 213.95,-71.14 206.76,-70.57 204.52,-63.72"/>
+</g>
+<!-- llfkmbbfibdg -->
+<g id="node8" class="node">
+<title>llfkmbbfibdg</title>
+<polygon fill="none" stroke="black" points="422.07,-23.56 336.66,-36 251.25,-23.56 251.33,-3.44 421.99,-3.44 422.07,-23.56"/>
+<text text-anchor="middle" x="336.66" y="-13.33" font-family="Times,serif" font-size="9pt">â„šDragHandler</text>
+</g>
+<!-- llfkmbbdfibg&#45;&gt;llfkmbbfibdg -->
+<g id="edge7" class="edge">
+<title>llfkmbbdfibg&#45;&gt;llfkmbbfibdg</title>
+<path fill="none" stroke="black" d="M336.66,-58.81C336.66,-51.04 336.66,-42.95 336.66,-36.1"/>
+<polygon fill="none" stroke="black" points="336.66,-58.8 340.66,-64.8 336.66,-70.8 332.66,-64.8 336.66,-58.8"/>
+</g>
+<!-- llfkmbcknglg -->
+<g id="node10" class="node">
+<title>llfkmbcknglg</title>
+<polygon fill="none" stroke="black" points="611.07,-23.56 525.66,-36 440.25,-23.56 440.33,-3.44 610.99,-3.44 611.07,-23.56"/>
+<text text-anchor="middle" x="525.66" y="-13.33" font-family="Times,serif" font-size="9pt">â„šDragHandler</text>
+</g>
+<!-- llfkmbodeang&#45;&gt;llfkmbcknglg -->
+<g id="edge9" class="edge">
+<title>llfkmbodeang&#45;&gt;llfkmbcknglg</title>
+<path fill="none" stroke="black" d="M499.32,-60.9C504.94,-51.96 510.95,-42.4 515.81,-34.67"/>
+<polygon fill="none" stroke="black" points="499.4,-60.77 499.59,-67.98 493.01,-70.93 492.82,-63.72 499.4,-60.77"/>
+</g>
+</g>
+</svg>
diff --git a/src/quick/doc/src/internal/textEdit.dot b/src/quick/doc/src/internal/textEdit.dot
new file mode 100644
index 0000000000..19986bcc21
--- /dev/null
+++ b/src/quick/doc/src/internal/textEdit.dot
@@ -0,0 +1,52 @@
+digraph G {
+ node [
+ shape=record
+ ]
+
+ textedit[label="â„šTextEdit"]
+ texteditpriv[label="â„šTextEditPrivate"]
+ texteditextra[label="{â„šTextEditPrivate::ExtraData|padding...}"]
+ textctrl[label="â„šTextControl"]
+ qqpxm[label="â„šPixmap"]
+
+ subgraph cluster_1 {
+ label = "swappable document"
+ color=green
+
+ doc[label="QTextDocument"]
+ docpriv[label="QTextDocumentPrivate"]
+ doclayout[label="QTextDocumentLayout"]
+ qqtih[label="{â„šTextImageHandler||QSizeF intrinsicSize(doc, pos, format)}"]
+
+ doc -> docpriv [arrowtail=diamond, dir=back];
+ doc -> doclayout[arrowhead=vee, label="creates lazily", style="dotted", color="blue"]
+ }
+
+ subgraph cluster_2 {
+ label = "lazily constructed"
+ color=grey
+
+ qqdoc[label="{â„šTextDocument||textDocument()\nsetTextDocument()}"]
+ qqdocpriv[label="â„šTextDocumentPrivate"]
+
+ qqdoc -> qqdocpriv[arrowtail=diamond, dir=back]
+ }
+
+ textedit -> texteditpriv[arrowtail=diamond, dir=back]
+ texteditpriv -> texteditextra[arrowtail=diamond, dir=back]
+ texteditpriv -> doc[arrowtail=odiamond, dir=back]
+ texteditpriv -> qqdoc[arrowtail=odiamond, dir=back]
+ texteditpriv -> textctrl[arrowtail=diamond, dir=back]
+ texteditpriv -> qqpxm[arrowtail=diamond, dir=back, headlabel = "n", label="pixmapsInProgress"]
+ texteditpriv -> qqtih[arrowhead=vee, label="creates", style="dotted", color="blue"]
+ texteditpriv -> qqdoc[arrowhead=vee, label="creates", style="dotted", color="blue"]
+ texteditpriv -> doc[arrowhead=vee, label="creates", style="dotted", color="blue"]
+ textedit -> qqpxm[arrowhead=vee, label="creates", style="dotted", color="blue"]
+ qqtih -> doc[arrowhead=vee, label="parent", style="dashed"]
+ doc -> textedit[arrowhead=vee, label="parent", style="dashed"]
+ qqdoc -> textedit[arrowhead=vee, label="parent", style="dashed"]
+ doclayout -> doc[arrowhead=vee, label="parent", style="dashed"]
+ docpriv -> doclayout[arrowtail=diamond, dir=back]
+ doclayout -> qqtih[arrowtail=odiamond, dir=back, label="registered handler", style="dashed"] # storage is complex
+ qqdocpriv -> doc[arrowtail=odiamond, dir=back, taillabel="QPointer"]
+}
diff --git a/src/quick/doc/src/qmltypereference.qdoc b/src/quick/doc/src/qmltypereference.qdoc
index a2c913de49..db750bf077 100644
--- a/src/quick/doc/src/qmltypereference.qdoc
+++ b/src/quick/doc/src/qmltypereference.qdoc
@@ -17,11 +17,11 @@ import QtQuick
\endqml
Visit the \l {Qt Quick} module documentation for more
-information about the concepts which are central to \c QtQuick.
+information about the concepts that are central to \c QtQuick.
\section1 Submodules
- Qt Quick includes several submodules which contain additional types.
+ Qt Quick includes several submodules that contain additional types:
\list
\li \l{Qt Quick Local Storage QML Types}{Local Storage} - a submodule
@@ -33,6 +33,12 @@ information about the concepts which are central to \c QtQuick.
\li \l{Qt Quick Layouts QML Types}{Layouts} - contains types that are used
to arrange items in the user interface
\li \l{Qt Quick Test QML Types}{Tests} - types for testing QML applications.
+ \li \l{Qt Quick Effects QML Types}{Effects} - provides types for applying
+ one or more simple graphical effects to Qt Quick items.
+ \li \l{Qt Quick Shapes QML Types}{Shapes} - provides types for rendering vector shapes in
+ a Qt Quick scene.
+ \li \l{Qt Quick Vector Image QML Types}{Vector Image} - provides types for displaying vector
+ image files.
\endlist
\target value-types
@@ -48,7 +54,7 @@ In addition, the \c QtQuick import provides the following value types:
\section1 Object Types
Most object types provided by the \c QtQuick import are based on the \l{Item}
-type, which itself derives from \l{QtQml::QtObject}{QtObject}. \l{Qt QML QML
+type, which itself derives from \l{QtQml::QtObject}{QtObject}. \l{Qt Qml QML
Types#Object Types} {QML object types} provided by the Qt QML module (such as
\l{QtQml::QtObject}{QtObject} and \l{QtQml::Component}{Component}) are also
available when you import \c QtQuick.
@@ -108,7 +114,7 @@ available when you import \c QtQuick.
\section1 SVG Color Reference
The following table lists the available
- \l {http://www.w3.org/TR/SVG/types.html#ColorKeywords}{SVG colors}:
+ \l {https://www.w3.org/TR/css-color-3/#svg-color}{SVG colors}:
\include svg-colors.qdocinc
@@ -151,13 +157,21 @@ available when you import \c QtQuick.
\li \l bool \c font.kerning
\li \l bool \c font.preferShaping
\li \l enumeration \c font.hintingPreference
+ \li \c object \l [QML] {QtQuick::Text::}{font.features}
\li \l string \c font.styleName
+ \li \c object \c [QML] {QtQuick::Text::}{font.variableAxes}
+ \li \l bool \c font.contextFontMerging
+ \li \l bool \c font.preferTypoLineMetrics
\endlist
Example:
- \qml
- Text { font.family: "Helvetica"; font.pointSize: 13; font.bold: true }
- \endqml
+
+ \snippet qml/font.qml text
+
+ As \c font is a \l {QML_STRUCTURED_VALUE}{structured value} type, it can
+ also be constructed with a JavaScript object:
+
+ \snippet qml/font.qml structured-value-construction
When integrating with C++, note that any QFont value
\l{qtqml-cppintegration-data.html}{passed into QML from C++} is automatically
@@ -165,71 +179,36 @@ available when you import \c QtQuick.
This value type is provided by the QtQuick import.
- Font weighting is classified on a scale from 0 to 99, where a weight of 0 is ultralight,
- and 99 is extremely black. The following values are supported:
+ Numerical values for font weights follow the CSS specification, where a
+ weight of 100 is extremely light, and 900 is extremely bold.
+ The following values are supported:
- \table
- \row
- \li \c Font.Thin
- \li 0
- \row
- \li \c Font.ExtraLight
- \li 12
- \row
- \li \c Font.Light
- \li 25
- \row
- \li \c Font.Normal
- \li 50
- \row
- \li \c Font.Medium
- \li 57
- \row
- \li \c Font.DemiBold
- \li 63
- \row
- \li \c Font.Bold
- \li 75
- \row
- \li \c Font.ExtraBold
- \li 81
- \row
- \li \c Font.Black
- \li 87
- \endtable
+ \value Font.Thin 100
+ \value Font.ExtraLight 200
+ \value Font.Light 300
+ \value Font.Normal 400
+ \value Font.Medium 500
+ \value Font.DemiBold 600
+ \value Font.Bold 700
+ \value Font.ExtraBold 800
+ \value Font.Black 900
Capitalization supports the following values:
- \table
- \row
- \li \c Font.MixedCase
- \li No capitalization change is applied.
- \row
- \li \c Font.AllUppercase
- \li Alters the text to be rendered in all uppercase type.
- \row
- \li \c Font.AllLowercase
- \li Alters the text to be rendered in all lowercase type.
- \row
- \li \c Font.SmallCaps
- \li Alters the text to be rendered in small-caps type.
- \row
- \li \c Font.Capitalize
- \li Alters the text to be rendered with the first character of each word as an uppercase character.
- \endtable
+ \value Font.MixedCase No capitalization change is applied.
+ \value Font.AllUppercase Alters the text to be rendered in all uppercase type.
+ \value Font.AllLowercase Alters the text to be rendered in all lowercase type.
+ \value Font.SmallCaps Alters the text to be rendered in small-caps type.
+ \value Font.Capitalize Alters the text to be rendered with the first character of each word as an uppercase character.
Setting the hinting preference only has an effect when using the "NativeRendering" render type.
The property supports the following values:
- \list
- \value Font.PreferDefaultHinting - Use the default hinting level for the target platform.
- \value Font.PreferNoHinting - If possible, render text without hinting the outlines
- of the glyphs.
- \value Font.PreferVerticalHinting - If possible, render text with no horizontal hinting,
- but align glyphs to the pixel grid in the vertical direction.
- \value Font.PreferFullHinting - If possible, render text with hinting in both horizontal and
- vertical directions.
- \endlist
+ \value Font.PreferDefaultHinting Use the default hinting level for the target platform.
+ \value Font.PreferNoHinting If possible, render text without hinting the outlines of the glyphs.
+ \value Font.PreferVerticalHinting If possible, render text with no horizontal hinting,
+ but align glyphs to the pixel grid in the vertical direction.
+ \value Font.PreferFullHinting If possible, render text with hinting in both horizontal and vertical directions.
\sa {QML Value Types}
*/
diff --git a/src/quick/doc/src/qtquick-android.qdoc b/src/quick/doc/src/qtquick-android.qdoc
new file mode 100644
index 0000000000..b21378565d
--- /dev/null
+++ b/src/quick/doc/src/qtquick-android.qdoc
@@ -0,0 +1,14 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+\page qtquick-android-classes.html
+\title Qt Quick Android Classes
+\summary The Qt Quick module provides classes for embedding Qt Quick in Android Applications.
+
+ \note Classes under this module requires \l{Qt for Android}.
+
+ \annotatedlist qt_android_classes
+
+ For more information on Qt Quick module, see the \l{Qt Quick} module documentation.
+*/
diff --git a/src/quick/doc/src/qtquick-how-tos.qdoc b/src/quick/doc/src/qtquick-how-tos.qdoc
new file mode 100644
index 0000000000..c030b071e2
--- /dev/null
+++ b/src/quick/doc/src/qtquick-how-tos.qdoc
@@ -0,0 +1,109 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \page qtquick-how-tos.html
+ \title Qt Quick How-tos
+
+ This page aims to provide an easily discoverable, useful reference that
+ shows the simplest and best way of performing specific tasks in Qt Quick.
+ Each solution provides QML and/or C++ code snippets where applicable, and
+ every snippet is automatically tested by Qt to ensure they remain
+ functional.
+
+ How do I:
+
+ \list
+ \li \l {Call a C++ function from QML when a Button is clicked}
+ \li \l {See which item has active focus}
+ \li \l {Create a time picker like Android's TimePickerDialog}
+ \li \l {Use a C++ enum in JavaScript}
+ \endlist
+
+
+ \section1 Call a C++ function from QML when a Button is clicked
+
+ Assuming that the C++ type should be globally available to the QML
+ files in the application, the simplest way is to make it a QML singleton
+ with \l QML_SINGLETON. For example, in the header file, \c backend.h:
+
+ \snippet how-tos/how-to-cpp-button/backend.h file
+
+ \c backend.cpp:
+
+ \snippet how-tos/how-to-cpp-button/backend.cpp file
+
+ You can then call that function from any QML file:
+
+ \snippet how-tos/how-to-cpp-button/Main.qml file
+
+ If the C++ type only needs to be available to a small set of QML files,
+ consider using \l QML_ELEMENT. For more ways of exposing C++ types to QML,
+ see \l {Choosing the Correct Integration Method Between C++ and QML}.
+
+ This example assumes that the \c Backend type is available in a QML module.
+ With CMake, this is done via \l qt_add_qml_module. For an example that
+ demonstrates this in detail, see \l {Building a QML application}.
+
+
+ \section1 See which item has active focus
+
+ Write a \l {Property change signal handlers}{property change signal handler}
+ for the window's \l {Window::}{activeFocusItem} property:
+
+ \snippet how-tos/how-to-qml/active-focus-debugging/ActiveFocusDebuggingMain.qml file
+
+ This will print the item which currently has active focus to the console.
+ To ensure that the output is useful, give each item a descriptive
+ \l {QtObject::}{objectName}.
+
+
+ \section1 Create a time picker like Android's TimePickerDialog
+
+ We've prepared an example that consists of a few
+ \l {https://code.qt.io/cgit/qt/qtdeclarative.git/tree/tests/auto/quick/doc/how-tos/how-to-qml/time-picker/TimeComponentLabel.qml?h=\QtMajorVersion.\QtMinorVersion}
+ {QML files} which demonstrate how to do this. They can be used
+ in your application in the following manner:
+
+ \snippet how-tos/how-to-qml/time-picker/TimePickerMain.qml file
+
+ \table
+ \row
+ \li \image how-to-time-picker-light.png
+ \caption TimePickerDialog in its light theme.
+ \li \image how-to-time-picker-dark.png
+ \caption TimePickerDialog in its dark theme.
+ \endtable
+
+
+ \section1 Use a C++ enum in JavaScript
+
+ To expose a C++ enum to JavaScript (that is, \l QJSEngine, not
+ \l QQmlEngine or \l QQmlApplicationEngine), use
+ \l QJSEngine::newQMetaObject():
+
+ \quotefromfile how-tos/how-to-cpp-enum-js/tst_how-to-cpp-enum-js.cpp
+ \skipto QJSEngine engine
+ \printuntil setProperty
+ \skipto Backend backend
+ \printuntil backend.load()
+
+ The enum can then be used from JavaScript:
+
+ \snippet how-tos/how-to-cpp-enum-js/script.mjs file
+
+ When using \l QQmlEngine or \l QQmlApplicationEngine, there are easier
+ options; see
+ \l {Choosing the Correct Integration Method Between C++ and QML}
+ for more information.
+
+ \c backend.h:
+
+ \snippet how-tos/how-to-cpp-enum-js/backend.h file
+
+ \c backend.cpp:
+
+ \snippet how-tos/how-to-cpp-enum-js/backend.cpp file
+
+ For more information, see \l {QObject Integration}.
+*/
diff --git a/src/quick/doc/src/qtquick.qdoc b/src/quick/doc/src/qtquick.qdoc
index 6797169312..0d0890398f 100644
--- a/src/quick/doc/src/qtquick.qdoc
+++ b/src/quick/doc/src/qtquick.qdoc
@@ -7,7 +7,7 @@
\brief The Qt Quick module implements the "standard library" for QML
The Qt Quick module is the standard library for writing QML applications.
-While the \l{Qt QML} module provides the QML engine and
+While the \l{Qt Qml} module provides the QML engine and
language infrastructure, the Qt Quick module provides all the basic
types necessary for creating user interfaces with QML. It
provides a visual canvas and includes types for creating and animating
@@ -61,8 +61,10 @@ shader effects.
{States, Transitions And Animations}
\li \l {Important Concepts In Qt Quick - Data - Models, Views and data Storage}
{Data - Models, Views and Data Storage}
- \li \l {Important Concepts In Qt Quick - Graphical Effects}{Particles And
- Graphical Effects}
+ \li \list \l {Important Concepts In Qt Quick - Graphical Effects}
+ {Particles And Graphical Effects}
+ \li \l MultiEffect
+ \endlist
\li \l {Important Concepts In Qt Quick - Convenience Types}
{Convenience Types}
\endlist
@@ -71,7 +73,7 @@ When using the Qt Quick module, you will need to know how to write QML
applications using the QML language. In particular, QML Basics and QML
Essentials from the \l{QML Applications} page.
-To find out more about using the QML language, see the \l{Qt QML} module documentation.
+To find out more about using the QML language, see the \l{Qt Qml} module documentation.
\section1 C++ Extension Points
@@ -99,9 +101,11 @@ To find out more about using the QML language, see the \l{Qt QML} module documen
\li \l {QML Applications}
- essential information for application development with QML and Qt
Quick
- \li \l {Qt QML}
+ \li \l {Qt Qml}
- documentation for the Qt QML module, which provides the QML engine
and language infrastructure
+ \li \l {Qt Quick How-tos}
+ - shows how to achieve specific tasks in Qt Quick
\endlist
\endlist
@@ -119,6 +123,8 @@ To find out more about using the QML language, see the \l{Qt QML} module documen
\li \l {Qt Quick QML Types}
- a list of QML types provided by the \c{QtQuick} import
\list
+ \li \l {Qt Quick Effects QML Types} {Effects}
+ - provides graphical effects to apply to Qt Quick items
\li \l {Qt Quick Local Storage QML Types} {Local Storage}
- a submodule containing a JavaScript interface for an SQLite
database
@@ -128,9 +134,22 @@ To find out more about using the QML language, see the \l{Qt QML} module documen
- provides layouts for arranging Qt Quick items
\li \l {Qt Quick Test QML Types} {Tests}
- contains types for writing unit test for a QML application
+ \li \l{Qt Quick Shapes QML Types}{Shapes}
+ - provides types for rendering vector shapes in a Qt Quick scene.
+ \li \l{Qt Quick Vector Image QML Types}{Vector Image} - provides types for displaying
+ vector image files.
\endlist
+ \li \l {Qt Quick Android Classes}
+ - provides classes for using QML with Java/Kotlin Android APIs.
\endlist
+\section1 Qt Academy Courses
+ \list
+ \li \l {https://www.qt.io/academy/course-catalog#introduction-to-qt-quick}
+ {Introduction to Qt Quick}
+ \endlist
+
+
\section1 Licenses and Attributions
Qt Quick is available under commercial licenses from \l{The Qt Company}.
diff --git a/src/quick/handlers/qquickdragaxis_p.h b/src/quick/handlers/qquickdragaxis_p.h
index 5d0feba908..d9aecf9d9d 100644
--- a/src/quick/handlers/qquickdragaxis_p.h
+++ b/src/quick/handlers/qquickdragaxis_p.h
@@ -24,7 +24,7 @@ QT_BEGIN_NAMESPACE
class QQuickItem;
class QQuickPointerHandler;
-class Q_QUICK_PRIVATE_EXPORT QQuickDragAxis : public QObject
+class Q_QUICK_EXPORT QQuickDragAxis : public QObject
{
Q_OBJECT
Q_PROPERTY(qreal minimum READ minimum WRITE setMinimum NOTIFY minimumChanged)
diff --git a/src/quick/handlers/qquickdraghandler.cpp b/src/quick/handlers/qquickdraghandler.cpp
index 3d807e0f2d..dad07189db 100644
--- a/src/quick/handlers/qquickdraghandler.cpp
+++ b/src/quick/handlers/qquickdraghandler.cpp
@@ -52,7 +52,7 @@ Q_LOGGING_CATEGORY(lcDragHandler, "qt.quick.handler.drag")
At this time, drag-and-drop is not yet supported.
- \sa Drag, MouseArea
+ \sa Drag, MouseArea, {Qt Quick Examples - Pointer Handlers}
*/
QQuickDragHandler::QQuickDragHandler(QQuickItem *parent)
@@ -95,13 +95,13 @@ void QQuickDragHandler::onGrabChanged(QQuickPointerHandler *grabber, QPointingDe
This property holds the snap mode.
- The snap mode configures snapping of the \l target item's center to the event point.
+ The snap mode configures snapping of the \l target item's center to the \l eventPoint.
Possible values:
\value DragHandler.SnapNever Never snap
- \value DragHandler.SnapAuto The \l target snaps if the event point was pressed outside of the \l target
+ \value DragHandler.SnapAuto The \l target snaps if the \l eventPoint was pressed outside of the \l target
item \e and the \l target is a descendant of \l {PointerHandler::}{parent} item (default)
- \value DragHandler.SnapWhenPressedOutsideTarget The \l target snaps if the event point was pressed outside of the \l target
+ \value DragHandler.SnapWhenPressedOutsideTarget The \l target snaps if the \l eventPoint was pressed outside of the \l target
\value DragHandler.SnapAlways Always snap
*/
QQuickDragHandler::SnapMode QQuickDragHandler::snapMode() const
@@ -184,7 +184,9 @@ void QQuickDragHandler::handlePointerEventImpl(QPointerEvent *event)
// and in approximately the same direction
qreal minAngle = 361;
qreal maxAngle = -361;
- bool allOverThreshold = !event->isEndEvent();
+ bool allOverThreshold = QQuickDeliveryAgentPrivate::isTouchEvent(event) ?
+ static_cast<QTouchEvent *>(event)->touchPointStates() != QEventPoint::Released :
+ !event->isEndEvent();
QVector<QEventPoint> chosenPoints;
if (event->isBeginEvent())
@@ -264,17 +266,6 @@ void QQuickDragHandler::handlePointerEventImpl(QPointerEvent *event)
}
}
-void QQuickDragHandler::enforceConstraints()
-{
- if (!target() || !target()->parentItem())
- return;
- QPointF pos = target()->position();
- QPointF copy(pos);
- enforceAxisConstraints(&pos);
- if (pos != copy)
- target()->setPosition(pos);
-}
-
void QQuickDragHandler::enforceAxisConstraints(QPointF *localPos)
{
if (m_xAxis.enabled())
@@ -377,6 +368,24 @@ void QQuickDragHandler::setActiveTranslation(const QVector2D &trans)
\c {0, 0} again.
*/
+/*!
+ \qmlproperty flags QtQuick::DragHandler::acceptedButtons
+
+ The mouse buttons that can activate this DragHandler.
+
+ By default, this property is set to
+ \l {QtQuick::MouseEvent::button} {Qt.LeftButton}.
+ It can be set to an OR combination of mouse buttons, and will ignore events
+ from other buttons.
+
+ For example, if a component (such as TextEdit) already handles
+ left-button drags in its own way, it can be augmented with a
+ DragHandler that does something different when dragged via the
+ right button:
+
+ \snippet pointerHandlers/dragHandlerAcceptedButtons.qml 0
+*/
+
QT_END_NAMESPACE
#include "moc_qquickdraghandler_p.cpp"
diff --git a/src/quick/handlers/qquickdraghandler_p.h b/src/quick/handlers/qquickdraghandler_p.h
index f67e0ccf72..e8f2ef5e1d 100644
--- a/src/quick/handlers/qquickdraghandler_p.h
+++ b/src/quick/handlers/qquickdraghandler_p.h
@@ -22,7 +22,7 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-class Q_QUICK_PRIVATE_EXPORT QQuickDragHandler : public QQuickMultiPointHandler
+class Q_QUICK_EXPORT QQuickDragHandler : public QQuickMultiPointHandler
{
Q_OBJECT
Q_PROPERTY(QQuickDragAxis * xAxis READ xAxis CONSTANT)
@@ -62,8 +62,6 @@ public:
QQuickDragHandler::SnapMode snapMode() const;
void setSnapMode(QQuickDragHandler::SnapMode mode);
- void enforceConstraints();
-
Q_SIGNALS:
void translationChanged(QVector2D delta);
Q_REVISION(2, 14) void snapModeChanged();
diff --git a/src/quick/handlers/qquickhandlerpoint.cpp b/src/quick/handlers/qquickhandlerpoint.cpp
index cb15dbccd6..c9a197d3dc 100644
--- a/src/quick/handlers/qquickhandlerpoint.cpp
+++ b/src/quick/handlers/qquickhandlerpoint.cpp
@@ -6,15 +6,14 @@
#include "private/qquickdeliveryagent_p_p.h"
QT_BEGIN_NAMESPACE
-Q_DECLARE_LOGGING_CATEGORY(lcTouchTarget)
/*!
- \qmltype HandlerPoint
+ \qmltype handlerPoint
\instantiates QQuickHandlerPoint
\inqmlmodule QtQuick
\brief An event point.
- A QML representation of a QEventPoint.
+ A handler-owned QML representation of a QEventPoint.
It's possible to make bindings to properties of a handler's current
\l {SinglePointHandler::point}{point} or
@@ -23,15 +22,15 @@ Q_DECLARE_LOGGING_CATEGORY(lcTouchTarget)
\snippet pointerHandlers/dragHandlerNullTarget.qml 0
The point is kept up-to-date when the DragHandler is actively responding to
- an EventPoint; but after the point is released, or when the current point is
+ an \l eventPoint; but after the point is released, or when the current point is
being handled by a different handler, \c position.x and \c position.y are 0.
- \note This is practically identical to QtQuick::EventPoint; however an
- EventPoint is a long-lived QObject which is invalidated between gestures
- and reused for subsequent event deliveries. Continuous bindings to its
+ \note This is practically identical to \l eventPoint; however an eventPoint
+ is a short-lived copy of a long-lived Q_GADGET which is invalidated between
+ gestures and reused for subsequent event deliveries. Continuous bindings to its
properties are not possible, and an individual handler cannot rely on it
outside the period when that point is part of an active gesture which that
- handler is handling. HandlerPoint is a Q_GADGET that the handler owns.
+ handler is handling. handlerPoint is a Q_GADGET that the handler owns.
This allows you to make lifetime bindings to its properties.
\sa SinglePointHandler::point, MultiPointHandler::centroid
@@ -145,13 +144,13 @@ void QQuickHandlerPoint::reset(const QVector<QQuickHandlerPoint> &points)
/*!
\readonly
- \qmlproperty int QtQuick::HandlerPoint::id
+ \qmlproperty int QtQuick::handlerPoint::id
\brief The ID number of the point
During a touch gesture, from the time that the first finger is pressed
until the last finger is released, each touchpoint will have a unique ID
number. Likewise, if input from multiple devices occurs (for example
- simultaneous mouse and touch presses), all the current event points from
+ simultaneous mouse and touch presses), all the current \l{eventPoint}{eventPoints} from
all the devices will have unique IDs.
\note Do not assume that id numbers start at zero or that they are
@@ -163,7 +162,7 @@ void QQuickHandlerPoint::reset(const QVector<QQuickHandlerPoint> &points)
/*!
\readonly
- \qmlproperty pointingDeviceUniqueId QtQuick::HandlerPoint::uniqueId
+ \qmlproperty pointingDeviceUniqueId QtQuick::handlerPoint::uniqueId
\brief The unique ID of the point, if any
This is normally empty, because touchscreens cannot uniquely identify fingers.
@@ -185,25 +184,25 @@ void QQuickHandlerPoint::reset(const QVector<QQuickHandlerPoint> &points)
/*!
\readonly
- \qmlproperty QPointF QtQuick::HandlerPoint::position
+ \qmlproperty QPointF QtQuick::handlerPoint::position
\brief The position within the \c parent Item
- This is the position of the event point relative to the bounds of
+ This is the position of the \l eventPoint relative to the bounds of
the \l {PointerHandler::parent} {parent}.
*/
/*!
\readonly
- \qmlproperty QPointF QtQuick::HandlerPoint::scenePosition
+ \qmlproperty QPointF QtQuick::handlerPoint::scenePosition
\brief The position within the scene
- This is the position of the event point relative to the bounds of the Qt
+ This is the position of the \l eventPoint relative to the bounds of the Qt
Quick scene (typically the whole window).
*/
/*!
\readonly
- \qmlproperty QPointF QtQuick::HandlerPoint::pressPosition
+ \qmlproperty QPointF QtQuick::handlerPoint::pressPosition
\brief The pressed position within the \c parent Item
This is the position at which this point was pressed, relative to the
@@ -212,7 +211,7 @@ void QQuickHandlerPoint::reset(const QVector<QQuickHandlerPoint> &points)
/*!
\readonly
- \qmlproperty QPointF QtQuick::HandlerPoint::scenePressPosition
+ \qmlproperty QPointF QtQuick::handlerPoint::scenePressPosition
\brief The pressed position within the scene
This is the position at which this point was pressed, in the coordinate
@@ -221,7 +220,7 @@ void QQuickHandlerPoint::reset(const QVector<QQuickHandlerPoint> &points)
/*!
\readonly
- \qmlproperty QPointF QtQuick::HandlerPoint::sceneGrabPosition
+ \qmlproperty QPointF QtQuick::handlerPoint::sceneGrabPosition
\brief The grabbed position within the scene
If this point has been grabbed by a Pointer Handler or an Item, it means
@@ -232,7 +231,7 @@ void QQuickHandlerPoint::reset(const QVector<QQuickHandlerPoint> &points)
/*!
\readonly
- \qmlproperty enumeration QtQuick::HandlerPoint::pressedButtons
+ \qmlproperty enumeration QtQuick::handlerPoint::pressedButtons
\brief Which mouse or stylus buttons are currently pressed
\sa MouseArea::pressedButtons
@@ -240,7 +239,7 @@ void QQuickHandlerPoint::reset(const QVector<QQuickHandlerPoint> &points)
/*!
\readonly
- \qmlproperty enumeration QtQuick::HandlerPoint::modifiers
+ \qmlproperty enumeration QtQuick::handlerPoint::modifiers
\brief Which modifier keys are currently pressed
This property holds the keyboard modifiers that were pressed at the time
@@ -249,20 +248,20 @@ void QQuickHandlerPoint::reset(const QVector<QQuickHandlerPoint> &points)
/*!
\readonly
- \qmlproperty QVector2D QtQuick::HandlerPoint::velocity
+ \qmlproperty QVector2D QtQuick::handlerPoint::velocity
\brief A vector representing the average speed and direction of movement
This is a velocity vector pointing in the direction of movement, in logical
pixels per second. It has x and y components, at least one of which will be
nonzero when this point is in motion. It holds the average recent velocity:
- how fast and in which direction the event point has been moving recently.
+ how fast and in which direction the \l eventPoint has been moving recently.
\sa QtQuick::TouchPoint::velocity, QEventPoint::velocity
*/
/*!
\readonly
- \qmlproperty qreal QtQuick::HandlerPoint::rotation
+ \qmlproperty qreal QtQuick::handlerPoint::rotation
This property holds the rotation angle of the stylus on a graphics tablet
or the contact patch of a touchpoint on a touchscreen.
@@ -273,7 +272,7 @@ void QQuickHandlerPoint::reset(const QVector<QQuickHandlerPoint> &points)
/*!
\readonly
- \qmlproperty qreal QtQuick::HandlerPoint::pressure
+ \qmlproperty qreal QtQuick::handlerPoint::pressure
This property tells how hard the user is pressing the stylus on a graphics
tablet or the finger against a touchscreen, in the range from \c 0 (no
@@ -286,7 +285,7 @@ void QQuickHandlerPoint::reset(const QVector<QQuickHandlerPoint> &points)
/*!
\readonly
- \qmlproperty size QtQuick::HandlerPoint::ellipseDiameters
+ \qmlproperty size QtQuick::handlerPoint::ellipseDiameters
This property holds the diameters of the contact patch, if the event
comes from a touchpoint and the device provides this information.
@@ -310,6 +309,13 @@ void QQuickHandlerPoint::reset(const QVector<QQuickHandlerPoint> &points)
\sa QtQuick::TouchPoint::ellipseDiameters, QEventPoint::ellipseDiameters
*/
+/*!
+ \readonly
+ \qmlproperty PointerDevice QtQuick::handlerPoint::device
+
+ This property holds the device that the point (and its event) came from.
+*/
+
QT_END_NAMESPACE
#include "moc_qquickhandlerpoint_p.cpp"
diff --git a/src/quick/handlers/qquickhandlerpoint_p.h b/src/quick/handlers/qquickhandlerpoint_p.h
index 9e2b873e70..1654406d86 100644
--- a/src/quick/handlers/qquickhandlerpoint_p.h
+++ b/src/quick/handlers/qquickhandlerpoint_p.h
@@ -22,22 +22,22 @@ QT_BEGIN_NAMESPACE
class QQuickMultiPointHandler;
class QQuickSinglePointHandler;
-class Q_QUICK_PRIVATE_EXPORT QQuickHandlerPoint {
+class Q_QUICK_EXPORT QQuickHandlerPoint {
Q_GADGET
- Q_PROPERTY(int id READ id)
- Q_PROPERTY(QPointingDeviceUniqueId uniqueId READ uniqueId)
- Q_PROPERTY(QPointF position READ position)
- Q_PROPERTY(QPointF scenePosition READ scenePosition)
- Q_PROPERTY(QPointF pressPosition READ pressPosition)
- Q_PROPERTY(QPointF scenePressPosition READ scenePressPosition)
- Q_PROPERTY(QPointF sceneGrabPosition READ sceneGrabPosition)
- Q_PROPERTY(Qt::MouseButtons pressedButtons READ pressedButtons)
- Q_PROPERTY(Qt::KeyboardModifiers modifiers READ modifiers)
- Q_PROPERTY(QVector2D velocity READ velocity)
- Q_PROPERTY(qreal rotation READ rotation)
- Q_PROPERTY(qreal pressure READ pressure)
- Q_PROPERTY(QSizeF ellipseDiameters READ ellipseDiameters)
- Q_PROPERTY(QPointingDevice *device READ device)
+ Q_PROPERTY(int id READ id FINAL)
+ Q_PROPERTY(QPointingDeviceUniqueId uniqueId READ uniqueId FINAL)
+ Q_PROPERTY(QPointF position READ position FINAL)
+ Q_PROPERTY(QPointF scenePosition READ scenePosition FINAL)
+ Q_PROPERTY(QPointF pressPosition READ pressPosition FINAL)
+ Q_PROPERTY(QPointF scenePressPosition READ scenePressPosition FINAL)
+ Q_PROPERTY(QPointF sceneGrabPosition READ sceneGrabPosition FINAL)
+ Q_PROPERTY(Qt::MouseButtons pressedButtons READ pressedButtons FINAL)
+ Q_PROPERTY(Qt::KeyboardModifiers modifiers READ modifiers FINAL)
+ Q_PROPERTY(QVector2D velocity READ velocity FINAL)
+ Q_PROPERTY(qreal rotation READ rotation FINAL)
+ Q_PROPERTY(qreal pressure READ pressure FINAL)
+ Q_PROPERTY(QSizeF ellipseDiameters READ ellipseDiameters FINAL)
+ Q_PROPERTY(QPointingDevice *device READ device FINAL)
QML_ANONYMOUS
public:
diff --git a/src/quick/handlers/qquickhoverhandler.cpp b/src/quick/handlers/qquickhoverhandler.cpp
index 16e5ef5ef3..5bf53235c5 100644
--- a/src/quick/handlers/qquickhoverhandler.cpp
+++ b/src/quick/handlers/qquickhoverhandler.cpp
@@ -33,7 +33,7 @@ Q_LOGGING_CATEGORY(lcHoverHandler, "qt.quick.handler.hover")
The \l cursorShape property allows changing the cursor whenever
\l hovered changes to \c true.
- \sa MouseArea, PointHandler
+ \sa MouseArea, PointHandler, {Qt Quick Examples - Pointer Handlers}
*/
class QQuickHoverHandlerPrivate : public QQuickSinglePointHandlerPrivate
@@ -145,7 +145,8 @@ bool QQuickHoverHandler::wantsPointerEvent(QPointerEvent *event)
if (event->isSinglePointEvent() && static_cast<QSinglePointEvent *>(event)->button())
return false;
auto &point = event->point(0);
- if (QQuickPointerDeviceHandler::wantsPointerEvent(event) && wantsEventPoint(event, point) && parentContains(point)) {
+ const bool inside = parentContains(point);
+ if (QQuickPointerDeviceHandler::wantsPointerEvent(event) && wantsEventPoint(event, point) && inside) {
// assume this is a mouse or tablet event, so there's only one point
setPointId(point.id());
return true;
@@ -162,7 +163,7 @@ bool QQuickHoverHandler::wantsPointerEvent(QPointerEvent *event)
// But after kCursorOverrideTimeout ms, QQuickItemPrivate::effectiveCursorHandler()
// will ignore it, just in case there is no QQuickPointerTabletEvent to unset it.
// For example, a tablet proximity leave event could occur, but we don't deliver it to the window.
- if (!(m_hoveredTablet && QQuickDeliveryAgentPrivate::isMouseEvent(event)))
+ if (!inside || !(m_hoveredTablet && QQuickDeliveryAgentPrivate::isMouseEvent(event)))
setHovered(false);
return false;
@@ -230,6 +231,9 @@ void QQuickHoverHandler::setHovered(bool hovered)
\value PointerDevice.Puck A digitizer with crosshairs, on a graphics tablet.
\value PointerDevice.AllDevices Any type of pointing device.
+ \note Not all platforms are yet able to distinguish mouse and touchpad; and
+ on those that do, you often want to make mouse and touchpad behavior the same.
+
\sa QInputDevice::DeviceType
*/
@@ -338,6 +342,13 @@ void QQuickHoverHandler::setHovered(bool hovered)
\sa Qt::CursorShape, QQuickItem::cursor()
*/
+/*!
+ \internal
+ \qmlproperty flags HoverHandler::dragThreshold
+
+ This property is not used in HoverHandler.
+*/
+
QT_END_NAMESPACE
#include "moc_qquickhoverhandler_p.cpp"
diff --git a/src/quick/handlers/qquickhoverhandler_p.h b/src/quick/handlers/qquickhoverhandler_p.h
index 45caa1189b..a83e1fba86 100644
--- a/src/quick/handlers/qquickhoverhandler_p.h
+++ b/src/quick/handlers/qquickhoverhandler_p.h
@@ -25,7 +25,7 @@ QT_BEGIN_NAMESPACE
class QQuickHoverHandlerPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickHoverHandler : public QQuickSinglePointHandler
+class Q_QUICK_EXPORT QQuickHoverHandler : public QQuickSinglePointHandler
{
Q_OBJECT
Q_PROPERTY(bool hovered READ isHovered NOTIFY hoveredChanged)
diff --git a/src/quick/handlers/qquickmultipointhandler.cpp b/src/quick/handlers/qquickmultipointhandler.cpp
index 1ef76148c5..c50de437cd 100644
--- a/src/quick/handlers/qquickmultipointhandler.cpp
+++ b/src/quick/handlers/qquickmultipointhandler.cpp
@@ -229,7 +229,7 @@ void QQuickMultiPointHandler::setMaximumPointCount(int maximumPointCount)
/*!
\readonly
- \qmlproperty QtQuick::HandlerPoint QtQuick::MultiPointHandler::centroid
+ \qmlproperty QtQuick::handlerPoint QtQuick::MultiPointHandler::centroid
A point exactly in the middle of the currently-pressed touch points.
If only one point is pressed, it's the same as that point.
diff --git a/src/quick/handlers/qquickmultipointhandler_p.h b/src/quick/handlers/qquickmultipointhandler_p.h
index d15fad997d..8d60984d12 100644
--- a/src/quick/handlers/qquickmultipointhandler_p.h
+++ b/src/quick/handlers/qquickmultipointhandler_p.h
@@ -25,7 +25,7 @@ QT_BEGIN_NAMESPACE
class QQuickMultiPointHandlerPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickMultiPointHandler : public QQuickPointerDeviceHandler
+class Q_QUICK_EXPORT QQuickMultiPointHandler : public QQuickPointerDeviceHandler
{
Q_OBJECT
Q_PROPERTY(int minimumPointCount READ minimumPointCount WRITE setMinimumPointCount NOTIFY minimumPointCountChanged)
diff --git a/src/quick/handlers/qquickmultipointhandler_p_p.h b/src/quick/handlers/qquickmultipointhandler_p_p.h
index 077b3dac55..c9c140e387 100644
--- a/src/quick/handlers/qquickmultipointhandler_p_p.h
+++ b/src/quick/handlers/qquickmultipointhandler_p_p.h
@@ -21,7 +21,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QQuickMultiPointHandlerPrivate : public QQuickPointerDeviceHandlerPrivate
+class Q_QUICK_EXPORT QQuickMultiPointHandlerPrivate : public QQuickPointerDeviceHandlerPrivate
{
Q_DECLARE_PUBLIC(QQuickMultiPointHandler)
diff --git a/src/quick/handlers/qquickpinchhandler.cpp b/src/quick/handlers/qquickpinchhandler.cpp
index 94b64c357d..92a1777137 100644
--- a/src/quick/handlers/qquickpinchhandler.cpp
+++ b/src/quick/handlers/qquickpinchhandler.cpp
@@ -17,7 +17,7 @@
QT_BEGIN_NAMESPACE
-Q_LOGGING_CATEGORY(lcPinchHandler, "qt.quick.handler.pinch")
+Q_STATIC_LOGGING_CATEGORY(lcPinchHandler, "qt.quick.handler.pinch")
/*!
\qmltype PinchHandler
@@ -32,7 +32,7 @@ Q_LOGGING_CATEGORY(lcPinchHandler, "qt.quick.handler.pinch")
by default it is fully functional, and manipulates its \l target,
which is the Item within which it is declared.
- \snippet pointerHandlers/pinchHandler.qml 0
+ \snippet pointerHandlers/pinchHandlerSimple.qml 0
It has properties to restrict the range of dragging, rotation, and zoom.
@@ -56,7 +56,7 @@ Q_LOGGING_CATEGORY(lcPinchHandler, "qt.quick.handler.pinch")
but if it's a disallowed number, it does not scale or rotate
its \l target, and the \l active property remains \c false.
- \sa PinchArea, QPointerEvent::pointCount(), QNativeGestureEvent::fingerCount()
+ \sa PinchArea, QPointerEvent::pointCount(), QNativeGestureEvent::fingerCount(), {Qt Quick Examples - Pointer Handlers}
*/
QQuickPinchHandler::QQuickPinchHandler(QQuickItem *parent)
@@ -146,7 +146,6 @@ void QQuickPinchHandler::setActiveScale(qreal scale)
*/
/*!
- \readonly
\qmlproperty real QtQuick::PinchHandler::persistentScale
The scale factor that will automatically be set on the \l target if it is not null.
@@ -161,12 +160,12 @@ void QQuickPinchHandler::setActiveScale(qreal scale)
and \c scaleChanged(1) is emitted.
*/
-void QQuickPinchHandler::setPersistentScale(qreal rot)
+void QQuickPinchHandler::setPersistentScale(qreal scale)
{
- if (rot == persistentScale())
+ if (scale == persistentScale())
return;
- m_scaleAxis.updateValue(m_scaleAxis.activeValue(), rot);
+ m_scaleAxis.updateValue(m_scaleAxis.activeValue(), scale);
emit scaleChanged(1);
}
@@ -248,7 +247,6 @@ void QQuickPinchHandler::setActiveRotation(qreal rot)
}
/*!
- \readonly
\qmlproperty real QtQuick::PinchHandler::persistentRotation
The rotation to be applied to the \l target if it is not null.
@@ -297,7 +295,7 @@ void QQuickPinchHandler::setPersistentRotation(qreal rot)
The translation of the cluster of points while the pinch gesture is being
performed. It is \c {0, 0} when the gesture begins, and increases as the
- event point(s) are dragged downward and to the right. After the gesture
+ \l {eventPoint}{eventPoint(s)} are dragged downward and to the right. After the gesture
ends, it stays the same; and when the next pinch gesture begins, it is
reset to \c {0, 0} again.
@@ -478,12 +476,6 @@ void QQuickPinchHandler::onActiveChanged()
{
QQuickMultiPointHandler::onActiveChanged();
const bool curActive = active();
- if (const QQuickItem *t = target(); curActive && t) {
- m_xAxis.m_accumulatedValue = t->position().x();
- m_yAxis.m_accumulatedValue = t->position().y();
- m_scaleAxis.m_accumulatedValue = t->scale();
- m_rotationAxis.m_accumulatedValue = t->rotation();
- }
m_xAxis.onActiveChanged(curActive, 0);
m_yAxis.onActiveChanged(curActive, 0);
m_scaleAxis.onActiveChanged(curActive, 1);
@@ -492,9 +484,12 @@ void QQuickPinchHandler::onActiveChanged()
if (curActive) {
m_startAngles = angles(centroid().sceneGrabPosition());
m_startDistance = averageTouchPointDistance(centroid().sceneGrabPosition());
+ m_startTargetPos = target() ? target()->position() : QPointF();
qCDebug(lcPinchHandler) << "activated with starting scale" << m_scaleAxis.m_startValue
- << "target scale" << m_scaleAxis.m_startValue << "rotation" << m_rotationAxis.m_startValue;
+ << "rotation" << m_rotationAxis.m_startValue
+ << "target pos" << m_startTargetPos;
} else {
+ m_startTargetPos = QPointF();
qCDebug(lcPinchHandler) << "deactivated with scale" << m_scaleAxis.m_activeValue << "rotation" << m_rotationAxis.m_activeValue;
}
}
@@ -524,7 +519,7 @@ void QQuickPinchHandler::handlePointerEventImpl(QPointerEvent *event)
emit updated();
return;
case Qt::ZoomNativeGesture:
- setActiveScale(1 + gesture->value());
+ setActiveScale(m_scaleAxis.activeValue() * (1 + gesture->value()));
break;
case Qt::RotateNativeGesture:
setActiveRotation(m_rotationAxis.activeValue() + gesture->value());
@@ -677,17 +672,16 @@ void QQuickPinchHandler::handlePointerEventImpl(QPointerEvent *event)
if (target() && target()->parentItem()) {
- const QPointF centroidParentPos = target()->parentItem()->mapFromScene(centroid().scenePosition());
+ auto *t = target();
+ const QPointF centroidParentPos = t->parentItem()->mapFromScene(centroid().scenePosition());
// 3. Drag/translate
- const QPointF centroidStartParentPos = target()->parentItem()->mapFromScene(centroid().sceneGrabPosition());
+ const QPointF centroidStartParentPos = t->parentItem()->mapFromScene(centroid().sceneGrabPosition());
auto activeTranslation = centroidParentPos - centroidStartParentPos;
// apply rotation + scaling around the centroid - then apply translation.
- QPointF pos = QQuickItemPrivate::get(target())->adjustedPosForTransform(centroidParentPos,
- startPos(), QVector2D(activeTranslation),
- m_scaleAxis.m_startValue,
- m_scaleAxis.persistentValue() / m_scaleAxis.m_startValue,
- m_rotationAxis.m_startValue,
- m_rotationAxis.persistentValue() - m_rotationAxis.m_startValue);
+ QPointF pos = QQuickItemPrivate::get(t)->adjustedPosForTransform(centroidParentPos,
+ m_startTargetPos, QVector2D(activeTranslation),
+ t->scale(), m_scaleAxis.persistentValue() / m_scaleAxis.m_startValue,
+ t->rotation(), m_rotationAxis.persistentValue() - m_rotationAxis.m_startValue);
if (xAxis()->enabled())
pos.setX(qBound(xAxis()->minimum(), pos.x(), xAxis()->maximum()));
@@ -700,12 +694,12 @@ void QQuickPinchHandler::handlePointerEventImpl(QPointerEvent *event)
const QVector2D delta(activeTranslation.x() - m_xAxis.activeValue(),
activeTranslation.y() - m_yAxis.activeValue());
- m_xAxis.updateValue(activeTranslation.x(), pos.x(), delta.x());
- m_yAxis.updateValue(activeTranslation.y(), pos.y(), delta.y());
+ m_xAxis.updateValue(activeTranslation.x(), m_xAxis.persistentValue() + delta.x(), delta.x());
+ m_yAxis.updateValue(activeTranslation.y(), m_yAxis.persistentValue() + delta.y(), delta.y());
emit translationChanged(delta);
- target()->setPosition(QPointF(m_xAxis.persistentValue(), m_yAxis.persistentValue()));
- target()->setRotation(m_rotationAxis.persistentValue());
- target()->setScale(m_scaleAxis.persistentValue());
+ t->setPosition(pos);
+ t->setRotation(m_rotationAxis.persistentValue());
+ t->setScale(m_scaleAxis.persistentValue());
} else {
auto activeTranslation = centroid().scenePosition() - centroid().scenePressPosition();
auto accumulated = QPointF(m_xAxis.m_startValue, m_yAxis.m_startValue) + activeTranslation;
@@ -726,14 +720,16 @@ void QQuickPinchHandler::handlePointerEventImpl(QPointerEvent *event)
emit updated();
}
-QPointF QQuickPinchHandler::startPos()
-{
- return {m_xAxis.m_startValue, m_yAxis.m_startValue};
-}
+/*!
+ \internal
+ \qmlproperty flags QtQuick::PinchHandler::acceptedButtons
+
+ This property is not used in PinchHandler.
+*/
/*!
\readonly
- \qmlproperty QtQuick::HandlerPoint QtQuick::PinchHandler::centroid
+ \qmlproperty QtQuick::handlerPoint QtQuick::PinchHandler::centroid
A point exactly in the middle of the currently-pressed touch points.
The \l target will be rotated around this point.
diff --git a/src/quick/handlers/qquickpinchhandler_p.h b/src/quick/handlers/qquickpinchhandler_p.h
index d86b96c932..114ae9642a 100644
--- a/src/quick/handlers/qquickpinchhandler_p.h
+++ b/src/quick/handlers/qquickpinchhandler_p.h
@@ -26,7 +26,7 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-class Q_QUICK_PRIVATE_EXPORT QQuickPinchHandler : public QQuickMultiPointHandler
+class Q_QUICK_EXPORT QQuickPinchHandler : public QQuickMultiPointHandler
{
Q_OBJECT
@@ -103,17 +103,15 @@ Q_SIGNALS:
void minimumRotationChanged();
void maximumRotationChanged();
void updated();
- Q_REVISION(6, 5) void scaleChanged(qreal delta);
- Q_REVISION(6, 5) void rotationChanged(qreal delta);
- Q_REVISION(6, 5) void translationChanged(QVector2D delta);
+ void scaleChanged(qreal delta);
+ void rotationChanged(qreal delta);
+ void translationChanged(QVector2D delta);
protected:
bool wantsPointerEvent(QPointerEvent *event) override;
void onActiveChanged() override;
void handlePointerEventImpl(QPointerEvent *event) override;
- QPointF startPos();
-
private:
QQuickDragAxis m_xAxis = {this, u"x"_s};
QQuickDragAxis m_yAxis = {this, u"y"_s};
@@ -123,6 +121,7 @@ private:
// internal
qreal m_startDistance = 0;
qreal m_accumulatedStartCentroidDistance = 0;
+ QPointF m_startTargetPos;
QVector<PointData> m_startAngles;
QQuickMatrix4x4 m_transform;
};
diff --git a/src/quick/handlers/qquickpointerdevicehandler.cpp b/src/quick/handlers/qquickpointerdevicehandler.cpp
index b610e083cc..a6b98ec2b5 100644
--- a/src/quick/handlers/qquickpointerdevicehandler.cpp
+++ b/src/quick/handlers/qquickpointerdevicehandler.cpp
@@ -110,7 +110,7 @@ Qt::KeyboardModifiers QQuickPointerDeviceHandler::acceptedModifiers() const
\qml
Item {
TapHandler {
- acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus
+ acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad | PointerDevice.Stylus
onTapped: console.log("clicked")
}
TapHandler {
@@ -119,6 +119,9 @@ Qt::KeyboardModifiers QQuickPointerDeviceHandler::acceptedModifiers() const
}
}
\endqml
+
+ \note Not all platforms are yet able to distinguish mouse and touchpad; and
+ on those that do, you often want to make mouse and touchpad behavior the same.
*/
void QQuickPointerDeviceHandler::setAcceptedDevices(QPointingDevice::DeviceTypes acceptedDevices)
{
@@ -139,7 +142,7 @@ void QQuickPointerDeviceHandler::setAcceptedDevices(QPointingDevice::DeviceTypes
By default, this property is set to
\l {QPointingDevice::PointerType} {PointerDevice.AllPointerTypes}.
If you set it to an OR combination of device types, it will ignore events
- from non-matching events.
+ from non-matching \l {PointerDevice}{devices}.
For example, a control could be made to respond to mouse, touch, and stylus clicks
in some way, but delete itself if tapped with an eraser tool on a graphics tablet,
@@ -149,7 +152,7 @@ void QQuickPointerDeviceHandler::setAcceptedDevices(QPointingDevice::DeviceTypes
Rectangle {
id: rect
TapHandler {
- acceptedPointerTypes: PointerDevice.GenericPointer | PointerDevice.Finger | PointerDevice.Pen
+ acceptedPointerTypes: PointerDevice.Generic | PointerDevice.Finger | PointerDevice.Pen
onTapped: console.log("clicked")
}
TapHandler {
@@ -270,7 +273,7 @@ bool QQuickPointerDeviceHandler::wantsPointerEvent(QPointerEvent *event)
if (d->acceptedModifiers != Qt::KeyboardModifierMask && event->modifiers() != d->acceptedModifiers)
return false;
// Some handlers (HoverHandler, PinchHandler) set acceptedButtons to Qt::NoButton to indicate that button state is irrelevant.
- if (event->pointingDevice()->pointerType() != QPointingDevice::PointerType::Finger &&
+ if (event->pointingDevice()->type() != QPointingDevice::DeviceType::TouchScreen &&
acceptedButtons() != Qt::NoButton && event->type() != QEvent::Wheel &&
(static_cast<QSinglePointEvent *>(event)->buttons() & acceptedButtons()) == 0 &&
(static_cast<QSinglePointEvent *>(event)->button() & acceptedButtons()) == 0)
diff --git a/src/quick/handlers/qquickpointerdevicehandler_p.h b/src/quick/handlers/qquickpointerdevicehandler_p.h
index be7bda2c0a..50f6e1d7e0 100644
--- a/src/quick/handlers/qquickpointerdevicehandler_p.h
+++ b/src/quick/handlers/qquickpointerdevicehandler_p.h
@@ -20,7 +20,7 @@ QT_BEGIN_NAMESPACE
class QQuickPointerDeviceHandlerPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickPointerDeviceHandler : public QQuickPointerHandler
+class Q_QUICK_EXPORT QQuickPointerDeviceHandler : public QQuickPointerHandler
{
Q_OBJECT
Q_PROPERTY(QInputDevice::DeviceTypes acceptedDevices READ acceptedDevices WRITE
diff --git a/src/quick/handlers/qquickpointerdevicehandler_p_p.h b/src/quick/handlers/qquickpointerdevicehandler_p_p.h
index 809f51656a..ac90005199 100644
--- a/src/quick/handlers/qquickpointerdevicehandler_p_p.h
+++ b/src/quick/handlers/qquickpointerdevicehandler_p_p.h
@@ -20,7 +20,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QQuickPointerDeviceHandlerPrivate : public QQuickPointerHandlerPrivate
+class Q_QUICK_EXPORT QQuickPointerDeviceHandlerPrivate : public QQuickPointerHandlerPrivate
{
Q_DECLARE_PUBLIC(QQuickPointerDeviceHandler)
diff --git a/src/quick/handlers/qquickpointerhandler.cpp b/src/quick/handlers/qquickpointerhandler.cpp
index 3a15eb4d41..92e726bdcc 100644
--- a/src/quick/handlers/qquickpointerhandler.cpp
+++ b/src/quick/handlers/qquickpointerhandler.cpp
@@ -8,12 +8,13 @@
#include <QtQuick/private/qquickdeliveryagent_p_p.h>
#include <QtGui/private/qinputdevice_p.h>
+#include <QtCore/qpointer.h>
+
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcPointerHandlerDispatch, "qt.quick.handler.dispatch")
-Q_LOGGING_CATEGORY(lcPointerHandlerGrab, "qt.quick.handler.grab")
-Q_LOGGING_CATEGORY(lcPointerHandlerActive, "qt.quick.handler.active")
-Q_DECLARE_LOGGING_CATEGORY(lcHandlerParent)
+Q_STATIC_LOGGING_CATEGORY(lcPointerHandlerGrab, "qt.quick.handler.grab")
+Q_STATIC_LOGGING_CATEGORY(lcPointerHandlerActive, "qt.quick.handler.active")
/*!
\qmltype PointerHandler
@@ -27,6 +28,19 @@ Q_DECLARE_LOGGING_CATEGORY(lcHandlerParent)
events from any kind of pointing device (touch, mouse or graphics tablet).
*/
+/*! \internal
+ So far we only offer public QML API for Pointer Handlers, but we expect
+ in some future version of Qt to have public C++ API as well. This will open
+ up the possibility to instantiate handlers in custom items (which we should
+ begin doing in Qt Quick Controls in the near future), and to subclass to make
+ custom handlers (as TableView is already doing).
+
+ To make a custom Pointer Handler, first try to choose the parent class
+ according to your needs. If the gesture that you want to recognize could
+ involve multiple touchpoints (even if it could start with only one point),
+ subclass QQuickMultiPointHandler. If you are sure that you never want to
+ handle more than one QEventPoint, subclass QQuickSinglePointHandler.
+*/
QQuickPointerHandler::QQuickPointerHandler(QQuickItem *parent)
: QQuickPointerHandler(*(new QQuickPointerHandlerPrivate), parent)
{
@@ -57,7 +71,7 @@ QQuickPointerHandler::~QQuickPointerHandler()
\qmlproperty real PointerHandler::margin
The margin beyond the bounds of the \l {PointerHandler::parent}{parent}
- item within which an event point can activate this handler. For example, on
+ item within which an \l eventPoint can activate this handler. For example, on
a PinchHandler where the \l {PointerHandler::target}{target} is also the
\c parent, it's useful to set this to a distance at least half the width
of a typical user's finger, so that if the \c parent has been scaled down
@@ -90,7 +104,7 @@ void QQuickPointerHandler::setMargin(qreal pointDistanceThreshold)
\qmlproperty int PointerHandler::dragThreshold
\since 5.15
- The distance in pixels that the user must drag an event point in order to
+ The distance in pixels that the user must drag an \l eventPoint in order to
have it treated as a drag gesture.
The default value depends on the platform and screen resolution.
@@ -182,11 +196,13 @@ void QQuickPointerHandler::setCursorShape(Qt::CursorShape shape)
return;
d->cursorShape = shape;
d->cursorSet = true;
+ d->cursorDirty = true;
if (auto *parent = parentItem()) {
QQuickItemPrivate *itemPriv = QQuickItemPrivate::get(parent);
itemPriv->hasCursorHandler = true;
itemPriv->setHasCursorInChild(true);
}
+
emit cursorShapeChanged();
}
@@ -217,8 +233,8 @@ bool QQuickPointerHandler::isCursorShapeExplicitlySet() const
The \a grabber (subject) will be the Input Handler whose state is changing,
or null if the state change regards an Item.
The \a transition (verb) tells what happened.
- The \a point (object) is the point that was grabbed or ungrabbed.
- EventPoint has the sole responsibility to call this function.
+ The \a point (object) is the \l eventPoint that was grabbed or ungrabbed.
+ QQuickDeliveryAgent calls this function.
The Input Handler must react in whatever way is appropriate, and must
emit the relevant signals (for the benefit of QML code).
A subclass is allowed to override this virtual function, but must always
@@ -414,7 +430,7 @@ bool QQuickPointerHandler::approveGrabTransition(QPointerEvent *event, const QEv
\value PointerHandler.ApprovesCancellation
This handler will allow its grab to be set to null.
\value PointerHandler.ApprovesTakeOverByAnything
- This handler gives permission for any any type of Item or Handler to take the grab.
+ This handler gives permission for any type of Item or Handler to take the grab.
The default is
\c {PointerHandler.CanTakeOverFromItems | PointerHandler.CanTakeOverFromHandlersOfDifferentType | PointerHandler.ApprovesTakeOverByAnything}
@@ -437,10 +453,17 @@ void QQuickPointerHandler::setGrabPermissions(GrabPermissions grabPermission)
emit grabPermissionChanged();
}
+/*!
+ Overridden only because QQmlParserStatus requires it.
+*/
void QQuickPointerHandler::classBegin()
{
}
+/*!
+ Overridden from QQmlParserStatus to ensure that parentItem() sets its
+ cursor if this handler's \l cursorShape property has been set.
+*/
void QQuickPointerHandler::componentComplete()
{
Q_D(const QQuickPointerHandler);
@@ -453,6 +476,12 @@ void QQuickPointerHandler::componentComplete()
}
}
+/*! \internal
+ \deprecated You should handle the event during delivery by overriding
+ handlePointerEventImpl() or QQuickSinglePointHandler::handleEventPoint().
+ Therefore currentEvent() should not be needed. It is here only because
+ onActiveChanged() does not take the event as an argument.
+*/
QPointerEvent *QQuickPointerHandler::currentEvent()
{
Q_D(const QQuickPointerHandler);
@@ -507,19 +536,33 @@ QPointF QQuickPointerHandler::eventPos(const QEventPoint &point) const
return (target() ? target()->mapFromScene(point.scenePosition()) : point.scenePosition());
}
+/*!
+ Returns \c true if margin() > 0 and \a point is within the margin beyond
+ QQuickItem::boundingRect(), or else returns QQuickItem::contains()
+ QEventPoint::position() effectively (because parentContains(scenePosition)
+ calls QQuickItem::mapFromScene()).
+*/
bool QQuickPointerHandler::parentContains(const QEventPoint &point) const
{
return parentContains(point.scenePosition());
}
+/*!
+ Returns \c true if \a scenePosition is within the margin() beyond
+ QQuickItem::boundingRect() (if margin > 0), or parentItem() contains
+ \a scenePosition according to QQuickItem::contains(). (So if the \l margin
+ property is set, that overrides the bounds-check, and QQuickItem::contains()
+ is not called.) As a precheck, it's also required that the window contains
+ \a scenePosition mapped to global coordinates, if parentItem() is in a window.
+*/
bool QQuickPointerHandler::parentContains(const QPointF &scenePosition) const
{
if (QQuickItem *par = parentItem()) {
if (par->window()) {
- QRect windowGeometry = par->window()->geometry();
+ QRectF windowGeometry = par->window()->geometry();
if (!par->window()->isTopLevel())
- windowGeometry = QRect(QWindowPrivate::get(par->window())->globalPosition(), par->window()->size());
- QPoint screenPosition = par->window()->mapToGlobal(scenePosition.toPoint());
+ windowGeometry = QRectF(QWindowPrivate::get(par->window())->globalPosition(), par->window()->size());
+ QPointF screenPosition = par->window()->mapToGlobal(scenePosition);
if (!windowGeometry.contains(screenPosition))
return false;
}
@@ -560,12 +603,6 @@ void QQuickPointerHandler::setEnabled(bool enabled)
emit enabledChanged();
}
-bool QQuickPointerHandler::active() const
-{
- Q_D(const QQuickPointerHandler);
- return d->active;
-}
-
/*!
\qmlproperty Item QtQuick::PointerHandler::target
@@ -577,6 +614,14 @@ bool QQuickPointerHandler::active() const
but manipulate another; or to \c null, to disable the default behavior
and do something else instead.
*/
+QQuickItem *QQuickPointerHandler::target() const
+{
+ Q_D(const QQuickPointerHandler);
+ if (!d->targetExplicitlySet)
+ return parentItem();
+ return d->target;
+}
+
void QQuickPointerHandler::setTarget(QQuickItem *target)
{
Q_D(QQuickPointerHandler);
@@ -590,6 +635,27 @@ void QQuickPointerHandler::setTarget(QQuickItem *target)
emit targetChanged();
}
+/*!
+ \qmlproperty Item QtQuick::PointerHandler::parent
+
+ The \l Item which is the scope of the handler; the Item in which it was
+ declared. The handler will handle events on behalf of this Item, which
+ means a pointer event is relevant if at least one of its
+ \l {eventPoint}{eventPoints} occurs within the Item's interior. Initially
+ \l [QML] {target} {target()} is the same, but it can be reassigned.
+
+ \sa {target}, QObject::parent()
+*/
+/*! \internal
+ We still haven't shipped official support for declaring handlers in
+ QtQuick3D.Model objects. Many prerequisites are in place for that, so we
+ should try to keep it working; but there are issues with getting
+ DragHandler to drag its target intuitively in 3D space, for example.
+ TapHandler would work well enough.
+
+ \note When a handler is declared in a \l [QtQuick3D] {Model}{QtQuick3D.Model}
+ object, the parent is not an Item, therefore this property is \c null.
+*/
QQuickItem *QQuickPointerHandler::parentItem() const
{
return qmlobject_cast<QQuickItem *>(QObject::parent());
@@ -612,14 +678,6 @@ void QQuickPointerHandler::setParentItem(QQuickItem *p)
emit parentChanged();
}
-QQuickItem *QQuickPointerHandler::target() const
-{
- Q_D(const QQuickPointerHandler);
- if (!d->targetExplicitlySet)
- return parentItem();
- return d->target;
-}
-
/*! \internal
Pointer Handlers do most of their work in implementations of virtual functions
that are called directly from QQuickItem, not by direct event handling.
@@ -642,6 +700,11 @@ bool QQuickPointerHandler::event(QEvent *e)
}
}
+/*! \internal
+ The entry point to handle the \a event: it's called from
+ QQuickItemPrivate::handlePointerEvent(), begins with wantsPointerEvent(),
+ and calls handlePointerEventImpl() if that returns \c true.
+*/
void QQuickPointerHandler::handlePointerEvent(QPointerEvent *event)
{
Q_D(QQuickPointerHandler);
@@ -668,6 +731,28 @@ void QQuickPointerHandler::handlePointerEvent(QPointerEvent *event)
QQuickPointerHandlerPrivate::deviceDeliveryTargets(event->device()).append(this);
}
+/*!
+ It is the responsibility of this function to decide whether the \a event
+ could be relevant at all to this handler, as a preliminary check.
+
+ Returns \c true if this handler would like handlePointerEventImpl() to be called.
+ If it returns \c false, the handler will be deactivated: \c setActive(false)
+ will be called, and any remaining exclusive grab will be relinquished,
+ as a fail-safe.
+
+ If you override this function, you should call the immediate parent class
+ implementation (and return \c false if it returns \c false); that in turn
+ calls its parent class implementation, and so on.
+ QQuickSinglePointHandler::wantsPointerEvent() and
+ QQuickMultiPointHandler::wantsPointerEvent() call wantsEventPoint(), which
+ is also virtual. You usually can get the behavior you want by subclassing
+ the appropriate handler type, overriding
+ QQuickSinglePointHandler::handleEventPoint() or handlePointerEventImpl(),
+ and perhaps overriding wantsEventPoint() if needed.
+
+ \sa wantsEventPoint(), QQuickPointerDeviceHandler::wantsPointerEvent(),
+ QQuickMultiPointHandler::wantsPointerEvent(), QQuickSinglePointHandler::wantsPointerEvent()
+ */
bool QQuickPointerHandler::wantsPointerEvent(QPointerEvent *event)
{
Q_D(const QQuickPointerHandler);
@@ -675,6 +760,27 @@ bool QQuickPointerHandler::wantsPointerEvent(QPointerEvent *event)
return d->enabled;
}
+/*!
+ Returns \c true if the given \a point (as part of \a event) could be
+ relevant at all to this handler, as a preliminary check.
+
+ If you override this function, you should call the immediate parent class
+ implementation (and return \c false if it returns \c false); that in turn
+ calls its parent class implementation, and so on.
+
+ In particular, the bounds checking is done here: the base class
+ QQuickPointerHandler::wantsEventPoint() calls parentContains(point)
+ (which allows the flexibility promised by margin(), QQuickItem::contains()
+ and QQuickItem::containmentMask()). Pointer Handlers can receive
+ QEventPoints that are outside the parent item's bounds: this allows some
+ flexibility for dealing with multi-point gestures in which one or more
+ fingers have strayed outside the bounds, and yet the gesture is still
+ unambiguously intended for the target() item.
+
+ You should not generally react to the \a event or \a point here, but it's
+ ok to set state to remember what needs to be done in your overridden
+ handlePointerEventImpl() or QQuickSinglePointHandler::handleEventPoint().
+*/
bool QQuickPointerHandler::wantsEventPoint(const QPointerEvent *event, const QEventPoint &point)
{
Q_UNUSED(event);
@@ -689,12 +795,18 @@ bool QQuickPointerHandler::wantsEventPoint(const QPointerEvent *event, const QEv
\readonly
\qmlproperty bool QtQuick::PointerHandler::active
- This holds true whenever this Input Handler has taken sole responsibility
- for handing one or more EventPoints, by successfully taking an exclusive
- grab of those points. This means that it is keeping its properties
- up-to-date according to the movements of those Event Points and actively
+ This holds \c true whenever this Input Handler has taken sole responsibility
+ for handing one or more \l {eventPoint}{eventPoints}, by successfully taking an
+ exclusive grab of those points. This means that it is keeping its properties
+ up-to-date according to the movements of those eventPoints and actively
manipulating its \l target (if any).
*/
+bool QQuickPointerHandler::active() const
+{
+ Q_D(const QQuickPointerHandler);
+ return d->active;
+}
+
void QQuickPointerHandler::setActive(bool active)
{
Q_D(QQuickPointerHandler);
@@ -706,33 +818,46 @@ void QQuickPointerHandler::setActive(bool active)
}
}
-void QQuickPointerHandler::handlePointerEventImpl(QPointerEvent *)
-{
-}
-
/*!
- \qmlproperty Item QtQuick::PointerHandler::parent
-
- The \l Item which is the scope of the handler; the Item in which it was declared.
- The handler will handle events on behalf of this Item, which means a
- pointer event is relevant if at least one of its event points occurs within
- the Item's interior. Initially \l [QML] {target} {target()} is the same, but it
- can be reassigned.
-
- \note When a handler is declared in a \l [QtQuick3D] {Model}{QtQuick3D.Model}
- object, the parent is not an Item, therefore this property is \c null.
-
- \sa {target}, QObject::parent()
+ This function can be overridden to implement whatever behavior a specific
+ subclass is intended to have:
+ \list
+ \li Handle all the event's QPointerEvent::points() for which
+ wantsEventPoint() already returned \c true.
+ \li Call setPassiveGrab() setExclusiveGrab() or cancelAllGrabs() as
+ necessary.
+ \li Call QEvent::accept() to stop propagation, or ignore() to allow it
+ to keep going.
+ \endlist
*/
+void QQuickPointerHandler::handlePointerEventImpl(QPointerEvent *event)
+{
+ Q_UNUSED(event);
+}
/*!
- \qmlsignal QtQuick::PointerHandler::grabChanged(GrabTransition transition, eventPoint point)
+ \qmlsignal QtQuick::PointerHandler::grabChanged(PointerDevice::GrabTransition transition, eventPoint point)
This signal is emitted when the grab has changed in some way which is
relevant to this handler.
The \a transition (verb) tells what happened.
The \a point (object) is the point that was grabbed or ungrabbed.
+
+ Valid values for \a transition are:
+
+ \value PointerDevice.GrabExclusive
+ This handler has taken primary responsibility for handling the \a point.
+ \value PointerDevice.UngrabExclusive
+ This handler has given up its previous exclusive grab.
+ \value PointerDevice.CancelGrabExclusive
+ This handler's exclusive grab has been taken over or cancelled.
+ \value PointerDevice.GrabPassive
+ This handler has acquired a passive grab, to monitor the \a point.
+ \value PointerDevice.UngrabPassive
+ This handler has given up its previous passive grab.
+ \value PointerDevice.CancelGrabPassive
+ This handler's previous passive grab has terminated abnormally.
*/
/*!
@@ -753,9 +878,17 @@ QQuickPointerHandlerPrivate::QQuickPointerHandlerPrivate()
, hadKeepMouseGrab(false)
, hadKeepTouchGrab(false)
, cursorSet(false)
+ , cursorDirty(false)
{
}
+/*! \internal
+ Returns \c true if the movement delta \a d in pixels along the \a axis
+ exceeds QQuickPointerHandler::dragThreshold() \e or QEventPoint::velocity()
+ exceeds QStyleHints::startDragVelocity().
+
+ \sa QQuickDeliveryAgentPrivate::dragOverThreshold()
+*/
template <typename TEventPoint>
bool QQuickPointerHandlerPrivate::dragOverThreshold(qreal d, Qt::Axis axis, const TEventPoint &p) const
{
@@ -770,6 +903,12 @@ bool QQuickPointerHandlerPrivate::dragOverThreshold(qreal d, Qt::Axis axis, cons
return overThreshold;
}
+/*!
+ Returns \c true if the movement \a delta in pixels exceeds
+ QQuickPointerHandler::dragThreshold().
+
+ \sa QQuickDeliveryAgentPrivate::dragOverThreshold()
+*/
bool QQuickPointerHandlerPrivate::dragOverThreshold(QVector2D delta) const
{
Q_Q(const QQuickPointerHandler);
@@ -777,6 +916,13 @@ bool QQuickPointerHandlerPrivate::dragOverThreshold(QVector2D delta) const
return qAbs(delta.x()) > threshold || qAbs(delta.y()) > threshold;
}
+/*!
+ Returns \c true if the movement delta of \a point in pixels
+ (calculated as QEventPoint::scenePosition() - QEventPoint::scenePressPosition())
+ exceeds QQuickPointerHandler::dragThreshold().
+
+ \sa QQuickDeliveryAgentPrivate::dragOverThreshold()
+*/
bool QQuickPointerHandlerPrivate::dragOverThreshold(const QEventPoint &point) const
{
QPointF delta = point.scenePosition() - point.scenePressPosition();
diff --git a/src/quick/handlers/qquickpointerhandler_p.h b/src/quick/handlers/qquickpointerhandler_p.h
index 1001718394..e54338f677 100644
--- a/src/quick/handlers/qquickpointerhandler_p.h
+++ b/src/quick/handlers/qquickpointerhandler_p.h
@@ -23,7 +23,7 @@
#include <QtQml/QQmlParserStatus>
#include <QtQml/qqmlregistration.h>
#include <QtQuick/qtquickglobal.h>
-#include <QtQuick/private/qtquickexports_p.h>
+#include <QtQuick/qtquickexports.h>
QT_BEGIN_NAMESPACE
@@ -33,7 +33,7 @@ class QQuickItem;
class QQuickPointerHandlerPrivate;
class QPointerEvent;
-class Q_QUICK_PRIVATE_EXPORT QQuickPointerHandler : public QObject, public QQmlParserStatus
+class Q_QUICK_EXPORT QQuickPointerHandler : public QObject, public QQmlParserStatus
{
Q_OBJECT
Q_INTERFACES(QQmlParserStatus)
diff --git a/src/quick/handlers/qquickpointerhandler_p_p.h b/src/quick/handlers/qquickpointerhandler_p_p.h
index 36797ef8b3..7072222773 100644
--- a/src/quick/handlers/qquickpointerhandler_p_p.h
+++ b/src/quick/handlers/qquickpointerhandler_p_p.h
@@ -23,7 +23,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QQuickPointerHandlerPrivate : public QObjectPrivate
+class Q_QUICK_EXPORT QQuickPointerHandlerPrivate : public QObjectPrivate
{
Q_DECLARE_PUBLIC(QQuickPointerHandler)
@@ -57,6 +57,7 @@ public:
bool hadKeepMouseGrab : 1; // some handlers override target()->setKeepMouseGrab(); this remembers previous state
bool hadKeepTouchGrab : 1; // some handlers override target()->setKeepTouchGrab(); this remembers previous state
bool cursorSet : 1;
+ bool cursorDirty : 1;
};
QT_END_NAMESPACE
diff --git a/src/quick/handlers/qquickpointhandler.cpp b/src/quick/handlers/qquickpointhandler.cpp
index 1c5f36b6dd..ab1f274aa1 100644
--- a/src/quick/handlers/qquickpointhandler.cpp
+++ b/src/quick/handlers/qquickpointhandler.cpp
@@ -3,7 +3,9 @@
#include "qquickpointhandler_p.h"
#include <private/qquickwindow_p.h>
+
#include <QDebug>
+#include <QtCore/qpointer.h>
QT_BEGIN_NAMESPACE
@@ -66,7 +68,8 @@ QT_BEGIN_NAMESPACE
PointHandler will not automatically manipulate the \c target item in any way.
You need to use bindings to make it react to the \l point.
- \note On macOS, PointHandler does not react to the trackpad by default.
+ \note On macOS, PointHandler does not react to multiple fingers on the
+ trackpad by default, although it does react to a pressed point (mouse position).
That is because macOS can provide either native gesture recognition, or raw
touchpoints, but not both. We prefer to use the native gesture event in
PinchHandler, so we do not want to disable it by enabling touch. However
@@ -75,7 +78,7 @@ QT_BEGIN_NAMESPACE
want to react to all the touchpoints but do not require the smooth
native-gesture experience.
- \sa MultiPointTouchArea
+ \sa MultiPointTouchArea, HoverHandler, {Qt Quick Examples - Pointer Handlers}
*/
QQuickPointHandler::QQuickPointHandler(QQuickItem *parent)
@@ -128,6 +131,127 @@ QVector2D QQuickPointHandler::translation() const
return QVector2D(point().position() - point().pressPosition());
}
+/*!
+ \qmlproperty flags PointHandler::acceptedButtons
+
+ The mouse buttons that can activate this PointHandler.
+
+ By default, this property is set to \l {QtQuick::MouseEvent::button} {Qt.LeftButton}.
+ It can be set to an OR combination of mouse buttons, and will ignore events
+ in which other buttons are pressed or held.
+
+ \snippet pointerHandlers/pointHandlerAcceptedButtons.qml 0
+
+ \note On a touchscreen, there are no buttons, so this property does not
+ prevent PointHandler from reacting to touchpoints.
+*/
+
+/*!
+ \qmlproperty flags PointHandler::acceptedDevices
+
+ The types of pointing devices that can activate this PointHandler.
+
+ By default, this property is set to
+ \l{QInputDevice::DeviceType}{PointerDevice.AllDevices}.
+ If you set it to an OR combination of device types, it will ignore events
+ from non-matching \l {PointerDevice}{devices}:
+
+ \snippet pointerHandlers/pointHandler.qml 1
+*/
+
+/*!
+ \qmlproperty flags PointHandler::acceptedPointerTypes
+
+ The types of pointing instruments (finger, stylus, eraser, etc.)
+ that can activate this PointHandler.
+
+ By default, this property is set to
+ \l {QPointingDevice::PointerType} {PointerDevice.AllPointerTypes}.
+ If you set it to an OR combination of device types, it will ignore events
+ from non-matching \l {PointerDevice}{devices}:
+
+ \snippet pointerHandlers/pointHandlerCanvasDrawing.qml 0
+
+ The \l {Qt Quick Examples - Pointer Handlers} includes a more complex example for
+ drawing on a Canvas with a graphics tablet.
+*/
+
+/*!
+ \qmlproperty flags PointHandler::acceptedModifiers
+
+ If this property is set, PointHandler requires the given keyboard modifiers
+ to be pressed in order to react to \l {PointerEvent}{PointerEvents}, and
+ otherwise ignores them.
+
+ If this property is set to \c Qt.KeyboardModifierMask (the default value),
+ then PointHandler ignores the modifier keys.
+
+ For example, an \l [QML] Item could have two handlers, one of which is
+ enabled only if the required keyboard modifier is pressed:
+
+ \snippet pointerHandlers/pointHandlerAcceptedModifiers.qml 0
+
+ If you set \c acceptedModifiers to an OR combination of modifier keys,
+ it means \e all of those modifiers must be pressed to activate the handler.
+
+ The available modifiers are as follows:
+
+ \value NoModifier No modifier key is allowed.
+ \value ShiftModifier A Shift key on the keyboard must be pressed.
+ \value ControlModifier A Ctrl key on the keyboard must be pressed.
+ \value AltModifier An Alt key on the keyboard must be pressed.
+ \value MetaModifier A Meta key on the keyboard must be pressed.
+ \value KeypadModifier A keypad button must be pressed.
+ \value GroupSwitchModifier X11 only (unless activated on Windows by a command line argument).
+ A Mode_switch key on the keyboard must be pressed.
+ \value KeyboardModifierMask The handler does not care which modifiers are pressed.
+
+ \sa Qt::KeyboardModifier
+*/
+
+/*!
+ \readonly
+ \qmlproperty bool PointHandler::active
+
+ This holds \c true whenever the constraints are satisfied and this
+ PointHandler is reacting. This means that it is keeping its properties
+ up-to-date according to the movements of the \l {eventPoint}{eventPoints}
+ that satisfy the constraints.
+*/
+
+/*!
+ \internal
+ \qmlproperty flags PointHandler::dragThreshold
+
+ This property is not used in PointHandler.
+*/
+
+/*!
+ \qmlproperty real PointHandler::margin
+
+ The margin beyond the bounds of the \l {PointerHandler::parent}{parent}
+ item within which an \l eventPoint can activate this handler.
+
+ The default value is \c 0.
+
+ \snippet pointerHandlers/pointHandlerMargin.qml 0
+*/
+
+/*!
+ \qmlproperty real PointHandler::target
+
+ A property that can conveniently hold an Item to be manipulated or to show
+ feedback. Unlike other \l {Qt Quick Input Handlers}{Pointer Handlers},
+ PointHandler does not do anything with the \c target on its own: you
+ usually need to create reactive bindings to properties such as
+ \l SinglePointHandler::point and \l PointHandler::active. If you declare
+ an Item instance here, you need to explicitly set its \l {Item::}{parent},
+ because PointHandler is not an Item.
+
+ By default, it is the same as the \l {PointerHandler::}{parent}, the Item
+ within which the handler is declared.
+*/
+
QT_END_NAMESPACE
#include "moc_qquickpointhandler_p.cpp"
diff --git a/src/quick/handlers/qquickpointhandler_p.h b/src/quick/handlers/qquickpointhandler_p.h
index b8a5a049f5..46ee9bcbc7 100644
--- a/src/quick/handlers/qquickpointhandler_p.h
+++ b/src/quick/handlers/qquickpointhandler_p.h
@@ -19,7 +19,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QQuickPointHandler : public QQuickSinglePointHandler
+class Q_QUICK_EXPORT QQuickPointHandler : public QQuickSinglePointHandler
{
Q_OBJECT
Q_PROPERTY(QVector2D translation READ translation NOTIFY translationChanged)
diff --git a/src/quick/handlers/qquicksinglepointhandler.cpp b/src/quick/handlers/qquicksinglepointhandler.cpp
index c732a2562e..52e3f8ce5f 100644
--- a/src/quick/handlers/qquicksinglepointhandler.cpp
+++ b/src/quick/handlers/qquicksinglepointhandler.cpp
@@ -4,8 +4,9 @@
#include "qquicksinglepointhandler_p.h"
#include "qquicksinglepointhandler_p_p.h"
+#include <private/qquickdeliveryagent_p.h>
+
QT_BEGIN_NAMESPACE
-Q_DECLARE_LOGGING_CATEGORY(lcTouchTarget)
/*!
\qmltype SinglePointHandler
@@ -57,9 +58,21 @@ bool QQuickSinglePointHandler::wantsPointerEvent(QPointerEvent *event)
point = &p;
}
}
- if (missing)
- qCWarning(lcTouchTarget) << this << "pointId" << Qt::hex << d->pointInfo.id()
- << "is missing from current event, but was neither canceled nor released";
+ if (missing) {
+ // Received a stray touch begin event => reset and start over.
+ if (event->type() == QEvent::TouchBegin && event->points().count() == 1) {
+ const QEventPoint &point = event->point(0);
+ qCDebug(lcTouchTarget) << this << "pointId" << Qt::hex << point.id()
+ << "was received as a stray TouchBegin event. Canceling existing gesture"
+ " and starting over.";
+ d->pointInfo.reset(event, point);
+ return true;
+ } else {
+ qCWarning(lcTouchTarget) << this << "pointId" << Qt::hex << d->pointInfo.id()
+ << "is missing from current event, but was neither canceled nor released."
+ " Ignoring:" << event->type();
+ }
+ }
if (point) {
if (candidatePointCount == 1 || (candidatePointCount > 1 && d->ignoreAdditionalPoints)) {
point->setAccepted();
@@ -105,11 +118,16 @@ void QQuickSinglePointHandler::handlePointerEventImpl(QPointerEvent *event)
void QQuickSinglePointHandler::handleEventPoint(QPointerEvent *event, QEventPoint &point)
{
- if (point.state() != QEventPoint::Released)
- return;
+ if (point.state() == QEventPoint::Released) {
+ // If it's a mouse or tablet event, with buttons,
+ // do not deactivate unless all acceptable buttons are released.
+ if (event->isSinglePointEvent()) {
+ const Qt::MouseButtons releasedButtons = static_cast<QSinglePointEvent *>(event)->buttons();
+ if ((releasedButtons & acceptedButtons()) != Qt::NoButton)
+ return;
+ }
- const Qt::MouseButtons releasedButtons = static_cast<QSinglePointEvent *>(event)->buttons();
- if ((releasedButtons & acceptedButtons()) == Qt::NoButton) {
+ // Deactivate this handler on release
setExclusiveGrab(event, point, false);
d_func()->reset();
}
@@ -171,9 +189,9 @@ QQuickHandlerPoint QQuickSinglePointHandler::point() const
/*!
\readonly
- \qmlproperty HandlerPoint QtQuick::SinglePointHandler::point
+ \qmlproperty handlerPoint QtQuick::SinglePointHandler::point
- The event point currently being handled. When no point is currently being
+ The \l eventPoint currently being handled. When no point is currently being
handled, this object is reset to default values (all coordinates are 0).
*/
diff --git a/src/quick/handlers/qquicksinglepointhandler_p.h b/src/quick/handlers/qquicksinglepointhandler_p.h
index 038265c114..b35c5805e1 100644
--- a/src/quick/handlers/qquicksinglepointhandler_p.h
+++ b/src/quick/handlers/qquicksinglepointhandler_p.h
@@ -22,7 +22,7 @@ QT_BEGIN_NAMESPACE
class QQuickSinglePointHandlerPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickSinglePointHandler : public QQuickPointerDeviceHandler
+class Q_QUICK_EXPORT QQuickSinglePointHandler : public QQuickPointerDeviceHandler
{
Q_OBJECT
Q_PROPERTY(QQuickHandlerPoint point READ point NOTIFY pointChanged)
diff --git a/src/quick/handlers/qquicksinglepointhandler_p_p.h b/src/quick/handlers/qquicksinglepointhandler_p_p.h
index 7b319a3d48..d8eeac32e7 100644
--- a/src/quick/handlers/qquicksinglepointhandler_p_p.h
+++ b/src/quick/handlers/qquicksinglepointhandler_p_p.h
@@ -21,7 +21,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QQuickSinglePointHandlerPrivate : public QQuickPointerDeviceHandlerPrivate
+class Q_QUICK_EXPORT QQuickSinglePointHandlerPrivate : public QQuickPointerDeviceHandlerPrivate
{
Q_DECLARE_PUBLIC(QQuickSinglePointHandler)
diff --git a/src/quick/handlers/qquicktaphandler.cpp b/src/quick/handlers/qquicktaphandler.cpp
index 306eec65b8..accf307382 100644
--- a/src/quick/handlers/qquicktaphandler.cpp
+++ b/src/quick/handlers/qquicktaphandler.cpp
@@ -13,7 +13,7 @@ QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcTapHandler, "qt.quick.handler.tap")
-qreal QQuickTapHandler::m_multiTapInterval(0.0);
+quint64 QQuickTapHandler::m_multiTapInterval(0);
// single tap distance is the same as the drag threshold
int QQuickTapHandler::m_mouseMultiClickDistanceSquared(-1);
int QQuickTapHandler::m_touchMultiTapDistanceSquared(-1);
@@ -50,14 +50,15 @@ int QQuickTapHandler::m_touchMultiTapDistanceSquared(-1);
QStyleHints::touchDoubleTapDistance() with touch, and the time between
taps must not exceed QStyleHints::mouseDoubleClickInterval().
- \sa MouseArea
+ \sa MouseArea, {Qt Quick Examples - Pointer Handlers}
*/
QQuickTapHandler::QQuickTapHandler(QQuickItem *parent)
: QQuickSinglePointHandler(parent)
+ , m_longPressThreshold(QGuiApplication::styleHints()->mousePressAndHoldInterval())
{
if (m_mouseMultiClickDistanceSquared < 0) {
- m_multiTapInterval = qApp->styleHints()->mouseDoubleClickInterval() / 1000.0;
+ m_multiTapInterval = qApp->styleHints()->mouseDoubleClickInterval();
m_mouseMultiClickDistanceSquared = qApp->styleHints()->mouseDoubleClickDistance();
m_mouseMultiClickDistanceSquared *= m_mouseMultiClickDistanceSquared;
m_touchMultiTapDistanceSquared = qApp->styleHints()->touchDoubleTapDistance();
@@ -78,6 +79,8 @@ bool QQuickTapHandler::wantsEventPoint(const QPointerEvent *event, const QEventP
bool ret = false;
bool overThreshold = d_func()->dragOverThreshold(point);
if (overThreshold && m_gesturePolicy != DragWithinBounds) {
+ if (m_longPressTimer.isActive())
+ qCDebug(lcTapHandler) << objectName() << "drag threshold exceeded";
m_longPressTimer.stop();
m_holdTimer.invalidate();
}
@@ -87,16 +90,17 @@ bool QQuickTapHandler::wantsEventPoint(const QPointerEvent *event, const QEventP
ret = parentContains(point);
break;
case QEventPoint::Updated:
+ ret = point.id() == this->point().id();
switch (m_gesturePolicy) {
case DragThreshold:
- ret = !overThreshold && parentContains(point);
+ ret = ret && !overThreshold && parentContains(point);
break;
case WithinBounds:
case DragWithinBounds:
- ret = parentContains(point);
+ ret = ret && parentContains(point);
break;
case ReleaseWithinBounds:
- ret = point.id() == this->point().id();
+ // no change to ret: depends only whether it's the already-tracking point ID
break;
}
break;
@@ -144,19 +148,28 @@ void QQuickTapHandler::handleEventPoint(QPointerEvent *event, QEventPoint &point
/*!
\qmlproperty real QtQuick::TapHandler::longPressThreshold
- The time in seconds that an event point must be pressed in order to
- trigger a long press gesture and emit the \l longPressed() signal.
- If the point is released before this time limit, a tap can be detected
- if the \l gesturePolicy constraint is satisfied. The default value is
- QStyleHints::mousePressAndHoldInterval() converted to seconds.
+ The time in seconds that an \l eventPoint must be pressed in order to
+ trigger a long press gesture and emit the \l longPressed() signal, if the
+ value is greater than \c 0. If the point is released before this time
+ limit, a tap can be detected if the \l gesturePolicy constraint is
+ satisfied. If \c longPressThreshold is \c 0, the timer is disabled and the
+ signal will not be emitted. If \c longPressThreshold is set to \c undefined,
+ the default value is used instead, and can be read back from this property.
+
+ The default value is QStyleHints::mousePressAndHoldInterval() converted to
+ seconds.
*/
qreal QQuickTapHandler::longPressThreshold() const
{
- return longPressThresholdMilliseconds() / 1000.0;
+ return m_longPressThreshold / qreal(1000);
}
void QQuickTapHandler::setLongPressThreshold(qreal longPressThreshold)
{
+ if (longPressThreshold < 0) {
+ resetLongPressThreshold();
+ return;
+ }
int ms = qRound(longPressThreshold * 1000);
if (m_longPressThreshold == ms)
return;
@@ -165,9 +178,14 @@ void QQuickTapHandler::setLongPressThreshold(qreal longPressThreshold)
emit longPressThresholdChanged();
}
-int QQuickTapHandler::longPressThresholdMilliseconds() const
+void QQuickTapHandler::resetLongPressThreshold()
{
- return (m_longPressThreshold < 0 ? QGuiApplication::styleHints()->mousePressAndHoldInterval() : m_longPressThreshold);
+ int ms = QGuiApplication::styleHints()->mousePressAndHoldInterval();
+ if (m_longPressThreshold == ms)
+ return;
+
+ m_longPressThreshold = ms;
+ emit longPressThresholdChanged();
}
void QQuickTapHandler::timerEvent(QTimerEvent *event)
@@ -175,6 +193,7 @@ void QQuickTapHandler::timerEvent(QTimerEvent *event)
if (event->timerId() == m_longPressTimer.timerId()) {
m_longPressTimer.stop();
qCDebug(lcTapHandler) << objectName() << "longPressed";
+ m_longPressed = true;
emit longPressed();
} else if (event->timerId() == m_doubleTapTimer.timerId()) {
m_doubleTapTimer.stop();
@@ -199,49 +218,96 @@ void QQuickTapHandler::timerEvent(QTimerEvent *event)
The \c gesturePolicy also affects grab behavior as described below.
- \value TapHandler.DragThreshold
- (the default value) The event point must not move significantly.
- If the mouse, finger or stylus moves past the system-wide drag
- threshold (QStyleHints::startDragDistance), the tap gesture is
- canceled, even if the button or finger is still pressed. This policy
- can be useful whenever TapHandler needs to cooperate with other
- input handlers (for example \l DragHandler) or event-handling Items
- (for example QtQuick Controls), because in this case TapHandler
- will not take the exclusive grab, but merely a
- \l {QPointerEvent::addPassiveGrabber()}{passive grab}.
-
- \value TapHandler.WithinBounds
- If the event point leaves the bounds of the \c parent Item, the tap
- gesture is canceled. The TapHandler will take the
- \l {QPointerEvent::setExclusiveGrabber}{exclusive grab} on
- press, but will release the grab as soon as the boundary constraint
- is no longer satisfied.
-
- \value TapHandler.ReleaseWithinBounds
- At the time of release (the mouse button is released or the finger
- is lifted), if the event point is outside the bounds of the
- \c parent Item, a tap gesture is not recognized. This corresponds to
- typical behavior for button widgets: you can cancel a click by
- dragging outside the button, and you can also change your mind by
- dragging back inside the button before release. Note that it's
- necessary for TapHandler to take the
- \l {QPointerEvent::setExclusiveGrabber}{exclusive grab} on press
- and retain it until release in order to detect this gesture.
-
- \value TapHandler.DragWithinBounds
- On press, TapHandler takes the
- \l {QPointerEvent::setExclusiveGrabber}{exclusive grab}; after that,
- the event point can be dragged within the bounds of the \c parent
- item, while the \l timeHeld property keeps counting, and the
- \l longPressed() signal will be emitted regardless of drag distance.
- However, like \c WithinBounds, if the point leaves the bounds,
- the tap gesture is \l {PointerHandler::}{canceled()}, \l active()
- becomes \c false, and \l timeHeld stops counting. This is suitable
- for implementing press-drag-release components, such as menus, in
- which a single TapHandler detects press, \c timeHeld drives an
- "opening" animation, and then the user can drag to a menu item and
- release, while never leaving the bounds of the parent scene containing
- the menu. This value was added in Qt 6.3.
+ \table
+ \header
+ \li Constant
+ \li Description
+ \row
+ \li \c TapHandler.DragThreshold
+ \image pointerHandlers/tapHandlerOverlappingButtons.webp
+ Grab on press: \e passive
+ \li (the default value) The \l eventPoint must not move significantly.
+ If the mouse, finger or stylus moves past the system-wide drag
+ threshold (QStyleHints::startDragDistance), the tap gesture is
+ canceled, even if the device or finger is still pressed. This policy
+ can be useful whenever TapHandler needs to cooperate with other
+ input handlers (for example \l DragHandler) or event-handling Items
+ (for example \l {Qt Quick Controls}), because in this case TapHandler
+ will not take the exclusive grab, but merely a
+ \l {QPointerEvent::addPassiveGrabber()}{passive grab}.
+ That is, \c DragThreshold is especially useful to \e augment
+ existing behavior: it reacts to tap/click/long-press even when
+ another item or handler is already reacting, perhaps even in a
+ different layer of the UI. The following snippet shows one
+ TapHandler as used in one component; but if we stack up two
+ instances of the component, you will see the handlers in both of them
+ react simultaneously when a press occurs over both of them, because
+ the passive grab does not stop event propagation:
+ \quotefromfile pointerHandlers/tapHandlerOverlappingButtons.qml
+ \skipto Item
+ \printuntil component Button
+ \skipto TapHandler
+ \printuntil }
+ \skipuntil Text {
+ \skipuntil }
+ \printuntil Button
+ \printuntil Button
+ \printuntil }
+
+ \row
+ \li \c TapHandler.WithinBounds
+ \image pointerHandlers/tapHandlerButtonWithinBounds.webp
+ Grab on press: \e exclusive
+ \li If the \l eventPoint leaves the bounds of the \c parent Item, the tap
+ gesture is canceled. The TapHandler will take the
+ \l {QPointerEvent::setExclusiveGrabber}{exclusive grab} on
+ press, but will release the grab as soon as the boundary constraint
+ is no longer satisfied.
+ \snippet pointerHandlers/tapHandlerButtonWithinBounds.qml 1
+
+ \row
+ \li \c TapHandler.ReleaseWithinBounds
+ \image pointerHandlers/tapHandlerButtonReleaseWithinBounds.webp
+ Grab on press: \e exclusive
+ \li At the time of release (the mouse button is released or the finger
+ is lifted), if the \l eventPoint is outside the bounds of the
+ \c parent Item, a tap gesture is not recognized. This corresponds to
+ typical behavior for button widgets: you can cancel a click by
+ dragging outside the button, and you can also change your mind by
+ dragging back inside the button before release. Note that it's
+ necessary for TapHandler to take the
+ \l {QPointerEvent::setExclusiveGrabber}{exclusive grab} on press
+ and retain it until release in order to detect this gesture.
+ \snippet pointerHandlers/tapHandlerButtonReleaseWithinBounds.qml 1
+
+ \row
+ \li \c TapHandler.DragWithinBounds
+ \image pointerHandlers/dragReleaseMenu.webp
+ Grab on press: \e exclusive
+ \li On press, TapHandler takes the
+ \l {QPointerEvent::setExclusiveGrabber}{exclusive grab}; after that,
+ the \l eventPoint can be dragged within the bounds of the \c parent
+ item, while the \l timeHeld property keeps counting, and the
+ \l longPressed() signal will be emitted regardless of drag distance.
+ However, like \c WithinBounds, if the point leaves the bounds,
+ the tap gesture is \l {PointerHandler::}{canceled()}, \l active()
+ becomes \c false, and \l timeHeld stops counting. This is suitable
+ for implementing press-drag-release components, such as menus, in
+ which a single TapHandler detects press, \c timeHeld drives an
+ "opening" animation, and then the user can drag to a menu item and
+ release, while never leaving the bounds of the parent scene containing
+ the menu. This value was added in Qt 6.3.
+ \snippet pointerHandlers/dragReleaseMenu.qml 1
+ \endtable
+
+ The \l {Qt Quick Examples - Pointer Handlers} demonstrates some use cases for these.
+
+ \note If you find that TapHandler is reacting in cases that conflict with
+ some other behavior, the first thing you should try is to think about which
+ \c gesturePolicy is appropriate. If you cannot fix it by changing \c gesturePolicy,
+ some cases are better served by adjusting \l {PointerHandler::}{grabPermissions},
+ either in this handler, or in another handler that should \e prevent TapHandler
+ from reacting.
*/
void QQuickTapHandler::setGesturePolicy(QQuickTapHandler::GesturePolicy gesturePolicy)
{
@@ -290,7 +356,7 @@ void QQuickTapHandler::setExclusiveSignals(QQuickTapHandler::ExclusiveSignals ex
Holds true whenever the mouse or touch point is pressed,
and any movement since the press is compliant with the current
- \l gesturePolicy. When the event point is released or the policy is
+ \l gesturePolicy. When the \l eventPoint is released or the policy is
violated, \e pressed will change to false.
*/
void QQuickTapHandler::setPressed(bool press, bool cancel, QPointerEvent *event, QEventPoint &point)
@@ -302,21 +368,12 @@ void QQuickTapHandler::setPressed(bool press, bool cancel, QPointerEvent *event,
connectPreRenderSignal(press);
updateTimeHeld();
if (press) {
- m_longPressTimer.start(longPressThresholdMilliseconds(), this);
+ if (m_longPressThreshold > 0)
+ m_longPressTimer.start(m_longPressThreshold, this);
m_holdTimer.start();
} else {
m_longPressTimer.stop();
m_holdTimer.invalidate();
- if (m_exclusiveSignals == (SingleTap | DoubleTap)) {
- if (m_tapCount == 0) {
- m_singleTapReleasedPoint = point;
- m_singleTapReleasedButton = event->isSinglePointEvent() ? static_cast<QSinglePointEvent *>(event)->button() : Qt::NoButton;
- qCDebug(lcTapHandler) << objectName() << "waiting to emit singleTapped:" << qApp->styleHints()->mouseDoubleClickInterval() << "ms";
- m_doubleTapTimer.start(qApp->styleHints()->mouseDoubleClickInterval(), this);
- } else if (m_doubleTapTimer.isActive()) {
- qCDebug(lcTapHandler) << objectName() << "tap" << (m_tapCount + 1) << "after" << event->timestamp() / 1000.0 - m_lastTapTimestamp << "sec";
- }
- }
}
if (press) {
// on press, grab before emitting changed signals
@@ -326,33 +383,57 @@ void QQuickTapHandler::setPressed(bool press, bool cancel, QPointerEvent *event,
setExclusiveGrab(event, point, press);
}
if (!cancel && !press && parentContains(point)) {
- if (point.timeHeld() < longPressThreshold()) {
+ if (m_longPressed) {
+ qCDebug(lcTapHandler) << objectName() << "long press threshold" << longPressThreshold() << "exceeded:" << point.timeHeld();
+ } else {
// Assuming here that pointerEvent()->timestamp() is in ms.
- const qreal ts = event->timestamp() / 1000.0;
- const qreal interval = ts - m_lastTapTimestamp;
+ const quint64 ts = event->timestamp();
+ const quint64 interval = ts - m_lastTapTimestamp;
const auto distanceSquared = QVector2D(point.scenePosition() - m_lastTapPos).lengthSquared();
- if (interval < m_multiTapInterval && distanceSquared <
+ const auto singleTapReleasedButton = event->isSinglePointEvent() ? static_cast<QSinglePointEvent *>(event)->button() : Qt::NoButton;
+ if ((interval < m_multiTapInterval && distanceSquared <
(event->device()->type() == QInputDevice::DeviceType::Mouse ?
m_mouseMultiClickDistanceSquared : m_touchMultiTapDistanceSquared))
+ && m_singleTapReleasedButton == singleTapReleasedButton) {
++m_tapCount;
- else
+ } else {
+ m_singleTapReleasedButton = singleTapReleasedButton;
+ m_singleTapReleasedPoint = point;
m_tapCount = 1;
+ }
qCDebug(lcTapHandler) << objectName() << "tapped" << m_tapCount << "times; interval since last:" << interval
<< "sec; distance since last:" << qSqrt(distanceSquared);
auto button = event->isSinglePointEvent() ? static_cast<QSinglePointEvent *>(event)->button() : Qt::NoButton;
emit tapped(point, button);
emit tapCountChanged();
- if (m_tapCount == 1 && !m_exclusiveSignals.testFlag(DoubleTap))
- emit singleTapped(point, button);
- else if (m_tapCount == 2 && !m_exclusiveSignals.testFlag(SingleTap)) {
- emit doubleTapped(point, button);
+ switch (m_exclusiveSignals) {
+ case NotExclusive:
+ if (m_tapCount == 1)
+ emit singleTapped(point, button);
+ else if (m_tapCount == 2)
+ emit doubleTapped(point, button);
+ break;
+ case SingleTap:
+ if (m_tapCount == 1)
+ emit singleTapped(point, button);
+ break;
+ case DoubleTap:
+ if (m_tapCount == 2)
+ emit doubleTapped(point, button);
+ break;
+ case (SingleTap | DoubleTap):
+ if (m_tapCount == 1) {
+ qCDebug(lcTapHandler) << objectName() << "waiting to emit singleTapped:" << m_multiTapInterval << "ms";
+ m_doubleTapTimer.start(m_multiTapInterval, this);
+ }
}
+ qCDebug(lcTapHandler) << objectName() << "tap" << m_tapCount << "after" << event->timestamp() - m_lastTapTimestamp << "ms";
+
m_lastTapTimestamp = ts;
m_lastTapPos = point.scenePosition();
- } else {
- qCDebug(lcTapHandler) << objectName() << "tap threshold" << longPressThreshold() << "exceeded:" << point.timeHeld();
}
}
+ m_longPressed = false;
emit pressedChanged();
if (!press && m_gesturePolicy != DragThreshold) {
// on release, ungrab after emitting changed signals
@@ -380,13 +461,25 @@ void QQuickTapHandler::onGrabChanged(QQuickPointerHandler *grabber, QPointingDev
void QQuickTapHandler::connectPreRenderSignal(bool conn)
{
+ // disconnect pre-existing connection, if any
+ disconnect(m_preRenderSignalConnection);
+
auto par = parentItem();
- if (!par)
+ if (!par || !par->window())
return;
- if (conn)
- connect(par->window(), &QQuickWindow::beforeSynchronizing, this, &QQuickTapHandler::updateTimeHeld);
- else
- disconnect(par->window(), &QQuickWindow::beforeSynchronizing, this, &QQuickTapHandler::updateTimeHeld);
+
+ /*
+ Note: beforeSynchronizing is emitted from the SG thread, and the
+ timeHeldChanged signal can be used to do arbitrary things in user QML.
+
+ But the docs say the GUI thread is blockd, and "Therefore, it is safe
+ to access GUI thread thread data in a slot or lambda that is connected
+ with Qt::DirectConnection." We use the default AutoConnection just in case.
+ */
+ if (conn) {
+ m_preRenderSignalConnection = connect(par->window(), &QQuickWindow::beforeSynchronizing,
+ this, &QQuickTapHandler::updateTimeHeld);
+ }
}
void QQuickTapHandler::updateTimeHeld()
@@ -399,8 +492,8 @@ void QQuickTapHandler::updateTimeHeld()
\readonly
The number of taps which have occurred within the time and space
- constraints to be considered a single gesture. For example, to detect
- a triple-tap, you can write:
+ constraints to be considered a single gesture. The counter is reset to 1
+ if the button changed. For example, to detect a triple-tap, you can write:
\qml
Rectangle {
diff --git a/src/quick/handlers/qquicktaphandler_p.h b/src/quick/handlers/qquicktaphandler_p.h
index 4fdb7065eb..a9908ebabd 100644
--- a/src/quick/handlers/qquicktaphandler_p.h
+++ b/src/quick/handlers/qquicktaphandler_p.h
@@ -24,13 +24,13 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QQuickTapHandler : public QQuickSinglePointHandler
+class Q_QUICK_EXPORT QQuickTapHandler : public QQuickSinglePointHandler
{
Q_OBJECT
Q_PROPERTY(bool pressed READ isPressed NOTIFY pressedChanged)
Q_PROPERTY(int tapCount READ tapCount NOTIFY tapCountChanged)
Q_PROPERTY(qreal timeHeld READ timeHeld NOTIFY timeHeldChanged)
- Q_PROPERTY(qreal longPressThreshold READ longPressThreshold WRITE setLongPressThreshold NOTIFY longPressThresholdChanged)
+ Q_PROPERTY(qreal longPressThreshold READ longPressThreshold WRITE setLongPressThreshold NOTIFY longPressThresholdChanged RESET resetLongPressThreshold)
Q_PROPERTY(GesturePolicy gesturePolicy READ gesturePolicy WRITE setGesturePolicy NOTIFY gesturePolicyChanged)
Q_PROPERTY(QQuickTapHandler::ExclusiveSignals exclusiveSignals READ exclusiveSignals WRITE setExclusiveSignals NOTIFY exclusiveSignalsChanged REVISION(6, 5))
@@ -48,11 +48,11 @@ public:
enum ExclusiveSignal {
NotExclusive = 0,
- SingleTap = 1 << 1,
- DoubleTap = 1 << 2
+ SingleTap = 1 << 0,
+ DoubleTap = 1 << 1
};
Q_DECLARE_FLAGS(ExclusiveSignals, ExclusiveSignal)
- Q_FLAG(ExclusiveSignal)
+ Q_FLAG(ExclusiveSignals)
explicit QQuickTapHandler(QQuickItem *parent = nullptr);
@@ -63,6 +63,7 @@ public:
qreal longPressThreshold() const;
void setLongPressThreshold(qreal longPressThreshold);
+ void resetLongPressThreshold();
GesturePolicy gesturePolicy() const { return m_gesturePolicy; }
void setGesturePolicy(GesturePolicy gesturePolicy);
@@ -92,25 +93,26 @@ protected:
private:
void setPressed(bool press, bool cancel, QPointerEvent *event, QEventPoint &point);
- int longPressThresholdMilliseconds() const;
void connectPreRenderSignal(bool conn = true);
void updateTimeHeld();
private:
QPointF m_lastTapPos;
- qreal m_lastTapTimestamp = 0;
+ quint64 m_lastTapTimestamp = 0;
QElapsedTimer m_holdTimer;
QBasicTimer m_longPressTimer;
QBasicTimer m_doubleTapTimer;
QEventPoint m_singleTapReleasedPoint;
+ QMetaObject::Connection m_preRenderSignalConnection;
Qt::MouseButton m_singleTapReleasedButton;
int m_tapCount = 0;
int m_longPressThreshold = -1;
GesturePolicy m_gesturePolicy = GesturePolicy::DragThreshold;
ExclusiveSignals m_exclusiveSignals = NotExclusive;
bool m_pressed = false;
+ bool m_longPressed = false;
- static qreal m_multiTapInterval;
+ static quint64 m_multiTapInterval;
static int m_mouseMultiClickDistanceSquared;
static int m_touchMultiTapDistanceSquared;
};
diff --git a/src/quick/handlers/qquickwheelhandler.cpp b/src/quick/handlers/qquickwheelhandler.cpp
index fc353620e5..7e29af1422 100644
--- a/src/quick/handlers/qquickwheelhandler.cpp
+++ b/src/quick/handlers/qquickwheelhandler.cpp
@@ -9,7 +9,7 @@
QT_BEGIN_NAMESPACE
-Q_LOGGING_CATEGORY(lcWheelHandler, "qt.quick.handler.wheel")
+Q_STATIC_LOGGING_CATEGORY(lcWheelHandler, "qt.quick.handler.wheel")
/*!
\qmltype WheelHandler
@@ -46,7 +46,7 @@ Q_LOGGING_CATEGORY(lcWheelHandler, "qt.quick.handler.wheel")
WheelHandler handles only a rotating mouse wheel by default; this
can be changed by setting acceptedDevices.
- \sa MouseArea, Flickable
+ \sa MouseArea, Flickable, {Qt Quick Examples - Pointer Handlers}
*/
QQuickWheelHandler::QQuickWheelHandler(QQuickItem *parent)
@@ -84,7 +84,7 @@ void QQuickWheelHandler::setOrientation(Qt::Orientation orientation)
\qmlproperty bool QtQuick::WheelHandler::invertible
Whether or not to reverse the direction of property change if
- QQuickPointerScrollEvent::inverted is true. The default is \c true.
+ \l QWheelEvent::inverted is \c true. The default is \c true.
If the operating system has a "natural scrolling" setting that causes
scrolling to be in the same direction as the finger movement, then if this
@@ -524,7 +524,7 @@ QMetaProperty &QQuickWheelHandlerPrivate::targetMetaProperty() const
By default, this property is set to
\l{QInputDevice::DeviceType}{PointerDevice.Mouse}, so as to react only to
- events events from an actual mouse wheel.
+ events from an actual mouse wheel.
WheelHandler can be made to respond to both mouse wheel and touchpad
scrolling by setting acceptedDevices to
diff --git a/src/quick/handlers/qquickwheelhandler_p.h b/src/quick/handlers/qquickwheelhandler_p.h
index bf05ff8af3..38d6fea8e6 100644
--- a/src/quick/handlers/qquickwheelhandler_p.h
+++ b/src/quick/handlers/qquickwheelhandler_p.h
@@ -25,7 +25,7 @@ QT_BEGIN_NAMESPACE
class QQuickWheelEvent;
class QQuickWheelHandlerPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickWheelHandler : public QQuickSinglePointHandler
+class Q_QUICK_EXPORT QQuickWheelHandler : public QQuickSinglePointHandler
{
Q_OBJECT
Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged)
diff --git a/src/quick/handlers/qquickwheelhandler_p_p.h b/src/quick/handlers/qquickwheelhandler_p_p.h
index 2124e29599..db1d394e96 100644
--- a/src/quick/handlers/qquickwheelhandler_p_p.h
+++ b/src/quick/handlers/qquickwheelhandler_p_p.h
@@ -21,7 +21,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QQuickWheelHandlerPrivate : public QQuickSinglePointHandlerPrivate
+class Q_QUICK_EXPORT QQuickWheelHandlerPrivate : public QQuickSinglePointHandlerPrivate
{
Q_DECLARE_PUBLIC(QQuickWheelHandler)
diff --git a/src/quick/items/context2d/qquickcanvasitem.cpp b/src/quick/items/context2d/qquickcanvasitem.cpp
index 0eb4fe40d3..d78bd040c2 100644
--- a/src/quick/items/context2d/qquickcanvasitem.cpp
+++ b/src/quick/items/context2d/qquickcanvasitem.cpp
@@ -9,7 +9,7 @@
#include <private/qquickcontext2dtexture_p.h>
#include <private/qsgadaptationlayer_p.h>
#include <qsgtextureprovider.h>
-#include <QtQuick/private/qquickpixmapcache_p.h>
+#include <QtQuick/private/qquickpixmap_p.h>
#include <QtGui/QGuiApplication>
#include <qsgtextureprovider.h>
@@ -252,7 +252,7 @@ QQuickCanvasItemPrivate::~QQuickCanvasItemPrivate()
QPainter instead of the more expensive and likely less performing
JavaScript and Context2D approach.
- \sa Context2D, QQuickPaintedItem
+ \sa Context2D, QQuickPaintedItem, {Qt Quick Examples - Pointer Handlers}
*/
QQuickCanvasItem::QQuickCanvasItem(QQuickItem *parent)
@@ -444,9 +444,8 @@ void QQuickCanvasItem::setCanvasWindow(const QRectF& rect)
\qmlproperty enumeration QtQuick::Canvas::renderTarget
Holds the current canvas render target.
- \list
- \li Canvas.Image - render to an in memory image buffer.
- \endlist
+ \value Canvas.Image Render to an in-memory image buffer.
+ \value Canvas.FramebufferObject As of Qt 6.0, this value is ignored.
This hint is supplied along with renderStrategy to the graphics context to
determine the method of rendering. A renderStrategy, renderTarget or a
@@ -480,11 +479,9 @@ void QQuickCanvasItem::setRenderTarget(QQuickCanvasItem::RenderTarget target)
\qmlproperty enumeration QtQuick::Canvas::renderStrategy
Holds the current canvas rendering strategy.
- \list
- \li Canvas.Immediate - context will perform graphics commands immediately in the main UI thread.
- \li Canvas.Threaded - context will defer graphics commands to a private rendering thread.
- \li Canvas.Cooperative - context will defer graphics commands to the applications global render thread.
- \endlist
+ \value Canvas.Immediate context will perform graphics commands immediately in the main UI thread.
+ \value Canvas.Threaded context will defer graphics commands to a private rendering thread.
+ \value Canvas.Cooperative context will defer graphics commands to the applications global render thread.
This hint is supplied along with renderTarget to the graphics context to
determine the method of rendering. A renderStrategy, renderTarget or a
@@ -813,7 +810,7 @@ QSGTextureProvider *QQuickCanvasItem::textureProvider() const
*/
-void QQuickCanvasItem::getContext(QQmlV4Function *args)
+void QQuickCanvasItem::getContext(QQmlV4FunctionPtr args)
{
Q_D(QQuickCanvasItem);
@@ -857,7 +854,7 @@ void QQuickCanvasItem::getContext(QQmlV4Function *args)
scene.
*/
-void QQuickCanvasItem::requestAnimationFrame(QQmlV4Function *args)
+void QQuickCanvasItem::requestAnimationFrame(QQmlV4FunctionPtr args)
{
QV4::Scope scope(args->v4engine());
QV4::ScopedFunctionObject f(scope, (*args)[0]);
@@ -886,7 +883,7 @@ void QQuickCanvasItem::requestAnimationFrame(QQmlV4Function *args)
This function will cancel the animation callback referenced by \a handle.
*/
-void QQuickCanvasItem::cancelRequestAnimationFrame(QQmlV4Function *args)
+void QQuickCanvasItem::cancelRequestAnimationFrame(QQmlV4FunctionPtr args)
{
QV4::Scope scope(args->v4engine());
QV4::ScopedValue v(scope, (*args)[0]);
@@ -961,12 +958,12 @@ bool QQuickCanvasItem::save(const QString &filename, const QSizeF &imageSize) co
return toImage(QRectF(QPointF(0, 0), imageSize)).save(url.toLocalFile());
}
-QQmlRefPointer<QQuickCanvasPixmap> QQuickCanvasItem::loadedPixmap(const QUrl& url)
+QQmlRefPointer<QQuickCanvasPixmap> QQuickCanvasItem::loadedPixmap(const QUrl& url, QSizeF sourceSize)
{
Q_D(QQuickCanvasItem);
QUrl fullPathUrl = d->baseUrl.resolved(url);
if (!d->pixmaps.contains(fullPathUrl)) {
- loadImage(url);
+ loadImage(url, sourceSize);
}
return d->pixmaps.value(fullPathUrl);
}
@@ -980,7 +977,7 @@ QQmlRefPointer<QQuickCanvasPixmap> QQuickCanvasItem::loadedPixmap(const QUrl& ur
*/
/*!
- \qmlmethod QtQuick::Canvas::loadImage(url image)
+ \qmlmethod QtQuick::Canvas::loadImage(url image, size sourceSize = undefined)
Loads the given \a image asynchronously.
@@ -989,10 +986,14 @@ QQmlRefPointer<QQuickCanvasPixmap> QQuickCanvasItem::loadedPixmap(const QUrl& ur
\note Only loaded images can be painted on the Canvas item.
+ If \a sourceSize is specified, the image will be scaled to that size during loading. This is
+ useful for loading scalable (vector) images (eg. SVGs) at their intended display size. This
+ parameter was introduced in Qt 6.7.
+
\sa unloadImage(), imageLoaded(), isImageLoaded(),
Context2D::createImageData(), Context2D::drawImage()
*/
-void QQuickCanvasItem::loadImage(const QUrl& url)
+void QQuickCanvasItem::loadImage(const QUrl& url, QSizeF sourceSize)
{
Q_D(QQuickCanvasItem);
QUrl fullPathUrl = d->baseUrl.resolved(url);
@@ -1004,6 +1005,8 @@ void QQuickCanvasItem::loadImage(const QUrl& url)
pix->load(qmlEngine(this)
, fullPathUrl
+ , QRect()
+ , sourceSize.toSize()
, QQuickPixmap::Cache | QQuickPixmap::Asynchronous);
if (pix->isLoading())
pix->connectFinished(this, SIGNAL(imageLoaded()));
@@ -1203,7 +1206,7 @@ QRect QQuickCanvasItem::tiledRect(const QRectF &window, const QSize &tileSize)
This signal is emitted when the \a region needs to be rendered. If a context
is active it can be referenced from the context property.
- This signal can be triggered by markdirty(), requestPaint() or by changing
+ This signal can be triggered by markDirty(), requestPaint() or by changing
the current canvas window.
*/
diff --git a/src/quick/items/context2d/qquickcanvasitem_p.h b/src/quick/items/context2d/qquickcanvasitem_p.h
index d7821a53ba..300039743d 100644
--- a/src/quick/items/context2d/qquickcanvasitem_p.h
+++ b/src/quick/items/context2d/qquickcanvasitem_p.h
@@ -31,9 +31,8 @@ class QQuickCanvasContext;
class QQuickCanvasItemPrivate;
class QQuickPixmap;
-class QQmlV4Function;
-class QQuickCanvasPixmap : public QQmlRefCount
+class QQuickCanvasPixmap final : public QQmlRefCounted<QQuickCanvasPixmap>
{
public:
QQuickCanvasPixmap(const QImage& image);
@@ -110,17 +109,17 @@ public:
QImage toImage(const QRectF& rect = QRectF()) const;
- Q_INVOKABLE void getContext(QQmlV4Function *args);
+ Q_INVOKABLE void getContext(QQmlV4FunctionPtr args);
- Q_INVOKABLE void requestAnimationFrame(QQmlV4Function *args);
- Q_INVOKABLE void cancelRequestAnimationFrame(QQmlV4Function *args);
+ Q_INVOKABLE void requestAnimationFrame(QQmlV4FunctionPtr args);
+ Q_INVOKABLE void cancelRequestAnimationFrame(QQmlV4FunctionPtr args);
Q_INVOKABLE void requestPaint();
Q_INVOKABLE void markDirty(const QRectF& dirtyRect = QRectF());
Q_INVOKABLE bool save(const QString &filename, const QSizeF &imageSize = QSizeF()) const;
Q_INVOKABLE QString toDataURL(const QString& type = QLatin1String("image/png")) const;
- QQmlRefPointer<QQuickCanvasPixmap> loadedPixmap(const QUrl& url);
+ QQmlRefPointer<QQuickCanvasPixmap> loadedPixmap(const QUrl& url, QSizeF sourceSize = QSizeF());
bool isTextureProvider() const override;
QSGTextureProvider *textureProvider() const override;
@@ -139,7 +138,7 @@ Q_SIGNALS:
void imageLoaded();
public Q_SLOTS:
- void loadImage(const QUrl& url);
+ void loadImage(const QUrl& url, QSizeF sourceSize = QSizeF());
void unloadImage(const QUrl& url);
bool isImageLoaded(const QUrl& url) const;
bool isImageLoading(const QUrl& url) const;
@@ -186,6 +185,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickCanvasItem)
-
#endif //QQUICKCANVASITEM_P_H
diff --git a/src/quick/items/context2d/qquickcontext2d.cpp b/src/quick/items/context2d/qquickcontext2d.cpp
index b9354b0ac2..1a6391a270 100644
--- a/src/quick/items/context2d/qquickcontext2d.cpp
+++ b/src/quick/items/context2d/qquickcontext2d.cpp
@@ -44,6 +44,8 @@
#include <private/qsgdefaultrendercontext_p.h>
+#include <QtCore/qpointer.h>
+
#include <cmath>
#if defined(Q_OS_QNX) || defined(Q_OS_ANDROID)
#include <ctype.h>
@@ -96,7 +98,7 @@ QT_BEGIN_NAMESPACE
THROW_GENERIC_ERROR("Not a Context2D object");
#define qClamp(val, min, max) qMin(qMax(val, min), max)
#define CHECK_RGBA(c) (c == '-' || c == '.' || (c >=0 && c <= 9))
-Q_QUICK_PRIVATE_EXPORT QColor qt_color_from_string(const QV4::Value &name)
+Q_QUICK_EXPORT QColor qt_color_from_string(const QV4::Value &name)
{
QByteArray str = name.toQString().toUtf8();
@@ -932,7 +934,7 @@ void QV4::Heap::QQuickJSContext2DImageData::init()
DEFINE_OBJECT_VTABLE(QQuickJSContext2DImageData);
-static QV4::ReturnedValue qt_create_image_data(qreal w, qreal h, QV4::ExecutionEngine *v4, const QImage& image)
+static QV4::ReturnedValue qt_create_image_data(qreal w, qreal h, QV4::ExecutionEngine *v4, QImage&& image)
{
QV4::Scope scope(v4);
QQuickContext2DEngineData *ed = engineData(scope.engine);
@@ -946,7 +948,7 @@ static QV4::ReturnedValue qt_create_image_data(qreal w, qreal h, QV4::ExecutionE
} else {
// After qtbase 88e56d0932a3615231adf40d5ae033e742d72c33, the image size can be off by one.
Q_ASSERT(qAbs(image.width() - qRound(w * image.devicePixelRatio())) <= 1 && qAbs(image.height() - qRound(h * image.devicePixelRatio())) <= 1);
- *pixelData->d()->image = image.format() == QImage::Format_ARGB32 ? image : image.convertToFormat(QImage::Format_ARGB32);
+ *pixelData->d()->image = image.format() == QImage::Format_ARGB32 ? std::move(image) : std::move(image).convertToFormat(QImage::Format_ARGB32);
}
QV4::Scoped<QQuickJSContext2DImageData> imageData(scope, scope.engine->memoryManager->allocate<QQuickJSContext2DImageData>());
@@ -1289,29 +1291,81 @@ QV4::ReturnedValue QQuickJSContext2D::method_set_globalAlpha(const QV4::Function
/*!
\qmlproperty string QtQuick::Context2D::globalCompositeOperation
- Holds the current the current composition operation, from the list below:
- \list
- \li source-atop - A atop B. Display the source image wherever both images are opaque.
- Display the destination image wherever the destination image is opaque but the source image is transparent.
- Display transparency elsewhere.
- \li source-in - A in B. Display the source image wherever both the source image and destination image are opaque.
- Display transparency elsewhere.
- \li source-out - A out B. Display the source image wherever the source image is opaque and the destination image is transparent.
- Display transparency elsewhere.
- \li source-over - (default) A over B. Display the source image wherever the source image is opaque.
- Display the destination image elsewhere.
- \li destination-atop - B atop A. Same as source-atop but using the destination image instead of the source image and vice versa.
- \li destination-in - B in A. Same as source-in but using the destination image instead of the source image and vice versa.
- \li destination-out - B out A. Same as source-out but using the destination image instead of the source image and vice versa.
- \li destination-over - B over A. Same as source-over but using the destination image instead of the source image and vice versa.
- \li lighter - A plus B. Display the sum of the source image and destination image, with color values approaching 255 (100%) as a limit.
- \li copy - A (B is ignored). Display the source image instead of the destination image.
- \li xor - A xor B. Exclusive OR of the source image and destination image.
- \endlist
-
- Additionally, this property also accepts the compositon modes listed in QPainter::CompositionMode. According to the W3C standard, these
- extension composition modes are provided as "vendorName-operationName" syntax, for example: QPainter::CompositionMode_Exclusion is provided as
- "qt-exclusion".
+ Holds the current the current composition operation. Allowed operations are:
+
+ \value "source-atop"
+ QPainter::CompositionMode_SourceAtop
+ A atop B. Display the source image wherever both images are opaque.
+ Display the destination image wherever the destination image is opaque
+ but the source image is transparent. Display transparency elsewhere.
+ \value "source-in"
+ QPainter::CompositionMode_SourceIn
+ A in B. Display the source image wherever both the source image and
+ destination image are opaque. Display transparency elsewhere.
+ \value "source-out"
+ QPainter::CompositionMode_SourceOut
+ A out B. Display the source image wherever the source image is opaque
+ and the destination image is transparent. Display transparency elsewhere.
+ \value "source-over"
+ QPainter::CompositionMode_SourceOver (default)
+ A over B. Display the source image wherever the source image is opaque.
+ Display the destination image elsewhere.
+ \value "destination-atop"
+ QPainter::CompositionMode_DestinationAtop
+ B atop A. Same as \c source-atop but using the destination image instead
+ of the source image and vice versa.
+ \value "destination-in"
+ QPainter::CompositionMode_DestinationIn
+ B in A. Same as \c source-in but using the destination image instead of
+ the source image and vice versa.
+ \value "destination-out"
+ QPainter::CompositionMode_DestinationOut
+ B out A. Same as \c source-out but using the destination image instead
+ of the source image and vice versa.
+ \value "destination-over"
+ QPainter::CompositionMode_DestinationOver
+ B over A. Same as \c source-over but using the destination image
+ instead of the source image and vice versa.
+ \value "lighter"
+ QPainter::CompositionMode_Plus
+ A plus B. Display the sum of the source image and destination image,
+ with color values approaching \c 255 (100%) as a limit.
+ \value "copy"
+ QPainter::CompositionMode_Source
+ A (B is ignored). Display the source image instead of the destination image.
+ \value "xor"
+ QPainter::CompositionMode_Xor
+ A xor B. Exclusive OR of the source image and destination image.
+ \value "qt-clear"
+ QPainter::CompositionMode_Clear
+ \value "qt-destination"
+ QPainter::CompositionMode_Destination
+ \value "qt-multiply"
+ QPainter::CompositionMode_Multiply
+ \value "qt-screen"
+ QPainter::CompositionMode_Screen
+ \value "qt-overlay"
+ QPainter::CompositionMode_Overlay
+ \value "qt-darken"
+ QPainter::CompositionMode_Darken
+ \value "qt-lighten"
+ QPainter::CompositionMode_Lighten
+ \value "qt-color-dodge"
+ QPainter::CompositionMode_ColorDodge
+ \value "qt-color-burn"
+ QPainter::CompositionMode_ColorBurn
+ \value "qt-hard-light"
+ QPainter::CompositionMode_HardLight
+ \value "qt-soft-light"
+ QPainter::CompositionMode_SoftLight
+ \value "qt-difference"
+ QPainter::CompositionMode_Difference
+ \value "qt-exclusion"
+ QPainter::CompositionMode_Exclusion
+
+ In compliance with the W3C standard, the extended composition modes beyond
+ the required modes are provided as "vendorName-operationName" syntax, for
+ example: QPainter::CompositionMode_Exclusion is provided as "qt-exclusion".
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_globalCompositeOperation(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
@@ -1422,18 +1476,19 @@ QV4::ReturnedValue QQuickJSContext2D::method_set_fillStyle(const QV4::FunctionOb
}
RETURN_UNDEFINED();
}
+
/*!
\qmlproperty enumeration QtQuick::Context2D::fillRule
- Holds the current fill rule used for filling shapes. The following fill rules are supported:
- \list
- \li Qt.OddEvenFill
- \li Qt.WindingFill
- \endlist
- Note: Unlike the QPainterPath, the Canvas API uses the winding fill as the default fill rule.
- The fillRule property is part of the context rendering state.
+ Holds the current fill rule used for filling shapes. The following fill rules are supported:
- \sa fillStyle
- */
+ \value Qt.OddEvenFill Qt::OddEvenFill
+ \value Qt.WindingFill (default) Qt::WindingFill
+
+ \note Unlike QPainterPath, the Canvas API uses the winding fill as the default fill rule.
+ The fillRule property is part of the context rendering state.
+
+ \sa fillStyle
+*/
QV4::ReturnedValue QQuickJSContext2D::method_get_fillRule(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
@@ -1687,48 +1742,49 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_createConicalGradient(cons
}
/*!
- \qmlmethod variant QtQuick::Context2D::createPattern(color color, enumeration patternMode)
- This is an overloaded function.
- Returns a CanvasPattern object that uses the given \a color and \a patternMode.
- The valid pattern modes are:
- \list
- \li Qt.SolidPattern
- \li Qt.Dense1Pattern
- \li Qt.Dense2Pattern
- \li Qt.Dense3Pattern
- \li Qt.Dense4Pattern
- \li Qt.Dense5Pattern
- \li Qt.Dense6Pattern
- \li Qt.Dense7Pattern
- \li Qt.HorPattern
- \li Qt.VerPattern
- \li Qt.CrossPattern
- \li Qt.BDiagPattern
- \li Qt.FDiagPattern
- \li Qt.DiagCrossPattern
-\endlist
+ \qmlmethod variant QtQuick::Context2D::createPattern(color color, enumeration patternMode)
+ This is an overloaded function.
+ Returns a CanvasPattern object that uses the given \a color and \a patternMode.
+ The valid pattern modes are:
+
+ \value Qt.SolidPattern Qt::SolidPattern
+ \value Qt.Dense1Pattern Qt::Dense1Pattern
+ \value Qt.Dense2Pattern Qt::Dense2Pattern
+ \value Qt.Dense3Pattern Qt::Dense3Pattern
+ \value Qt.Dense4Pattern Qt::Dense4Pattern
+ \value Qt.Dense5Pattern Qt::Dense5Pattern
+ \value Qt.Dense6Pattern Qt::Dense6Pattern
+ \value Qt.Dense7Pattern Qt::Dense7Pattern
+ \value Qt.HorPattern Qt::HorPattern
+ \value Qt.VerPattern Qt::VerPattern
+ \value Qt.CrossPattern Qt::CrossPattern
+ \value Qt.BDiagPattern Qt::BDiagPattern
+ \value Qt.FDiagPattern Qt::FDiagPattern
+ \value Qt.DiagCrossPattern Qt::DiagCrossPattern
+
\sa Qt::BrushStyle
- */
+*/
/*!
- \qmlmethod variant QtQuick::Context2D::createPattern(Image image, string repetition)
- Returns a CanvasPattern object that uses the given image and repeats in the direction(s) given by the repetition argument.
+ \qmlmethod variant QtQuick::Context2D::createPattern(Image image, string repetition)
+ Returns a CanvasPattern object that uses the given image and repeats in the
+ direction(s) given by the repetition argument.
- The \a image parameter must be a valid Image item, a valid CanvasImageData object or loaded image url, if there is no image data, throws an INVALID_STATE_ERR exception.
+ The \a image parameter must be a valid Image item, a valid CanvasImageData
+ object or loaded image url. If there is no image data, thus function throws an
+ INVALID_STATE_ERR exception.
- The allowed values for \a repetition are:
+ The allowed values for \a repetition are:
- \list
- \li "repeat" - both directions
- \li "repeat-x - horizontal only
- \li "repeat-y" - vertical only
- \li "no-repeat" - neither
- \endlist
+ \value "repeat" both directions
+ \value "repeat-x horizontal only
+ \value "repeat-y" vertical only
+ \value "no-repeat" neither
- If the repetition argument is empty or null, the value "repeat" is used.
+ If the repetition argument is empty or null, the value "repeat" is used.
- \sa strokeStyle
- \sa fillStyle
- */
+ \sa strokeStyle
+ \sa fillStyle
+*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_createPattern(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
QV4::Scope scope(b);
@@ -1792,13 +1848,20 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_createPattern(const QV4::F
// line styles
/*!
\qmlproperty string QtQuick::Context2D::lineCap
- Holds the current line cap style.
- The possible line cap styles are:
- \list
- \li butt - the end of each line has a flat edge perpendicular to the direction of the line, this is the default line cap value.
- \li round - a semi-circle with the diameter equal to the width of the line must then be added on to the end of the line.
- \li square - a rectangle with the length of the line width and the width of half the line width, placed flat against the edge perpendicular to the direction of the line.
- \endlist
+ Holds the current line cap style.
+ The possible line cap styles are:
+
+ \value "butt"
+ (default) Qt::FlatCap the end of each line has a flat edge
+ perpendicular to the direction of the line.
+ \value "round"
+ Qt::RoundCap a semi-circle with the diameter equal to the width of the
+ line is added on to the end of the line.
+ \value "square"
+ Qt::SquareCap a rectangle with the length of the line width and the
+ width of half the line width, placed flat against the edge
+ perpendicular to the direction of the line.
+
Other values are ignored.
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_lineCap(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
@@ -1848,16 +1911,18 @@ QV4::ReturnedValue QQuickJSContext2D::method_set_lineCap(const QV4::FunctionObje
/*!
\qmlproperty string QtQuick::Context2D::lineJoin
- Holds the current line join style. A join exists at any point in a subpath
- shared by two consecutive lines. When a subpath is closed, then a join also exists
- at its first point (equivalent to its last point) connecting the first and last lines in the subpath.
+ Holds the current line join style. A join exists at any point in a subpath
+ shared by two consecutive lines. When a subpath is closed, then a join also
+ exists at its first point (equivalent to its last point) connecting the
+ first and last lines in the subpath.
The possible line join styles are:
- \list
- \li bevel - this is all that is rendered at joins.
- \li round - a filled arc connecting the two aforementioned corners of the join, abutting (and not overlapping) the aforementioned triangle, with the diameter equal to the line width and the origin at the point of the join, must be rendered at joins.
- \li miter - a second filled triangle must (if it can given the miter length) be rendered at the join, this is the default line join style.
- \endlist
+
+ \value "bevel" Qt::BevelJoin The triangular notch between the two lines is filled.
+ \value "round" Qt::RoundJoin A circular arc between the two lines is filled.
+ \value "miter" (default) Qt::MiterJoin The outer edges of the lines are extended to
+ meet at an angle, and this area is filled.
+
Other values are ignored.
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_lineJoin(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
@@ -2306,8 +2371,8 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_strokeRect(const QV4::Func
\image qml-item-canvas-startAngle.png
- The \a anticlockwise parameter is \c true for each arc in the figure above
- because they are all drawn in the anticlockwise direction.
+ The \a anticlockwise parameter is \c false for each arc in the figure above
+ because they are all drawn in the clockwise direction.
\sa arcTo, {http://www.w3.org/TR/2dcontext/#dom-context-2d-arc}{W3C's 2D
Context Standard for arc()}
@@ -2790,19 +2855,20 @@ QV4::ReturnedValue QQuickJSContext2D::method_set_font(const QV4::FunctionObject
}
/*!
- \qmlproperty string QtQuick::Context2D::textAlign
-
- Holds the current text alignment settings.
- The possible values are:
- \list
- \li start
- \li end
- \li left
- \li right
- \li center
- \endlist
- Other values are ignored. The default value is "start".
- */
+ \qmlproperty string QtQuick::Context2D::textAlign
+
+ Holds the current text alignment settings. The possible values are:
+
+ \value "start" (default) Align to the start edge of the text (left side in
+ left-to-right text, right side in right-to-left text).
+ \value "end" Align to the end edge of the text (right side in left-to-right
+ text, left side in right-to-left text).
+ \value "left" Qt::AlignLeft
+ \value "right" Qt::AlignRight
+ \value "center" Qt::AlignHCenter
+
+ Other values are ignored.
+*/
QV4::ReturnedValue QQuickJSContext2D::method_get_textAlign(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
@@ -2857,20 +2923,19 @@ QV4::ReturnedValue QQuickJSContext2D::method_set_textAlign(const QV4::FunctionOb
}
/*!
- \qmlproperty string QtQuick::Context2D::textBaseline
-
- Holds the current baseline alignment settings.
- The possible values are:
- \list
- \li top
- \li hanging
- \li middle
- \li alphabetic
- \li ideographic
- \li bottom
- \endlist
- Other values are ignored. The default value is "alphabetic".
- */
+ \qmlproperty string QtQuick::Context2D::textBaseline
+
+ Holds the current baseline alignment settings. The possible values are:
+
+ \value "top" The top of the em square
+ \value "hanging" The hanging baseline
+ \value "middle" The middle of the em square
+ \value "alphabetic" (default) The alphabetic baseline
+ \value "ideographic" The ideographic-under baseline
+ \value "bottom" The bottom of the em square
+
+ Other values are ignored. The default value is "alphabetic".
+*/
QV4::ReturnedValue QQuickJSContext2D::method_get_textBaseline(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
QV4::Scope scope(b);
@@ -3347,7 +3412,7 @@ bool QQuickJSContext2DPixelData::virtualPut(QV4::Managed *m, QV4::PropertyKey id
/*!
\qmlmethod CanvasImageData QtQuick::Context2D::createImageData(CanvasImageData imageData)
- Creates a CanvasImageData object with the same dimensions as the argument.
+ Creates a CanvasImageData object with the same dimensions as the \a imageData argument.
*/
/*!
\qmlmethod CanvasImageData QtQuick::Context2D::createImageData(Url imageUrl)
@@ -3378,7 +3443,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_createImageData(const QV4:
}
} else if (arg0->isString()) {
QImage image = r->d()->context()->createPixmap(QUrl(arg0->toQStringNoThrow()))->image();
- RETURN_RESULT(qt_create_image_data(image.width(), image.height(), scope.engine, image));
+ RETURN_RESULT(qt_create_image_data(image.width(), image.height(), scope.engine, std::move(image)));
}
} else if (argc == 2) {
qreal w = argv[0].toNumber();
@@ -3419,7 +3484,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_getImageData(const QV4::Fu
THROW_DOM(DOMEXCEPTION_INDEX_SIZE_ERR, "getImageData(): Invalid arguments");
QImage image = r->d()->context()->canvas()->toImage(QRectF(x, y, w, h));
- RETURN_RESULT(qt_create_image_data(w, h, scope.engine, image));
+ RETURN_RESULT(qt_create_image_data(w, h, scope.engine, std::move(image)));
}
RETURN_RESULT(QV4::Encode::null());
}
@@ -3534,7 +3599,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_putImageData(const QV4::Fu
\code
var gradient = ctx.createLinearGradient(0, 0, 100, 100);
gradient.addColorStop(0.3, Qt.rgba(1, 0, 0, 1));
- gradient.addColorStop(0.7, 'rgba(0, 255, 255, 1');
+ gradient.addColorStop(0.7, 'rgba(0, 255, 255, 1)');
\endcode
*/
QV4::ReturnedValue QQuickContext2DStyle::gradient_proto_addColorStop(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
@@ -4076,6 +4141,7 @@ static int textAlignOffset(QQuickContext2D::TextAlignType value, const QFontMetr
break;
case QQuickContext2D::Right:
offset = metrics.horizontalAdvance(text);
+ break;
case QQuickContext2D::Left:
default:
break;
@@ -4089,9 +4155,9 @@ void QQuickContext2D::setGrabbedImage(const QImage& grab)
m_grabbed = true;
}
-QQmlRefPointer<QQuickCanvasPixmap> QQuickContext2D::createPixmap(const QUrl& url)
+QQmlRefPointer<QQuickCanvasPixmap> QQuickContext2D::createPixmap(const QUrl& url, QSizeF sourceSize)
{
- return m_canvas->loadedPixmap(url);
+ return m_canvas->loadedPixmap(url, sourceSize);
}
QPainterPath QQuickContext2D::createTextGlyphs(qreal x, qreal y, const QString& text)
diff --git a/src/quick/items/context2d/qquickcontext2d_p.h b/src/quick/items/context2d/qquickcontext2d_p.h
index 9ae769f504..50917d3ecf 100644
--- a/src/quick/items/context2d/qquickcontext2d_p.h
+++ b/src/quick/items/context2d/qquickcontext2d_p.h
@@ -212,7 +212,7 @@ public:
bool isPointInPath(qreal x, qreal y) const;
QPainterPath createTextGlyphs(qreal x, qreal y, const QString& text);
- QQmlRefPointer<QQuickCanvasPixmap> createPixmap(const QUrl& url);
+ QQmlRefPointer<QQuickCanvasPixmap> createPixmap(const QUrl& url, QSizeF sourceSize = QSizeF());
QSurface *surface() const { return m_surface.data(); }
void setGrabbedImage(const QImage& grab);
@@ -239,8 +239,6 @@ public:
static QMutex mutex;
};
-
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickContext2D)
#endif // QQUICKCONTEXT2D_P_H
diff --git a/src/quick/items/qquickaccessibleattached.cpp b/src/quick/items/qquickaccessibleattached.cpp
index 865fb8bf11..4a5dfa4111 100644
--- a/src/quick/items/qquickaccessibleattached.cpp
+++ b/src/quick/items/qquickaccessibleattached.cpp
@@ -120,6 +120,15 @@ QT_BEGIN_NAMESPACE
\endtable
*/
+/*!
+ \qmlproperty string QtQuick::Accessible::id
+
+ This property sets an identifier for the object.
+ It can be used to provide stable identifiers to UI tests.
+ By default, the identifier is set to the ID of the QML object.
+ If the ID is not set the default of \l QAccessible::Identifier is used.
+*/
+
/*! \qmlproperty bool QtQuick::Accessible::focusable
\brief This property holds whether this item is focusable.
@@ -301,15 +310,19 @@ QQuickAccessibleAttached::QQuickAccessibleAttached(QObject *parent)
: QObject(parent), m_role(QAccessible::NoRole)
{
Q_ASSERT(parent);
- if (!item()) {
- qmlWarning(parent) << "Accessible must be attached to an Item";
- return;
- }
-
// Enable accessibility for items with accessible content. This also
- // enables accessibility for the ancestors of souch items.
- item()->d_func()->setAccessible();
- QAccessibleEvent ev(item(), QAccessible::ObjectCreated);
+ // enables accessibility for the ancestors of such items.
+ auto item = qobject_cast<QQuickItem *>(parent);
+ if (item) {
+ item->d_func()->setAccessible();
+ } else {
+ const QLatin1StringView className(QQmlData::ensurePropertyCache(parent)->firstCppMetaObject()->className());
+ if (className != QLatin1StringView("QQuickAction")) {
+ qmlWarning(parent) << "Accessible must be attached to an Item or an Action";
+ return;
+ }
+ }
+ QAccessibleEvent ev(parent, QAccessible::ObjectCreated);
QAccessible::updateAccessibility(&ev);
if (const QMetaObject *pmo = parent->metaObject()) {
@@ -423,13 +436,15 @@ QQuickAccessibleAttached *QQuickAccessibleAttached::qmlAttachedProperties(QObjec
bool QQuickAccessibleAttached::ignored() const
{
- return item() ? !item()->d_func()->isAccessible : false;
+ auto item = qobject_cast<QQuickItem *>(parent());
+ return item ? !item->d_func()->isAccessible : false;
}
void QQuickAccessibleAttached::setIgnored(bool ignored)
{
- if (this->ignored() != ignored && item()) {
- item()->d_func()->isAccessible = !ignored;
+ auto item = qobject_cast<QQuickItem *>(parent());
+ if (item && this->ignored() != ignored) {
+ item->d_func()->isAccessible = !ignored;
emit ignoredChanged();
}
}
@@ -457,8 +472,13 @@ bool QQuickAccessibleAttached::doAction(const QString &actionName)
sig = &sigPreviousPage;
else if (actionName == QAccessibleActionInterface::nextPageAction())
sig = &sigNextPage;
- if (sig && isSignalConnected(*sig))
- return sig->invoke(this);
+ if (sig && isSignalConnected(*sig)) {
+ bool ret = false;
+ if (m_proxying)
+ ret = sig->invoke(m_proxying);
+ ret |= sig->invoke(this);
+ return ret;
+ }
return false;
}
@@ -497,6 +517,69 @@ QString QQuickAccessibleAttached::stripHtml(const QString &html)
#endif
}
+void QQuickAccessibleAttached::setProxying(QQuickAccessibleAttached *proxying)
+{
+ if (proxying == m_proxying)
+ return;
+
+ const QMetaObject &mo = staticMetaObject;
+ if (m_proxying) {
+ // We disconnect all signals from the proxy into this object
+ auto mo = m_proxying->metaObject();
+ auto propertyCache = QQmlData::ensurePropertyCache(m_proxying);
+ for (int signalIndex = propertyCache->signalOffset();
+ signalIndex < propertyCache->signalCount(); ++signalIndex) {
+ const QMetaMethod m = mo->method(propertyCache->signal(signalIndex)->coreIndex());
+ Q_ASSERT(m.methodType() == QMetaMethod::Signal);
+ if (m.methodType() != QMetaMethod::Signal)
+ continue;
+
+ disconnect(m_proxying, m, this, m);
+ }
+ }
+
+ m_proxying = proxying;
+
+ if (m_proxying) {
+ // We connect all signals from the proxy into this object
+ auto propertyCache = QQmlData::ensurePropertyCache(m_proxying);
+ auto mo = m_proxying->metaObject();
+ for (int signalIndex = propertyCache->signalOffset();
+ signalIndex < propertyCache->signalCount(); ++signalIndex) {
+ const QMetaMethod m = mo->method(propertyCache->signal(signalIndex)->coreIndex());
+ Q_ASSERT(m.methodType() == QMetaMethod::Signal);
+ connect(proxying, m, this, m);
+ }
+ }
+
+ // We check all properties
+ for (int prop = mo.propertyOffset(); prop < mo.propertyCount(); ++prop) {
+ const QMetaProperty p = mo.property(prop);
+ if (!p.hasNotifySignal()) {
+ continue;
+ }
+
+ const QMetaMethod signal = p.notifySignal();
+ if (signal.parameterCount() == 0)
+ signal.invoke(this);
+ else
+ signal.invoke(this, Q_ARG(bool, p.read(this).toBool()));
+ }
+}
+
+/*!
+ * \since 6.8
+ * Issues an announcement event with a \a message with politeness \a politeness.
+ *
+ * \sa QAccessibleAnnouncementEvent
+ */
+void QQuickAccessibleAttached::announce(const QString &message, QAccessible::AnnouncementPoliteness politeness)
+{
+ QAccessibleAnnouncementEvent event(parent(), message);
+ event.setPoliteness(politeness);
+ QAccessible::updateAccessibility(&event);
+}
+
QT_END_NAMESPACE
#include "moc_qquickaccessibleattached_p.cpp"
diff --git a/src/quick/items/qquickaccessibleattached_p.h b/src/quick/items/qquickaccessibleattached_p.h
index 7b70bdb75c..aa15b437ce 100644
--- a/src/quick/items/qquickaccessibleattached_p.h
+++ b/src/quick/items/qquickaccessibleattached_p.h
@@ -30,9 +30,11 @@ QT_BEGIN_NAMESPACE
#define STATE_PROPERTY(P) \
Q_PROPERTY(bool P READ P WRITE set_ ## P NOTIFY P ## Changed FINAL) \
- bool P() const { return m_state.P ; } \
+ bool P() const { return m_proxying && !m_stateExplicitlySet.P ? m_proxying->P() : m_state.P ; } \
void set_ ## P(bool arg) \
{ \
+ if (m_proxying) \
+ m_proxying->set_##P(arg);\
m_stateExplicitlySet.P = true; \
if (m_state.P == arg) \
return; \
@@ -45,12 +47,13 @@ QT_BEGIN_NAMESPACE
} \
Q_SIGNAL void P ## Changed(bool arg);
-class Q_QUICK_PRIVATE_EXPORT QQuickAccessibleAttached : public QObject
+class Q_QUICK_EXPORT QQuickAccessibleAttached : public QObject
{
Q_OBJECT
Q_PROPERTY(QAccessible::Role role READ role WRITE setRole NOTIFY roleChanged FINAL)
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged FINAL)
Q_PROPERTY(QString description READ description WRITE setDescription NOTIFY descriptionChanged FINAL)
+ Q_PROPERTY(QString id READ id WRITE setId NOTIFY idChanged REVISION(6, 8) FINAL)
Q_PROPERTY(bool ignored READ ignored WRITE setIgnored NOTIFY ignoredChanged FINAL)
QML_NAMED_ELEMENT(Accessible)
@@ -84,6 +87,8 @@ public:
QString name() const {
if (m_state.passwordEdit)
return QString();
+ if (m_proxying)
+ return m_proxying->name();
return m_name;
}
@@ -99,9 +104,15 @@ public:
}
void setNameImplicitly(const QString &name);
- QString description() const { return m_description; }
+ QString description() const {
+ return !m_descriptionExplicitlySet && m_proxying ? m_proxying->description() : m_description;
+ }
void setDescription(const QString &description)
{
+ if (!m_descriptionExplicitlySet && m_proxying) {
+ disconnect(m_proxying, &QQuickAccessibleAttached::descriptionChanged, this, &QQuickAccessibleAttached::descriptionChanged);
+ }
+ m_descriptionExplicitlySet = true;
if (m_description != description) {
m_description = description;
Q_EMIT descriptionChanged();
@@ -110,6 +121,17 @@ public:
}
}
+ QString id() const { return m_id; }
+ void setId(const QString &id)
+ {
+ if (m_id != id) {
+ m_id = id;
+ Q_EMIT idChanged();
+ QAccessibleEvent ev(parent(), QAccessible::IdentifierChanged);
+ QAccessible::updateAccessibility(&ev);
+ }
+ }
+
// Factory function
static QQuickAccessibleAttached *qmlAttachedProperties(QObject *obj);
@@ -143,6 +165,13 @@ public:
if (att && (role == QAccessible::NoRole || att->role() == role)) {
break;
}
+ if (auto action = object->property("action").value<QObject *>(); action) {
+ QQuickAccessibleAttached *att = QQuickAccessibleAttached::attachedProperties(action);
+ if (att && (role == QAccessible::NoRole || att->role() == role)) {
+ object = action;
+ break;
+ }
+ }
object = object->parent();
}
return object;
@@ -154,6 +183,9 @@ public:
void availableActions(QStringList *actions) const;
Q_REVISION(6, 2) Q_INVOKABLE static QString stripHtml(const QString &html);
+ void setProxying(QQuickAccessibleAttached *proxying);
+
+ Q_REVISION(6, 8) Q_INVOKABLE void announce(const QString &message, QAccessible::AnnouncementPoliteness politeness = QAccessible::AnnouncementPoliteness::Polite);
public Q_SLOTS:
void valueChanged() {
@@ -171,6 +203,7 @@ Q_SIGNALS:
void roleChanged();
void nameChanged();
void descriptionChanged();
+ void idChanged();
void ignoredChanged();
void pressAction();
void toggleAction();
@@ -184,14 +217,15 @@ Q_SIGNALS:
void nextPageAction();
private:
- QQuickItem *item() const { return qobject_cast<QQuickItem*>(parent()); }
-
QAccessible::Role m_role;
QAccessible::State m_state;
QAccessible::State m_stateExplicitlySet;
QString m_name;
bool m_nameExplicitlySet = false;
QString m_description;
+ bool m_descriptionExplicitlySet = false;
+ QQuickAccessibleAttached* m_proxying = nullptr;
+ QString m_id;
static QMetaMethod sigPress;
static QMetaMethod sigToggle;
@@ -211,8 +245,6 @@ public:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickAccessibleAttached)
-
#endif // accessibility
#endif
diff --git a/src/quick/items/qquickanchors.cpp b/src/quick/items/qquickanchors.cpp
index dfb8ce0a18..e6de648313 100644
--- a/src/quick/items/qquickanchors.cpp
+++ b/src/quick/items/qquickanchors.cpp
@@ -49,11 +49,7 @@ static inline qreal hcenter(const QQuickItem *item)
if (!QQuickAnchorsPrivate::get(anchors)->centerAligned)
return width / 2;
}
- int iw = width;
- if (iw % 2)
- return (width + 1) / 2;
- else
- return width / 2;
+ return qRound(width / 2);
}
static inline qreal vcenter(const QQuickItem *item)
@@ -63,11 +59,7 @@ static inline qreal vcenter(const QQuickItem *item)
if (!QQuickAnchorsPrivate::get(anchors)->centerAligned)
return height / 2;
}
- int ih = height;
- if (ih % 2)
- return (height + 1) / 2;
- else
- return height / 2;
+ return qRound(height / 2);
}
//local position
diff --git a/src/quick/items/qquickanchors_p.h b/src/quick/items/qquickanchors_p.h
index 1409cfee06..f33e21c5bd 100644
--- a/src/quick/items/qquickanchors_p.h
+++ b/src/quick/items/qquickanchors_p.h
@@ -26,7 +26,7 @@ QT_BEGIN_NAMESPACE
class QQuickItem;
class QQuickAnchorsPrivate;
class QQuickAnchorLine;
-class Q_QUICK_PRIVATE_EXPORT QQuickAnchors : public QObject
+class Q_QUICK_EXPORT QQuickAnchors : public QObject
{
Q_OBJECT
@@ -180,6 +180,4 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickAnchors::Anchors)
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickAnchors)
-
#endif // QQUICKANCHORS_P_H
diff --git a/src/quick/items/qquickanchors_p_p.h b/src/quick/items/qquickanchors_p_p.h
index eebfc5c06b..12e6dc465a 100644
--- a/src/quick/items/qquickanchors_p_p.h
+++ b/src/quick/items/qquickanchors_p_p.h
@@ -21,7 +21,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QQuickAnchorLine
+class Q_QUICK_EXPORT QQuickAnchorLine
{
Q_GADGET
QML_ANONYMOUS
diff --git a/src/quick/items/qquickanimatedimage.cpp b/src/quick/items/qquickanimatedimage.cpp
index e4bc156a30..93f7dd8340 100644
--- a/src/quick/items/qquickanimatedimage.cpp
+++ b/src/quick/items/qquickanimatedimage.cpp
@@ -106,6 +106,26 @@ void QQuickAnimatedImagePrivate::clearCache()
with QQuickImageProvider.
*/
+/*!
+ \qmlproperty size QtQuick::AnimatedImage::sourceSize
+
+ This property holds the scaled width and height of the full-frame image.
+
+ Unlike the \l {Item::}{width} and \l {Item::}{height} properties, which scale
+ the painting of the image, this property sets the maximum number of pixels
+ stored for cached frames so that large animations do not use more
+ memory than necessary.
+
+ If the original size is larger than \c sourceSize, the image is scaled down.
+
+ The natural size of the image can be restored by setting this property to
+ \c undefined.
+
+ \note \e {Changing this property dynamically causes the image source to be reloaded,
+ potentially even from the network, if it is not in the disk cache.}
+
+ \sa Image::sourceSize
+*/
QQuickAnimatedImage::QQuickAnimatedImage(QQuickItem *parent)
: QQuickImage(*(new QQuickAnimatedImagePrivate), parent)
{
@@ -287,10 +307,7 @@ void QQuickAnimatedImage::load()
Q_D(QQuickAnimatedImage);
if (d->url.isEmpty()) {
- if (d->progress != 0) {
- d->progress = 0;
- emit progressChanged(d->progress);
- }
+ d->setProgress(0);
d->setImage(QImage());
if (sourceSize() != d->oldSourceSize) {
@@ -298,9 +315,7 @@ void QQuickAnimatedImage::load()
emit sourceSizeChanged();
}
- d->status = Null;
- emit statusChanged(d->status);
-
+ d->setStatus(Null);
if (isPlaying() != d->oldPlaying)
emit playingChanged();
} else {
@@ -313,19 +328,18 @@ void QQuickAnimatedImage::load()
resolve2xLocalFile(resolvedUrl, targetDevicePixelRatio, &loadUrl, &d->devicePixelRatio);
QString lf = QQmlFile::urlToLocalFileOrQrc(loadUrl);
+ d->status = Null; // reset status, no emit
+
if (!lf.isEmpty()) {
d->setMovie(new QMovie(lf));
movieRequestFinished();
} else {
#if QT_CONFIG(qml_network)
- if (d->status != Loading) {
- d->status = Loading;
- emit statusChanged(d->status);
- }
- if (d->progress != 0) {
- d->progress = 0;
- emit progressChanged(d->progress);
- }
+ if (d->reply)
+ return;
+
+ d->setStatus(Loading);
+ d->setProgress(0);
QNetworkRequest req(d->url);
req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
@@ -337,27 +351,21 @@ void QQuickAnimatedImage::load()
}
}
-#define ANIMATEDIMAGE_MAXIMUM_REDIRECT_RECURSION 16
-
void QQuickAnimatedImage::movieRequestFinished()
{
Q_D(QQuickAnimatedImage);
#if QT_CONFIG(qml_network)
if (d->reply) {
- d->redirectCount++;
- if (d->redirectCount < ANIMATEDIMAGE_MAXIMUM_REDIRECT_RECURSION) {
- QVariant redirect = d->reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
- if (redirect.isValid()) {
- QUrl url = d->reply->url().resolved(redirect.toUrl());
- d->reply->deleteLater();
- setSource(url);
- return;
- }
- }
+ auto movie = new QMovie(d->reply);
+ // From this point, we no longer need to handle the reply.
+ // I.e. it will be used only as a data source for QMovie,
+ // so it should live as long as the movie lives.
+ d->reply->disconnect(this);
+ d->reply->setParent(movie);
+ d->reply = nullptr;
- d->redirectCount=0;
- d->setMovie(new QMovie(d->reply));
+ d->setMovie(movie);
}
#endif
@@ -373,13 +381,8 @@ void QQuickAnimatedImage::movieRequestFinished()
emit sourceSizeChanged();
}
- if (d->progress != 0) {
- d->progress = 0;
- emit progressChanged(d->progress);
- }
-
- d->status = Error;
- emit statusChanged(d->status);
+ d->setProgress(0);
+ d->setStatus(Error);
if (isPlaying() != d->oldPlaying)
emit playingChanged();
@@ -392,10 +395,7 @@ void QQuickAnimatedImage::movieRequestFinished()
d->movie->setCacheMode(QMovie::CacheAll);
d->movie->setSpeed(qRound(d->speed * 100.0));
- if (d->progress != 1.0) {
- d->progress = 1.0;
- emit progressChanged(d->progress);
- }
+ d->setProgress(1);
bool pausedAtStart = d->paused;
if (d->movie && d->playing)
@@ -416,8 +416,7 @@ void QQuickAnimatedImage::movieRequestFinished()
}
}
- d->status = Ready;
- emit statusChanged(d->status);
+ d->setStatus(Ready);
if (isPlaying() != d->oldPlaying)
emit playingChanged();
diff --git a/src/quick/items/qquickanimatedimage_p.h b/src/quick/items/qquickanimatedimage_p.h
index 38beb6d1c3..4d8be4068a 100644
--- a/src/quick/items/qquickanimatedimage_p.h
+++ b/src/quick/items/qquickanimatedimage_p.h
@@ -26,7 +26,7 @@ QT_BEGIN_NAMESPACE
class QMovie;
class QQuickAnimatedImagePrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickAnimatedImage : public QQuickImage
+class Q_QUICK_EXPORT QQuickAnimatedImage : public QQuickImage
{
Q_OBJECT
@@ -85,6 +85,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickAnimatedImage)
-
#endif // QQUICKANIMATEDIMAGE_P_H
diff --git a/src/quick/items/qquickanimatedimage_p_p.h b/src/quick/items/qquickanimatedimage_p_p.h
index 2b01b4365d..1bd5729b93 100644
--- a/src/quick/items/qquickanimatedimage_p_p.h
+++ b/src/quick/items/qquickanimatedimage_p_p.h
@@ -34,11 +34,7 @@ class QQuickAnimatedImagePrivate : public QQuickImagePrivate
public:
QQuickAnimatedImagePrivate()
- : playing(true), paused(false), oldPlaying(false), padding(0)
- , presetCurrentFrame(0), speed(1.0), movie(nullptr)
-#if QT_CONFIG(qml_network)
- , reply(nullptr), redirectCount(0)
-#endif
+ : playing(true), paused(false), oldPlaying(false)
{
}
@@ -46,18 +42,18 @@ public:
void setMovie(QMovie *movie);
void clearCache();
+ qreal speed = 1;
+ QMovie *movie = nullptr;
+ int presetCurrentFrame = 0;
+ QMap<int, QQuickPixmap *> frameMap;
+
+#if QT_CONFIG(qml_network)
+ QNetworkReply *reply = nullptr;
+#endif
+
bool playing : 1;
bool paused : 1;
bool oldPlaying : 1;
- unsigned padding: 29;
- int presetCurrentFrame;
- qreal speed;
- QMovie *movie;
-#if QT_CONFIG(qml_network)
- QNetworkReply *reply;
- int redirectCount;
-#endif
- QMap<int, QQuickPixmap *> frameMap;
};
QT_END_NAMESPACE
diff --git a/src/quick/items/qquickanimatedsprite_p.h b/src/quick/items/qquickanimatedsprite_p.h
index 26358cd7ef..8f4c6ff7fc 100644
--- a/src/quick/items/qquickanimatedsprite_p.h
+++ b/src/quick/items/qquickanimatedsprite_p.h
@@ -31,7 +31,7 @@ class QQuickSpriteEngine;
class QSGGeometryNode;
class QQuickAnimatedSpritePrivate;
class QSGSpriteNode;
-class Q_QUICK_PRIVATE_EXPORT QQuickAnimatedSprite : public QQuickItem
+class Q_QUICK_EXPORT QQuickAnimatedSprite : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(bool running READ running WRITE setRunning NOTIFY runningChanged)
diff --git a/src/quick/items/qquickborderimage.cpp b/src/quick/items/qquickborderimage.cpp
index c8ebc59356..004f0491bd 100644
--- a/src/quick/items/qquickborderimage.cpp
+++ b/src/quick/items/qquickborderimage.cpp
@@ -63,9 +63,8 @@ QT_BEGIN_NAMESPACE
\image qml-borderimage-normal-image.png
\endfloat
- An unscaled image is displayed using an Image. The \l border property is
- used to determine the parts of the image that will lie inside the unscaled corner
- areas and the parts that will be stretched horizontally and vertically.
+ For comparison, an unscaled image is displayed using a simple Image item.
+ Here we have overlaid lines to show how we'd like to break it up with BorderImage:
\snippet qml/borderimage/normal-image.qml normal image
@@ -74,12 +73,15 @@ QT_BEGIN_NAMESPACE
\image qml-borderimage-scaled.png
\endfloat
- A BorderImage is used to display the image, and it is given a size that is
+ But when a BorderImage is used to display the image, the \l border property is
+ used to determine the parts of the image that will lie inside the unscaled corner
+ areas, and the parts that will be stretched horizontally and vertically.
+ Then, you can give it a size that is
larger than the original image. Since the \l horizontalTileMode property is set to
\l{BorderImage::horizontalTileMode}{BorderImage.Stretch}, the parts of image in
regions 2 and 8 are stretched horizontally. Since the \l verticalTileMode property
is set to \l{BorderImage::verticalTileMode}{BorderImage.Stretch}, the parts of image
- in regions 4 and 6 are stretched vertically.
+ in regions 4 and 6 are stretched vertically:
\snippet qml/borderimage/borderimage-scaled.qml scaled border image
@@ -92,17 +94,25 @@ QT_BEGIN_NAMESPACE
\l horizontalTileMode property set to \l{BorderImage::horizontalTileMode}{BorderImage.Repeat},
the parts of image in regions 2 and 8 are tiled so that they fill the space at the
top and bottom of the item. Similarly, the \l verticalTileMode property is set to
- \l{BorderImage::verticalTileMode}{BorderImage.Repeat}, the parts of image in regions
- 4 and 6 are tiled so that they fill the space at the left and right of the item.
+ \l{BorderImage::verticalTileMode}{BorderImage.Repeat}, so the parts of image in regions
+ 4 and 6 are tiled to fill the space at the left and right of the item:
\snippet qml/borderimage/borderimage-tiled.qml tiled border image
\clearfloat
+ \beginfloatleft
+ \image qml-borderimage-rounded.png
+ \endfloat
+
In some situations, the width of regions 2 and 8 may not be an exact multiple of the width
of the corresponding regions in the source image. Similarly, the height of regions 4 and 6
- may not be an exact multiple of the height of the corresponding regions. It can be useful
- to use \l{BorderImage::horizontalTileMode}{BorderImage.Round} instead of
- \l{BorderImage::horizontalTileMode}{BorderImage.Repeat} in cases like these.
+ may not be an exact multiple of the height of the corresponding regions. If you use
+ \l{BorderImage::horizontalTileMode}{BorderImage.Round} mode, it will choose an integer
+ number of tiles and shrink them to fit:
+
+ \snippet qml/borderimage/borderimage-rounded.qml tiled border image
+
+ \clearfloat
The Border Image example in \l{Qt Quick Examples - Image Elements} shows how a BorderImage
can be used to simulate a shadow effect on a rectangular item.
@@ -149,12 +159,10 @@ QQuickBorderImage::~QQuickBorderImage()
This property describes the status of image loading. It can be one of:
- \list
- \li BorderImage.Null - no image has been set
- \li BorderImage.Ready - the image has been loaded
- \li BorderImage.Loading - the image is currently being loaded
- \li BorderImage.Error - an error occurred while loading the image
- \endlist
+ \value BorderImage.Null No image has been set
+ \value BorderImage.Ready The image has been loaded
+ \value BorderImage.Loading The image is currently being loaded
+ \value BorderImage.Error An error occurred while loading the image
\sa progress
*/
@@ -268,20 +276,19 @@ void QQuickBorderImage::load()
: d->url);
if (!lf.isEmpty()) {
QFile file(lf);
- file.open(QIODevice::ReadOnly);
- setGridScaledImage(QQuickGridScaledImage(&file));
+ if (!file.open(QIODevice::ReadOnly))
+ d->setStatus(Error);
+ else
+ setGridScaledImage(QQuickGridScaledImage(&file));
} else {
#if QT_CONFIG(qml_network)
- if (d->progress != 0.0) {
- d->progress = 0.0;
- emit progressChanged(d->progress);
- }
- d->status = Loading;
+ d->setProgress(0);
+ d->setStatus(Loading);
+
QNetworkRequest req(d->url);
d->sciReply = qmlEngine(this)->networkAccessManager()->get(req);
qmlobject_connect(d->sciReply, QNetworkReply, SIGNAL(finished()),
this, QQuickBorderImage, SLOT(sciRequestFinished()));
- emit statusChanged(d->status);
#endif
}
} else {
@@ -332,11 +339,9 @@ QQuickScaleGrid *QQuickBorderImage::border()
This property describes how to repeat or stretch the middle parts of the border image.
- \list
- \li BorderImage.Stretch - Scales the image to fit to the available area.
- \li BorderImage.Repeat - Tile the image until there is no more space. May crop the last image.
- \li BorderImage.Round - Like Repeat, but scales the images down to ensure that the last image is not cropped.
- \endlist
+ \value BorderImage.Stretch Scales the image to fit to the available area.
+ \value BorderImage.Repeat Tile the image until there is no more space. May crop the last image.
+ \value BorderImage.Round Like Repeat, but scales the images down to ensure that the last image is not cropped.
The default tile mode for each property is BorderImage.Stretch.
*/
@@ -376,8 +381,7 @@ void QQuickBorderImage::setGridScaledImage(const QQuickGridScaledImage& sci)
{
Q_D(QQuickBorderImage);
if (!sci.isValid()) {
- d->status = Error;
- emit statusChanged(d->status);
+ d->setStatus(Error);
} else {
QQuickScaleGrid *sg = border();
sg->setTop(sci.gridTop());
@@ -396,30 +400,29 @@ void QQuickBorderImage::requestFinished()
{
Q_D(QQuickBorderImage);
- QSize impsize = d->pix.implicitSize();
- if (d->pix.isError()) {
- d->status = Error;
- qmlWarning(this) << d->pix.error();
- if (d->progress != 0) {
- d->progress = 0;
- emit progressChanged(d->progress);
- }
- } else {
- d->status = Ready;
- if (d->progress != 1.0) {
- d->progress = 1.0;
- emit progressChanged(d->progress);
- }
+ if (d->pendingPix != d->currentPix) {
+ std::swap(d->pendingPix, d->currentPix);
+ d->pendingPix->clear(this); // Clear the old image
}
+ const QSize impsize = d->currentPix->implicitSize();
setImplicitSize(impsize.width() / d->devicePixelRatio, impsize.height() / d->devicePixelRatio);
- emit statusChanged(d->status);
+
+ if (d->currentPix->isError()) {
+ qmlWarning(this) << d->currentPix->error();
+ d->setStatus(Error);
+ d->setProgress(0);
+ } else {
+ d->setStatus(Ready);
+ d->setProgress(1);
+ }
+
if (sourceSize() != d->oldSourceSize) {
d->oldSourceSize = sourceSize();
emit sourceSizeChanged();
}
- if (d->frameCount != d->pix.frameCount()) {
- d->frameCount = d->pix.frameCount();
+ if (d->frameCount != d->currentPix->frameCount()) {
+ d->frameCount = d->currentPix->frameCount();
emit frameCountChanged();
}
@@ -427,28 +430,14 @@ void QQuickBorderImage::requestFinished()
}
#if QT_CONFIG(qml_network)
-#define BORDERIMAGE_MAX_REDIRECT 16
-
void QQuickBorderImage::sciRequestFinished()
{
Q_D(QQuickBorderImage);
- d->redirectCount++;
- if (d->redirectCount < BORDERIMAGE_MAX_REDIRECT) {
- QVariant redirect = d->sciReply->attribute(QNetworkRequest::RedirectionTargetAttribute);
- if (redirect.isValid()) {
- QUrl url = d->sciReply->url().resolved(redirect.toUrl());
- setSource(url);
- return;
- }
- }
- d->redirectCount=0;
-
if (d->sciReply->error() != QNetworkReply::NoError) {
- d->status = Error;
+ d->setStatus(Error);
d->sciReply->deleteLater();
d->sciReply = nullptr;
- emit statusChanged(d->status);
} else {
QQuickGridScaledImage sci(d->sciReply);
d->sciReply->deleteLater();
@@ -523,7 +512,7 @@ QSGNode *QQuickBorderImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeDat
{
Q_D(QQuickBorderImage);
- QSGTexture *texture = d->sceneGraphRenderContext()->textureForFactory(d->pix.textureFactory(), window());
+ QSGTexture *texture = d->sceneGraphRenderContext()->textureForFactory(d->currentPix->textureFactory(), window());
if (!texture || width() <= 0 || height() <= 0) {
delete oldNode;
@@ -548,7 +537,7 @@ QSGNode *QQuickBorderImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeDat
QRectF innerSourceRect;
QRectF subSourceRect;
d->calculateRects(d->border,
- QSize(d->pix.width(), d->pix.height()), QSizeF(width(), height()),
+ QSize(d->currentPix->width(), d->currentPix->height()), QSizeF(width(), height()),
d->horizontalTileMode, d->verticalTileMode, d->devicePixelRatio,
&targetRect, &innerTargetRect,
&innerSourceRect, &subSourceRect);
@@ -593,6 +582,13 @@ void QQuickBorderImage::pixmapChange()
frameCount is the number of frames in the image. Most images have only one frame.
*/
+/*!
+ \qmlproperty bool QtQuick::BorderImage::retainWhileLoading
+ \since 6.8
+
+ \include qquickimage.cpp qml-image-retainwhileloading
+ */
+
QT_END_NAMESPACE
#include "moc_qquickborderimage_p.cpp"
diff --git a/src/quick/items/qquickborderimage_p.h b/src/quick/items/qquickborderimage_p.h
index 92dd90b654..3c87955189 100644
--- a/src/quick/items/qquickborderimage_p.h
+++ b/src/quick/items/qquickborderimage_p.h
@@ -22,7 +22,7 @@ QT_BEGIN_NAMESPACE
class QQuickScaleGrid;
class QQuickGridScaledImage;
class QQuickBorderImagePrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickBorderImage : public QQuickImageBase
+class Q_QUICK_EXPORT QQuickBorderImage : public QQuickImageBase
{
Q_OBJECT
@@ -77,6 +77,5 @@ private:
};
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickBorderImage)
#endif // QQUICKBORDERIMAGE_P_H
diff --git a/src/quick/items/qquickborderimage_p_p.h b/src/quick/items/qquickborderimage_p_p.h
index 82867d9385..02d5ac9067 100644
--- a/src/quick/items/qquickborderimage_p_p.h
+++ b/src/quick/items/qquickborderimage_p_p.h
@@ -31,11 +31,6 @@ class QQuickBorderImagePrivate : public QQuickImageBasePrivate
public:
QQuickBorderImagePrivate()
- : border(0), horizontalTileMode(QQuickBorderImage::Stretch),
- verticalTileMode(QQuickBorderImage::Stretch), pixmapChanged(false)
-#if QT_CONFIG(qml_network)
- , sciReply(0), redirectCount(0)
-#endif
{
}
@@ -43,7 +38,6 @@ public:
{
}
-
QQuickScaleGrid *getScaleGrid()
{
Q_Q(QQuickBorderImage);
@@ -66,15 +60,14 @@ public:
QRectF *innerSourceRect,
QRectF *subSourceRect);
- QQuickScaleGrid *border;
QUrl sciurl;
- QQuickBorderImage::TileMode horizontalTileMode;
- QQuickBorderImage::TileMode verticalTileMode;
- bool pixmapChanged : 1;
+ QQuickScaleGrid *border = nullptr;
+ QQuickBorderImage::TileMode horizontalTileMode = QQuickBorderImage::Stretch;
+ QQuickBorderImage::TileMode verticalTileMode = QQuickBorderImage::Stretch;
+ bool pixmapChanged = false;
#if QT_CONFIG(qml_network)
- QNetworkReply *sciReply;
- int redirectCount;
+ QNetworkReply *sciReply = nullptr;
#endif
};
diff --git a/src/quick/items/qquickclipnode_p.h b/src/quick/items/qquickclipnode_p.h
index 953d0da08b..37b1b1778f 100644
--- a/src/quick/items/qquickclipnode_p.h
+++ b/src/quick/items/qquickclipnode_p.h
@@ -20,7 +20,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QQuickDefaultClipNode : public QSGClipNode
+class Q_QUICK_EXPORT QQuickDefaultClipNode : public QSGClipNode
{
public:
QQuickDefaultClipNode(const QRectF &);
diff --git a/src/quick/items/qquickcolorgroup.cpp b/src/quick/items/qquickcolorgroup.cpp
index fd913aba7a..bc1b4dee5a 100644
--- a/src/quick/items/qquickcolorgroup.cpp
+++ b/src/quick/items/qquickcolorgroup.cpp
@@ -45,9 +45,21 @@ QT_BEGIN_NAMESPACE
base: "green"
}
\endcode
+
+ The \l Palette type exposes color groups for each QML item state.
*/
/*!
+ \qmlproperty color QtQuick::ColorGroup::accent
+ \since 6.6
+
+ A color that typically contrasts or compliments \l base, \l window, and \l
+ button colors. It usually represents the users' choice of desktop
+ personalisation. Styling of interactive components is a typical use case.
+ Unless explicitly set, it defaults to \l highlight.
+*/
+
+/*!
\qmlproperty color QtQuick::ColorGroup::alternateBase
Used as the alternate background color in item views with alternating row colors.
@@ -180,8 +192,6 @@ QT_BEGIN_NAMESPACE
Additional signal indicates that the current state of this color group
has been changed. Usually it means that one of the colors is changed.
-
- \sa Palette
*/
/*!
@@ -499,6 +509,21 @@ void QQuickColorGroup::resetPlaceholderText()
resetColor(QPalette::PlaceholderText, &QQuickColorGroup::placeholderTextChanged);
}
+QColor QQuickColorGroup::accent() const
+{
+ return color(QPalette::Accent);
+}
+
+void QQuickColorGroup::setAccent(const QColor &color)
+{
+ setColor(QPalette::Accent, color, &QQuickColorGroup::accentChanged);
+}
+
+void QQuickColorGroup::resetAccent()
+{
+ resetColor(QPalette::Accent, &QQuickColorGroup::accentChanged);
+}
+
QPalette::ColorGroup QQuickColorGroup::groupTag() const
{
return m_groupTag;
diff --git a/src/quick/items/qquickcolorgroup_p.h b/src/quick/items/qquickcolorgroup_p.h
index 54c883e1c7..202bba7ee1 100644
--- a/src/quick/items/qquickcolorgroup_p.h
+++ b/src/quick/items/qquickcolorgroup_p.h
@@ -22,12 +22,14 @@
#include <QtQml/qqml.h>
+#include <QtCore/qpointer.h>
+
QT_BEGIN_NAMESPACE
class QQuickPalette;
class QQuickPaletteColorProvider;
-class Q_QUICK_PRIVATE_EXPORT QQuickColorGroup : public QObject
+class Q_QUICK_EXPORT QQuickColorGroup : public QObject
{
Q_OBJECT
@@ -52,6 +54,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickColorGroup : public QObject
Q_PROPERTY(QColor windowText READ windowText WRITE setWindowText RESET resetWindowText NOTIFY windowTextChanged FINAL)
Q_PROPERTY(QColor placeholderText READ placeholderText WRITE setPlaceholderText
RESET resetPlaceholderText NOTIFY placeholderTextChanged REVISION(6, 2) FINAL)
+ Q_PROPERTY(QColor accent READ accent WRITE setAccent RESET resetAccent NOTIFY accentChanged REVISION(6, 6) FINAL)
QML_NAMED_ELEMENT(ColorGroup)
QML_ADDED_IN_VERSION(6, 0)
@@ -144,6 +147,10 @@ public:
void setPlaceholderText(const QColor &color);
void resetPlaceholderText();
+ QColor accent() const;
+ void setAccent(const QColor &color);
+ void resetAccent();
+
QPalette::ColorGroup groupTag() const;
void setGroupTag(QPalette::ColorGroup tag);
@@ -173,6 +180,7 @@ Q_SIGNALS:
void windowChanged();
void windowTextChanged();
Q_REVISION(6, 2) void placeholderTextChanged();
+ Q_REVISION(6, 6) void accentChanged();
void changed();
@@ -197,6 +205,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickColorGroup)
-
#endif // QQUICKCOLORGROUP_H
diff --git a/src/quick/items/qquickdrag.cpp b/src/quick/items/qquickdrag.cpp
index bdca024b1d..47064ad433 100644
--- a/src/quick/items/qquickdrag.cpp
+++ b/src/quick/items/qquickdrag.cpp
@@ -9,7 +9,7 @@
#include <private/qquickitem_p.h>
#include <QtQuick/private/qquickevents_p_p.h>
#include <private/qquickitemchangelistener_p.h>
-#include <private/qquickpixmapcache_p.h>
+#include <private/qquickpixmap_p.h>
#include <private/qv4scopedvalue_p.h>
#include <QtCore/qbuffer.h>
#include <QtCore/qmimedata.h>
@@ -55,7 +55,7 @@ using namespace Qt::StringLiterals;
\l {supportedActions}{drop action} chosen by the recipient of the event,
otherwise it will return Qt.IgnoreAction.
- \sa {Qt Quick Examples - Drag and Drop}, {Qt Quick Examples - externaldraganddrop}
+ \sa {Qt Quick Examples - Drag and Drop}
*/
void QQuickDragAttachedPrivate::itemGeometryChanged(QQuickItem *, QQuickGeometryChange change,
@@ -354,7 +354,7 @@ void QQuickDragAttached::setImageSource(const QUrl &url)
if (url.isEmpty()) {
d->pixmapLoader.clear();
} else {
- d->pixmapLoader.load(qmlEngine(parent()), url);
+ d->loadPixmap();
}
Q_EMIT imageSourceChanged();
@@ -362,6 +362,48 @@ void QQuickDragAttached::setImageSource(const QUrl &url)
}
/*!
+ \qmlattachedproperty size QtQuick::Drag::imageSourceSize
+ \since 6.8
+
+ This property holds the size of the image that will be used to represent
+ the data during the drag and drop operation. Changing this property after
+ the drag operation has started will have no effect.
+
+ This property sets the maximum number of pixels stored for the loaded
+ image so that large images do not use more memory than necessary.
+ See \l {QtQuick::Image::sourceSize}{Image.sourceSize} for more details.
+
+ The example below shows an SVG image rendered at one size, and re-renders
+ it at a different size for the drag image:
+
+ \snippet qml/externalDragScaledImage.qml 0
+
+ \sa imageSource, Item::grabToImage()
+*/
+
+QSize QQuickDragAttached::imageSourceSize() const
+{
+ Q_D(const QQuickDragAttached);
+ int width = d->imageSourceSize.width();
+ int height = d->imageSourceSize.height();
+ return QSize(width != -1 ? width : d->pixmapLoader.width(),
+ height != -1 ? height : d->pixmapLoader.height());
+}
+
+void QQuickDragAttached::setImageSourceSize(const QSize &size)
+{
+ Q_D(QQuickDragAttached);
+ if (d->imageSourceSize != size) {
+ d->imageSourceSize = size;
+
+ if (!d->imageSource.isEmpty())
+ d->loadPixmap();
+
+ Q_EMIT imageSourceSizeChanged();
+ }
+}
+
+/*!
\qmlattachedproperty stringlist QtQuick::Drag::keys
This property holds a list of keys that can be used by a DropArea to filter drag events.
@@ -477,11 +519,9 @@ void QQuickDragAttached::setProposedAction(Qt::DropAction action)
A drag can also be started manually using \l startDrag.
- \list
- \li Drag.None - do not start drags automatically
- \li Drag.Automatic - start drags automatically
- \li Drag.Internal (default) - start backwards compatible drags automatically
- \endlist
+ \value Drag.None do not start drags automatically
+ \value Drag.Automatic start drags automatically
+ \value Drag.Internal (default) start backwards compatible drags automatically
When using \c Drag.Automatic you should also define \l mimeData and bind the
\l active property to the active property of MouseArea : \l {MouseArea::drag.active}
@@ -540,7 +580,7 @@ void QQuickDragAttachedPrivate::start(Qt::DropActions supportedActions)
property for the started sequence.
*/
-void QQuickDragAttached::start(QQmlV4Function *args)
+void QQuickDragAttached::start(QQmlV4FunctionPtr args)
{
Q_D(QQuickDragAttached);
if (d->inEvent) {
@@ -576,12 +616,10 @@ void QQuickDragAttached::start(QQmlV4Function *args)
The returned drop action may be one of:
- \list
- \li Qt.CopyAction Copy the data to the target
- \li Qt.MoveAction Move the data from the source to the target
- \li Qt.LinkAction Create a link from the source to the target.
- \li Qt.IgnoreAction Ignore the action (do nothing with the data).
- \endlist
+ \value Qt.CopyAction Copy the data to the target
+ \value Qt.MoveAction Move the data from the source to the target
+ \value Qt.LinkAction Create a link from the source to the target.
+ \value Qt.IgnoreAction Ignore the action (do nothing with the data).
*/
int QQuickDragAttached::drop()
@@ -705,11 +743,10 @@ QMimeData *QQuickDragAttachedPrivate::createMimeData() const
else
qmlWarning(q) << "Don't know how to encode text as " << mimeType;
} else {
- mimeData->setData(mimeType, text.toUtf8().constData());
+ mimeData->setData(mimeType, text.toUtf8());
}
} else {
- qmlWarning(q) << "Mime data contains a string, but mime type " << mimeType
- << " is not a supported text type";
+ mimeData->setData(mimeType, text.toUtf8());
}
break;
}
@@ -765,6 +802,17 @@ QMimeData *QQuickDragAttachedPrivate::createMimeData() const
return mimeData;
}
+void QQuickDragAttachedPrivate::loadPixmap()
+{
+ Q_Q(QQuickDragAttached);
+
+ QUrl loadUrl = imageSource;
+ const QQmlContext *context = qmlContext(q->parent());
+ if (context)
+ loadUrl = context->resolvedUrl(imageSource);
+ pixmapLoader.load(context ? context->engine() : nullptr, loadUrl, QRect(), q->imageSourceSize());
+}
+
Qt::DropAction QQuickDragAttachedPrivate::startDrag(Qt::DropActions supportedActions)
{
Q_Q(QQuickDragAttached);
@@ -808,7 +856,7 @@ Qt::DropAction QQuickDragAttachedPrivate::startDrag(Qt::DropActions supportedAct
property for the started sequence.
*/
-void QQuickDragAttached::startDrag(QQmlV4Function *args)
+void QQuickDragAttached::startDrag(QQmlV4FunctionPtr args)
{
Q_D(QQuickDragAttached);
diff --git a/src/quick/items/qquickdrag_p.h b/src/quick/items/qquickdrag_p.h
index 806dbc7602..fa73e91485 100644
--- a/src/quick/items/qquickdrag_p.h
+++ b/src/quick/items/qquickdrag_p.h
@@ -119,24 +119,23 @@ private:
friend class QQuickDragAttachedPrivate;
};
-class QQmlV4Function;
class QQuickDragAttached;
-class Q_QUICK_PRIVATE_EXPORT QQuickDrag : public QObject
+class Q_QUICK_EXPORT QQuickDrag : public QObject
{
Q_OBJECT
- Q_PROPERTY(QQuickItem *target READ target WRITE setTarget NOTIFY targetChanged RESET resetTarget)
- Q_PROPERTY(Axis axis READ axis WRITE setAxis NOTIFY axisChanged)
- Q_PROPERTY(qreal minimumX READ xmin WRITE setXmin NOTIFY minimumXChanged)
- Q_PROPERTY(qreal maximumX READ xmax WRITE setXmax NOTIFY maximumXChanged)
- Q_PROPERTY(qreal minimumY READ ymin WRITE setYmin NOTIFY minimumYChanged)
- Q_PROPERTY(qreal maximumY READ ymax WRITE setYmax NOTIFY maximumYChanged)
- Q_PROPERTY(bool active READ active NOTIFY activeChanged)
- Q_PROPERTY(bool filterChildren READ filterChildren WRITE setFilterChildren NOTIFY filterChildrenChanged)
- Q_PROPERTY(bool smoothed READ smoothed WRITE setSmoothed NOTIFY smoothedChanged)
+ Q_PROPERTY(QQuickItem *target READ target WRITE setTarget NOTIFY targetChanged RESET resetTarget FINAL)
+ Q_PROPERTY(Axis axis READ axis WRITE setAxis NOTIFY axisChanged FINAL FINAL)
+ Q_PROPERTY(qreal minimumX READ xmin WRITE setXmin NOTIFY minimumXChanged FINAL)
+ Q_PROPERTY(qreal maximumX READ xmax WRITE setXmax NOTIFY maximumXChanged FINAL)
+ Q_PROPERTY(qreal minimumY READ ymin WRITE setYmin NOTIFY minimumYChanged FINAL)
+ Q_PROPERTY(qreal maximumY READ ymax WRITE setYmax NOTIFY maximumYChanged FINAL)
+ Q_PROPERTY(bool active READ active NOTIFY activeChanged FINAL)
+ Q_PROPERTY(bool filterChildren READ filterChildren WRITE setFilterChildren NOTIFY filterChildrenChanged FINAL)
+ Q_PROPERTY(bool smoothed READ smoothed WRITE setSmoothed NOTIFY smoothedChanged FINAL)
// Note, threshold was added in QtQuick 2.2 but REVISION is not supported (or needed) for grouped
// properties See QTBUG-33179
- Q_PROPERTY(qreal threshold READ threshold WRITE setThreshold NOTIFY thresholdChanged RESET resetThreshold)
+ Q_PROPERTY(qreal threshold READ threshold WRITE setThreshold NOTIFY thresholdChanged RESET resetThreshold FINAL)
//### consider drag and drop
QML_NAMED_ELEMENT(Drag)
@@ -211,21 +210,23 @@ private:
};
class QQuickDragAttachedPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickDragAttached : public QObject
+class Q_QUICK_EXPORT QQuickDragAttached : public QObject
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickDragAttached)
- Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged)
- Q_PROPERTY(QObject *source READ source WRITE setSource NOTIFY sourceChanged RESET resetSource)
- Q_PROPERTY(QObject *target READ target NOTIFY targetChanged)
- Q_PROPERTY(QPointF hotSpot READ hotSpot WRITE setHotSpot NOTIFY hotSpotChanged)
- Q_PROPERTY(QUrl imageSource READ imageSource WRITE setImageSource NOTIFY imageSourceChanged)
- Q_PROPERTY(QStringList keys READ keys WRITE setKeys NOTIFY keysChanged)
- Q_PROPERTY(QVariantMap mimeData READ mimeData WRITE setMimeData NOTIFY mimeDataChanged)
- Q_PROPERTY(Qt::DropActions supportedActions READ supportedActions WRITE setSupportedActions NOTIFY supportedActionsChanged)
- Q_PROPERTY(Qt::DropAction proposedAction READ proposedAction WRITE setProposedAction NOTIFY proposedActionChanged)
- Q_PROPERTY(QQuickDrag::DragType dragType READ dragType WRITE setDragType NOTIFY dragTypeChanged)
+ Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged FINAL)
+ Q_PROPERTY(QObject *source READ source WRITE setSource NOTIFY sourceChanged RESET resetSource FINAL)
+ Q_PROPERTY(QObject *target READ target NOTIFY targetChanged FINAL)
+ Q_PROPERTY(QPointF hotSpot READ hotSpot WRITE setHotSpot NOTIFY hotSpotChanged FINAL)
+ Q_PROPERTY(QUrl imageSource READ imageSource WRITE setImageSource NOTIFY imageSourceChanged FINAL)
+ // imageSourceSize is new in Qt 6.8; revision omitted because of QTBUG-33179
+ Q_PROPERTY(QSize imageSourceSize READ imageSourceSize WRITE setImageSourceSize NOTIFY imageSourceSizeChanged FINAL)
+ Q_PROPERTY(QStringList keys READ keys WRITE setKeys NOTIFY keysChanged FINAL)
+ Q_PROPERTY(QVariantMap mimeData READ mimeData WRITE setMimeData NOTIFY mimeDataChanged FINAL)
+ Q_PROPERTY(Qt::DropActions supportedActions READ supportedActions WRITE setSupportedActions NOTIFY supportedActionsChanged FINAL)
+ Q_PROPERTY(Qt::DropAction proposedAction READ proposedAction WRITE setProposedAction NOTIFY proposedActionChanged FINAL)
+ Q_PROPERTY(QQuickDrag::DragType dragType READ dragType WRITE setDragType NOTIFY dragTypeChanged FINAL)
QML_ANONYMOUS
QML_ADDED_IN_VERSION(2, 0)
@@ -249,6 +250,9 @@ public:
QUrl imageSource() const;
void setImageSource(const QUrl &url);
+ QSize imageSourceSize() const;
+ void setImageSourceSize(const QSize &size);
+
QStringList keys() const;
void setKeys(const QStringList &keys);
@@ -269,8 +273,8 @@ public:
bool event(QEvent *event) override;
public Q_SLOTS:
- void start(QQmlV4Function *);
- void startDrag(QQmlV4Function *);
+ void start(QQmlV4FunctionPtr);
+ void startDrag(QQmlV4FunctionPtr);
void cancel();
Q_SIGNALS:
@@ -282,6 +286,7 @@ Q_SIGNALS:
void targetChanged();
void hotSpotChanged();
void imageSourceChanged();
+ void imageSourceSizeChanged(); // new in Qt 6.8
void keysChanged();
void mimeDataChanged();
void supportedActionsChanged();
@@ -291,6 +296,4 @@ Q_SIGNALS:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickDrag)
-
#endif
diff --git a/src/quick/items/qquickdrag_p_p.h b/src/quick/items/qquickdrag_p_p.h
index 3cb122dbcc..657dfefb1d 100644
--- a/src/quick/items/qquickdrag_p_p.h
+++ b/src/quick/items/qquickdrag_p_p.h
@@ -25,7 +25,7 @@
#include <QtQuick/qquickitem.h>
#include <QtQuick/private/qquickitemchangelistener_p.h>
-#include <QtQuick/private/qquickpixmapcache_p.h>
+#include <QtQuick/private/qquickpixmap_p.h>
QT_BEGIN_NAMESPACE
@@ -67,6 +67,7 @@ public:
Qt::DropAction startDrag(Qt::DropActions supportedActions);
void setTarget(QQuickItem *item);
QMimeData *createMimeData() const;
+ void loadPixmap();
QQuickDragGrabber dragGrabber;
@@ -86,6 +87,7 @@ public:
bool overrideActions : 1;
QPointF hotSpot;
QUrl imageSource;
+ QSize imageSourceSize;
QQuickPixmap pixmapLoader;
QStringList keys;
QVariantMap externalMimeData;
diff --git a/src/quick/items/qquickdroparea.cpp b/src/quick/items/qquickdroparea.cpp
index 1c865a6327..2538e0e238 100644
--- a/src/quick/items/qquickdroparea.cpp
+++ b/src/quick/items/qquickdroparea.cpp
@@ -7,6 +7,7 @@
#include <private/qv4arraybuffer_p.h>
+#include <QtCore/qpointer.h>
#include <QtCore/qregularexpression.h>
QT_BEGIN_NAMESPACE
@@ -72,7 +73,7 @@ QQuickDropAreaPrivate::~QQuickDropAreaPrivate()
The \l drag.source property is communicated to the source of a drag event as
the recipient of a drop on the drag target.
- \sa {Qt Quick Examples - Drag and Drop}, {Qt Quick Examples - externaldraganddrop}
+ \sa {Qt Quick Examples - Drag and Drop}
*/
QQuickDropArea::QQuickDropArea(QQuickItem *parent)
@@ -353,12 +354,10 @@ void QQuickDropArea::dropEvent(QDropEvent *event)
The drop action may be one of:
- \list
- \li Qt.CopyAction Copy the data to the target.
- \li Qt.MoveAction Move the data from the source to the target.
- \li Qt.LinkAction Create a link from the source to the target.
- \li Qt.IgnoreAction Ignore the action (do nothing with the data).
- \endlist
+ \value Qt.CopyAction Copy the data to the target.
+ \value Qt.MoveAction Move the data from the source to the target.
+ \value Qt.LinkAction Create a link from the source to the target.
+ \value Qt.IgnoreAction Ignore the action (do nothing with the data).
*/
/*!
diff --git a/src/quick/items/qquickdroparea_p.h b/src/quick/items/qquickdroparea_p.h
index 35d295f5aa..e9fff0afa0 100644
--- a/src/quick/items/qquickdroparea_p.h
+++ b/src/quick/items/qquickdroparea_p.h
@@ -29,23 +29,23 @@ class QQuickDropAreaPrivate;
class QQuickDragEvent : public QObject
{
Q_OBJECT
- Q_PROPERTY(qreal x READ x)
- Q_PROPERTY(qreal y READ y)
- Q_PROPERTY(QObject *source READ source)
- Q_PROPERTY(QStringList keys READ keys)
- Q_PROPERTY(Qt::DropActions supportedActions READ supportedActions)
- Q_PROPERTY(Qt::DropActions proposedAction READ proposedAction)
- Q_PROPERTY(Qt::DropAction action READ action WRITE setAction RESET resetAction)
- Q_PROPERTY(bool accepted READ accepted WRITE setAccepted)
- Q_PROPERTY(bool hasColor READ hasColor)
- Q_PROPERTY(bool hasHtml READ hasHtml)
- Q_PROPERTY(bool hasText READ hasText)
- Q_PROPERTY(bool hasUrls READ hasUrls)
- Q_PROPERTY(QVariant colorData READ colorData)
- Q_PROPERTY(QString html READ html)
- Q_PROPERTY(QString text READ text)
- Q_PROPERTY(QList<QUrl> urls READ urls)
- Q_PROPERTY(QStringList formats READ formats)
+ Q_PROPERTY(qreal x READ x FINAL)
+ Q_PROPERTY(qreal y READ y FINAL)
+ Q_PROPERTY(QObject *source READ source FINAL)
+ Q_PROPERTY(QStringList keys READ keys FINAL)
+ Q_PROPERTY(Qt::DropActions supportedActions READ supportedActions FINAL)
+ Q_PROPERTY(Qt::DropActions proposedAction READ proposedAction FINAL)
+ Q_PROPERTY(Qt::DropAction action READ action WRITE setAction RESET resetAction FINAL)
+ Q_PROPERTY(bool accepted READ accepted WRITE setAccepted FINAL)
+ Q_PROPERTY(bool hasColor READ hasColor FINAL)
+ Q_PROPERTY(bool hasHtml READ hasHtml FINAL)
+ Q_PROPERTY(bool hasText READ hasText FINAL)
+ Q_PROPERTY(bool hasUrls READ hasUrls FINAL)
+ Q_PROPERTY(QVariant colorData READ colorData FINAL)
+ Q_PROPERTY(QString html READ html FINAL)
+ Q_PROPERTY(QString text READ text FINAL)
+ Q_PROPERTY(QList<QUrl> urls READ urls FINAL)
+ Q_PROPERTY(QStringList formats READ formats FINAL)
QML_NAMED_ELEMENT(DragEvent)
QML_UNCREATABLE("DragEvent is only meant to be created by DropArea")
QML_ADDED_IN_VERSION(2, 0)
@@ -92,9 +92,9 @@ private:
class QQuickDropAreaDrag : public QObject
{
Q_OBJECT
- Q_PROPERTY(qreal x READ x NOTIFY positionChanged)
- Q_PROPERTY(qreal y READ y NOTIFY positionChanged)
- Q_PROPERTY(QObject *source READ source NOTIFY sourceChanged)
+ Q_PROPERTY(qreal x READ x NOTIFY positionChanged FINAL)
+ Q_PROPERTY(qreal y READ y NOTIFY positionChanged FINAL)
+ Q_PROPERTY(QObject *source READ source NOTIFY sourceChanged FINAL)
QML_ANONYMOUS
QML_ADDED_IN_VERSION(2, 0)
public:
@@ -117,7 +117,7 @@ private:
};
class QQuickDropAreaPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickDropArea : public QQuickItem
+class Q_QUICK_EXPORT QQuickDropArea : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(bool containsDrag READ containsDrag NOTIFY containsDragChanged)
@@ -161,6 +161,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickDropArea)
-
#endif // QQUICKDROPAREA_P_H
diff --git a/src/quick/items/qquickevents.cpp b/src/quick/items/qquickevents.cpp
index 5c722df4fe..d6d012a021 100644
--- a/src/quick/items/qquickevents.cpp
+++ b/src/quick/items/qquickevents.cpp
@@ -97,16 +97,15 @@ Item {
This property holds the keyboard modifier flags that existed immediately
before the event occurred.
- It contains a bitwise combination of:
- \list
- \li \l {Qt::NoModifier} {Qt.NoModifier} - No modifier key is pressed.
- \li \l {Qt::ShiftModifier} {Qt.ShiftModifier} - A Shift key on the keyboard is pressed.
- \li \l {Qt::ControlModifier} {Qt.ControlModifier} - A Ctrl key on the keyboard is pressed.
- \li \l {Qt::AltModifier} {Qt.AltModifier} - An Alt key on the keyboard is pressed.
- \li \l {Qt::MetaModifier} {Qt.MetaModifier} - A Meta key on the keyboard is pressed.
- \li \l {Qt::KeypadModifier} {Qt.KeypadModifier} - A keypad button is pressed.
- \li \l {Qt::GroupSwitchModifier} {Qt.GroupSwitchModifier} - X11 only. A Mode_switch key on the keyboard is pressed.
- \endlist
+ It contains a bitwise combination of numeric values (the same as in Qt::KeyboardModifier):
+
+ \value Qt.NoModifier No modifier key is pressed.
+ \value Qt.ShiftModifier} A Shift key on the keyboard is pressed.
+ \value Qt.ControlModifier A Ctrl key on the keyboard is pressed.
+ \value Qt.AltModifier An Alt key on the keyboard is pressed.
+ \value Qt.MetaModifier A Meta key on the keyboard is pressed.
+ \value Qt.KeypadModifier A keypad button is pressed.
+ \value Qt.GroupSwitchModifier X11 only. A Mode_switch key on the keyboard is pressed.
For example, to react to a Shift key + Enter key combination:
\qml
diff --git a/src/quick/items/qquickevents_p_p.h b/src/quick/items/qquickevents_p_p.h
index 24f3cef6c9..f82905333b 100644
--- a/src/quick/items/qquickevents_p_p.h
+++ b/src/quick/items/qquickevents_p_p.h
@@ -36,17 +36,18 @@ class QPointerEvent;
class QMouseEvent;
class QQuickPointerHandler;
-class Q_QUICK_PRIVATE_EXPORT QQuickKeyEvent : public QObject
+class Q_QUICK_EXPORT QQuickKeyEvent : public QObject
{
Q_OBJECT
- Q_PROPERTY(int key READ key CONSTANT)
- Q_PROPERTY(QString text READ text CONSTANT)
- Q_PROPERTY(int modifiers READ modifiers CONSTANT)
- Q_PROPERTY(bool isAutoRepeat READ isAutoRepeat CONSTANT)
- Q_PROPERTY(int count READ count CONSTANT)
- Q_PROPERTY(quint32 nativeScanCode READ nativeScanCode CONSTANT)
- Q_PROPERTY(bool accepted READ isAccepted WRITE setAccepted)
- QML_ANONYMOUS
+ Q_PROPERTY(int key READ key CONSTANT FINAL)
+ Q_PROPERTY(QString text READ text CONSTANT FINAL)
+ Q_PROPERTY(int modifiers READ modifiers CONSTANT FINAL)
+ Q_PROPERTY(bool isAutoRepeat READ isAutoRepeat CONSTANT FINAL)
+ Q_PROPERTY(int count READ count CONSTANT FINAL)
+ Q_PROPERTY(quint32 nativeScanCode READ nativeScanCode CONSTANT FINAL)
+ Q_PROPERTY(bool accepted READ isAccepted WRITE setAccepted FINAL)
+ QML_NAMED_ELEMENT(KeyEvent)
+ QML_UNCREATABLE("Should only be used by signal handlers in the Keys attached property")
QML_ADDED_IN_VERSION(2, 0)
public:
@@ -102,21 +103,23 @@ private:
bool m_autoRepeat = false;
};
-// used in Qt Location
-class Q_QUICK_PRIVATE_EXPORT QQuickMouseEvent : public QObject
+class Q_QUICK_EXPORT QQuickMouseEvent : public QObject
{
Q_OBJECT
- Q_PROPERTY(qreal x READ x CONSTANT)
- Q_PROPERTY(qreal y READ y CONSTANT)
- Q_PROPERTY(int button READ button CONSTANT)
- Q_PROPERTY(int buttons READ buttons CONSTANT)
- Q_PROPERTY(int modifiers READ modifiers CONSTANT)
- Q_PROPERTY(int source READ source CONSTANT REVISION(2, 7))
- Q_PROPERTY(bool wasHeld READ wasHeld CONSTANT)
- Q_PROPERTY(bool isClick READ isClick CONSTANT)
- Q_PROPERTY(bool accepted READ isAccepted WRITE setAccepted)
- Q_PROPERTY(int flags READ flags CONSTANT REVISION(2, 11))
- QML_ANONYMOUS
+ Q_PROPERTY(qreal x READ x CONSTANT FINAL)
+ Q_PROPERTY(qreal y READ y CONSTANT FINAL)
+ Q_PROPERTY(int button READ button CONSTANT FINAL)
+ Q_PROPERTY(int buttons READ buttons CONSTANT FINAL)
+ Q_PROPERTY(int modifiers READ modifiers CONSTANT FINAL)
+#if QT_DEPRECATED_SINCE(6, 6)
+ Q_PROPERTY(int source READ source CONSTANT REVISION(2, 7) FINAL)
+#endif
+ Q_PROPERTY(bool isClick READ isClick CONSTANT FINAL)
+ Q_PROPERTY(bool wasHeld READ wasHeld CONSTANT FINAL)
+ Q_PROPERTY(bool accepted READ isAccepted WRITE setAccepted FINAL)
+ Q_PROPERTY(int flags READ flags CONSTANT REVISION(2, 11) FINAL)
+ QML_NAMED_ELEMENT(MouseEvent)
+ QML_UNCREATABLE("Should only be used by mouse event signal handlers, for example in MouseArea")
QML_ADDED_IN_VERSION(2, 0)
public:
@@ -145,7 +148,9 @@ public:
int button() const { return _button; }
int buttons() const { return _buttons; }
int modifiers() const { return _modifiers; }
+#if QT_DEPRECATED_SINCE(6, 6)
int source() const { return _source; }
+#endif
bool wasHeld() const { return _wasHeld; }
bool isClick() const { return _isClick; }
@@ -153,7 +158,9 @@ public:
void setX(qreal x) { _x = x; }
void setY(qreal y) { _y = y; }
void setPosition(const QPointF &point) { _x = point.x(); _y = point.y(); }
+#if QT_DEPRECATED_SINCE(6, 6)
void setSource(Qt::MouseEventSource s) { _source = s; }
+#endif
bool isAccepted() { return _accepted; }
void setAccepted(bool accepted) { _accepted = accepted; }
@@ -172,20 +179,21 @@ private:
};
#if QT_CONFIG(wheelevent)
-class QQuickWheelEvent : public QObject
+class Q_QUICK_EXPORT QQuickWheelEvent : public QObject
{
Q_OBJECT
- Q_PROPERTY(const QPointingDevice *device READ pointingDevice CONSTANT)
- Q_PROPERTY(qreal x READ x CONSTANT)
- Q_PROPERTY(qreal y READ y CONSTANT)
- Q_PROPERTY(QPoint angleDelta READ angleDelta CONSTANT)
- Q_PROPERTY(QPoint pixelDelta READ pixelDelta CONSTANT)
- Q_PROPERTY(Qt::ScrollPhase phase READ phase CONSTANT)
- Q_PROPERTY(int buttons READ buttons CONSTANT)
- Q_PROPERTY(int modifiers READ modifiers CONSTANT)
- Q_PROPERTY(bool inverted READ inverted CONSTANT)
- Q_PROPERTY(bool accepted READ isAccepted WRITE setAccepted)
- QML_ANONYMOUS
+ Q_PROPERTY(const QPointingDevice *device READ pointingDevice CONSTANT FINAL)
+ Q_PROPERTY(qreal x READ x CONSTANT FINAL)
+ Q_PROPERTY(qreal y READ y CONSTANT FINAL)
+ Q_PROPERTY(QPoint angleDelta READ angleDelta CONSTANT FINAL)
+ Q_PROPERTY(QPoint pixelDelta READ pixelDelta CONSTANT FINAL)
+ Q_PROPERTY(Qt::ScrollPhase phase READ phase CONSTANT FINAL)
+ Q_PROPERTY(int buttons READ buttons CONSTANT FINAL)
+ Q_PROPERTY(int modifiers READ modifiers CONSTANT FINAL)
+ Q_PROPERTY(bool inverted READ inverted CONSTANT FINAL)
+ Q_PROPERTY(bool accepted READ isAccepted WRITE setAccepted FINAL)
+ QML_NAMED_ELEMENT(WheelEvent)
+ QML_UNCREATABLE("Should only be used by wheel event signal handlers, for example in MouseArea")
QML_ADDED_IN_VERSION(2, 0)
public:
@@ -231,11 +239,12 @@ private:
};
#endif
-class Q_QUICK_PRIVATE_EXPORT QQuickCloseEvent : public QObject
+class Q_QUICK_EXPORT QQuickCloseEvent : public QObject
{
Q_OBJECT
- Q_PROPERTY(bool accepted READ isAccepted WRITE setAccepted)
- QML_ANONYMOUS
+ Q_PROPERTY(bool accepted READ isAccepted WRITE setAccepted FINAL)
+ QML_NAMED_ELEMENT(CloseEvent)
+ QML_UNCREATABLE("Should only be used by Window's closing signal")
QML_ADDED_IN_VERSION(2, 0)
public:
@@ -250,14 +259,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickKeyEvent)
-QML_DECLARE_TYPE(QQuickMouseEvent)
-#if QT_CONFIG(wheelevent)
-QML_DECLARE_TYPE(QQuickWheelEvent)
-#endif
-QML_DECLARE_TYPE(QQuickCloseEvent)
-QML_DECLARE_TYPE(QPointingDevice)
-QML_DECLARE_TYPE(QPointingDeviceUniqueId)
-QML_DECLARE_TYPE(QPointerEvent)
-
#endif // QQUICKEVENTS_P_P_H
diff --git a/src/quick/items/qquickflickable.cpp b/src/quick/items/qquickflickable.cpp
index c2f71a9438..5a9a0ff846 100644
--- a/src/quick/items/qquickflickable.cpp
+++ b/src/quick/items/qquickflickable.cpp
@@ -7,7 +7,7 @@
#include "qquickwindow.h"
#include "qquickwindow_p.h"
#include "qquickmousearea_p.h"
-#if QT_CONFIG(draganddrop)
+#if QT_CONFIG(quick_draganddrop)
#include "qquickdrag_p.h"
#endif
@@ -22,39 +22,23 @@
#include <QtGui/private/qeventpoint_p.h>
#include <QtGui/qstylehints.h>
#include <QtCore/qmath.h>
-#include <qpa/qplatformintegration.h>
+#include <qpa/qplatformtheme.h>
#include <math.h>
#include <cmath>
QT_BEGIN_NAMESPACE
-Q_DECLARE_LOGGING_CATEGORY(lcHandlerParent)
-Q_LOGGING_CATEGORY(lcFlickable, "qt.quick.flickable")
-Q_LOGGING_CATEGORY(lcFilter, "qt.quick.flickable.filter")
-Q_LOGGING_CATEGORY(lcReplay, "qt.quick.flickable.replay")
-Q_LOGGING_CATEGORY(lcWheel, "qt.quick.flickable.wheel")
-Q_LOGGING_CATEGORY(lcVel, "qt.quick.flickable.velocity")
+Q_STATIC_LOGGING_CATEGORY(lcFlickable, "qt.quick.flickable")
+Q_STATIC_LOGGING_CATEGORY(lcFilter, "qt.quick.flickable.filter")
+Q_STATIC_LOGGING_CATEGORY(lcReplay, "qt.quick.flickable.replay")
+Q_STATIC_LOGGING_CATEGORY(lcWheel, "qt.quick.flickable.wheel")
+Q_STATIC_LOGGING_CATEGORY(lcVel, "qt.quick.flickable.velocity")
// RetainGrabVelocity is the maxmimum instantaneous velocity that
// will ensure the Flickable retains the grab on consecutive flicks.
static const int RetainGrabVelocity = 100;
-// Currently std::round can't be used on Android when using ndk g++, so
-// use C version instead. We could just define two versions of Round, one
-// for float and one for double, but then only one of them would be used
-// and compiler would trigger a warning about unused function.
-//
-// See https://code.google.com/p/android/issues/detail?id=54418
-template<typename T>
-static T Round(T t) {
- return round(t);
-}
-template<>
-Q_DECL_UNUSED float Round<float>(float f) {
- return roundf(f);
-}
-
static qreal EaseOvershoot(qreal t) {
return qAtan(t);
}
@@ -101,7 +85,7 @@ void QQuickFlickableVisibleArea::updateVisible()
qreal pagePos = 0;
qreal pageSize = 0;
if (!qFuzzyIsNull(maxYBounds)) {
- qreal y = p->pixelAligned ? Round(p->vData.move.value()) : p->vData.move.value();
+ qreal y = p->pixelAligned ? std::round(p->vData.move.value()) : p->vData.move.value();
pagePos = (-y + flickable->minYExtent()) / maxYBounds;
pageSize = viewheight / maxYBounds;
}
@@ -120,7 +104,7 @@ void QQuickFlickableVisibleArea::updateVisible()
const qreal maxxextent = -flickable->maxXExtent() + flickable->minXExtent();
const qreal maxXBounds = maxxextent + viewwidth;
if (!qFuzzyIsNull(maxXBounds)) {
- qreal x = p->pixelAligned ? Round(p->hData.move.value()) : p->hData.move.value();
+ qreal x = p->pixelAligned ? std::round(p->hData.move.value()) : p->hData.move.value();
pagePos = (-x + flickable->minXExtent()) / maxXBounds;
pageSize = viewwidth / maxXBounds;
} else {
@@ -249,8 +233,9 @@ QQuickFlickablePrivate::QQuickFlickablePrivate()
, syncDrag(false)
, lastPosTime(-1)
, lastPressTime(0)
- , deceleration(QGuiApplicationPrivate::platformIntegration()->styleHint(QPlatformIntegration::FlickDeceleration).toReal())
- , maxVelocity(QGuiApplicationPrivate::platformIntegration()->styleHint(QPlatformIntegration::FlickMaximumVelocity).toReal())
+ , deceleration(QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::FlickDeceleration).toReal())
+ , wheelDeceleration(15000)
+ , maxVelocity(QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::FlickMaximumVelocity).toReal())
, delayedPressEvent(nullptr), pressDelay(0), fixupDuration(400)
, flickBoost(1.0), initialWheelFlickDistance(qApp->styleHints()->wheelScrollLines() * 24)
, fixupMode(Normal), vTime(0), visibleArea(nullptr)
@@ -259,6 +244,9 @@ QQuickFlickablePrivate::QQuickFlickablePrivate()
, boundsMovement(QQuickFlickable::FollowBoundsBehavior)
, rebound(nullptr)
{
+ const int wheelDecelerationEnv = qEnvironmentVariableIntValue("QT_QUICK_FLICKABLE_WHEEL_DECELERATION");
+ if (wheelDecelerationEnv > 0)
+ wheelDeceleration = wheelDecelerationEnv;
}
void QQuickFlickablePrivate::init()
@@ -276,6 +264,7 @@ void QQuickFlickablePrivate::init()
q->setFlag(QQuickItem::ItemIsViewport);
QQuickItemPrivate *viewportPrivate = QQuickItemPrivate::get(contentItem);
viewportPrivate->addItemChangeListener(this, QQuickItemPrivate::Geometry);
+ setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Preferred);
}
/*!
@@ -339,20 +328,21 @@ void QQuickFlickablePrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometr
}
}
-bool QQuickFlickablePrivate::flickX(qreal velocity)
+bool QQuickFlickablePrivate::flickX(QEvent::Type eventType, qreal velocity)
{
Q_Q(QQuickFlickable);
- return flick(hData, q->minXExtent(), q->maxXExtent(), q->width(), fixupX_callback, velocity);
+ return flick(hData, q->minXExtent(), q->maxXExtent(), q->width(), fixupX_callback, eventType, velocity);
}
-bool QQuickFlickablePrivate::flickY(qreal velocity)
+bool QQuickFlickablePrivate::flickY(QEvent::Type eventType, qreal velocity)
{
Q_Q(QQuickFlickable);
- return flick(vData, q->minYExtent(), q->maxYExtent(), q->height(), fixupY_callback, velocity);
+ return flick(vData, q->minYExtent(), q->maxYExtent(), q->height(), fixupY_callback, eventType, velocity);
}
bool QQuickFlickablePrivate::flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal,
- QQuickTimeLineCallback::Callback fixupCallback, qreal velocity)
+ QQuickTimeLineCallback::Callback fixupCallback,
+ QEvent::Type eventType, qreal velocity)
{
Q_Q(QQuickFlickable);
qreal maxDistance = -1;
@@ -374,13 +364,14 @@ bool QQuickFlickablePrivate::flick(AxisData &data, qreal minExtent, qreal maxExt
v = maxVelocity;
}
+ qreal accel = eventType == QEvent::Wheel ? wheelDeceleration : deceleration;
+ qCDebug(lcFlickable) << "choosing deceleration" << accel << "for" << eventType;
// adjust accel so that we hit a full pixel
- qreal accel = deceleration;
qreal v2 = v * v;
qreal dist = v2 / (accel * 2.0);
if (v > 0)
dist = -dist;
- qreal target = -Round(-(data.move.value() - dist));
+ qreal target = std::round(data.move.value() - dist);
dist = -target + data.move.value();
accel = v2 / (2.0f * qAbs(dist));
@@ -502,18 +493,18 @@ void QQuickFlickablePrivate::fixup(AxisData &data, qreal minExtent, qreal maxExt
} else if (data.move.value() <= maxExtent) {
resetTimeline(data);
adjustContentPos(data, maxExtent);
- } else if (-Round(-data.move.value()) != data.move.value()) {
+ } else if (-std::round(-data.move.value()) != data.move.value()) {
// We could animate, but since it is less than 0.5 pixel it's probably not worthwhile.
resetTimeline(data);
qreal val = data.move.value();
- if (std::abs(-Round(-val) - val) < 0.25) // round small differences
- val = -Round(-val);
+ if (std::abs(std::round(val) - val) < 0.25) // round small differences
+ val = std::round(val);
else if (data.smoothVelocity.value() > 0) // continue direction of motion for larger
- val = -std::floor(-val);
+ val = std::ceil(val);
else if (data.smoothVelocity.value() < 0)
- val = -std::ceil(-val);
+ val = std::floor(val);
else // otherwise round
- val = -Round(-val);
+ val = std::round(val);
timeline.set(data.move, val);
}
data.inOvershoot = false;
@@ -549,7 +540,7 @@ void QQuickFlickablePrivate::updateBeginningEnd()
// Vertical
const qreal maxyextent = -q->maxYExtent();
const qreal minyextent = -q->minYExtent();
- const qreal ypos = -vData.move.value();
+ const qreal ypos = pixelAligned ? -std::round(vData.move.value()) : -vData.move.value();
bool atBeginning = fuzzyLessThanOrEqualTo(ypos, std::ceil(minyextent));
bool atEnd = fuzzyLessThanOrEqualTo(std::floor(maxyextent), ypos);
@@ -569,7 +560,7 @@ void QQuickFlickablePrivate::updateBeginningEnd()
// Horizontal
const qreal maxxextent = -q->maxXExtent();
const qreal minxextent = -q->minXExtent();
- const qreal xpos = -hData.move.value();
+ const qreal xpos = pixelAligned ? -std::round(hData.move.value()) : -hData.move.value();
atBeginning = fuzzyLessThanOrEqualTo(xpos, std::ceil(minxextent));
atEnd = fuzzyLessThanOrEqualTo(std::floor(maxxextent), xpos);
@@ -764,8 +755,6 @@ void QQuickFlickablePrivate::updateBeginningEnd()
\snippet qml/flickableScrollbar.qml 0
\dots 8
\snippet qml/flickableScrollbar.qml 1
-
- \sa {customitems/scrollbar}{UI Components: Scrollbar Example}
*/
QQuickFlickable::QQuickFlickable(QQuickItem *parent)
: QQuickItem(*(new QQuickFlickablePrivate), parent)
@@ -1111,14 +1100,17 @@ void QQuickFlickablePrivate::handlePressEvent(QPointerEvent *event)
}
q->setKeepMouseGrab(stealMouse);
- maybeBeginDrag(computeCurrentTime(event), event->points().first().position());
+ maybeBeginDrag(computeCurrentTime(event), event->points().first().position(),
+ event->isSinglePointEvent() ? static_cast<QSinglePointEvent *>(event)->buttons()
+ : Qt::NoButton);
}
-void QQuickFlickablePrivate::maybeBeginDrag(qint64 currentTimestamp, const QPointF &pressPosn)
+void QQuickFlickablePrivate::maybeBeginDrag(qint64 currentTimestamp, const QPointF &pressPosn, Qt::MouseButtons buttons)
{
Q_Q(QQuickFlickable);
clearDelayedPress();
- pressed = true;
+ // consider dragging only when event is left mouse button or touch event which has no button
+ pressed = buttons.testFlag(Qt::LeftButton) || (buttons == Qt::NoButton);
if (hData.transitionToBounds)
hData.transitionToBounds->stopTransition();
@@ -1382,10 +1374,14 @@ void QQuickFlickablePrivate::handleMoveEvent(QPointerEvent *event)
const QVector2D velocity = firstPointLocalVelocity(event);
bool overThreshold = false;
- if (q->yflick())
- overThreshold |= QQuickDeliveryAgentPrivate::dragOverThreshold(deltas.y(), Qt::YAxis, firstPoint);
- if (q->xflick())
- overThreshold |= QQuickDeliveryAgentPrivate::dragOverThreshold(deltas.x(), Qt::XAxis, firstPoint);
+ if (event->pointCount() == 1) {
+ if (q->yflick())
+ overThreshold |= QQuickDeliveryAgentPrivate::dragOverThreshold(deltas.y(), Qt::YAxis, firstPoint);
+ if (q->xflick())
+ overThreshold |= QQuickDeliveryAgentPrivate::dragOverThreshold(deltas.x(), Qt::XAxis, firstPoint);
+ } else {
+ qCDebug(lcFilter) << q->objectName() << "ignoring multi-touch" << event;
+ }
drag(currentTimestamp, event->type(), pos, deltas, overThreshold, false, false, velocity);
}
@@ -1447,24 +1443,32 @@ void QQuickFlickablePrivate::handleReleaseEvent(QPointerEvent *event)
}
flickBoost = canBoost ? qBound(1.0, flickBoost+0.25, QML_FLICK_MULTIFLICK_MAXBOOST) : 1.0;
- const int flickThreshold = QGuiApplicationPrivate::platformIntegration()->styleHint(QPlatformIntegration::FlickStartDistance).toInt();
+ const int flickThreshold = QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::FlickStartDistance).toInt();
+
+ bool anyPointGrabbed = event->points().constEnd() !=
+ std::find_if(event->points().constBegin(),event->points().constEnd(),
+ [q, event](const QEventPoint &point) { return event->exclusiveGrabber(point) == q; });
bool flickedVertically = false;
vVelocity *= flickBoost;
- bool isVerticalFlickAllowed = q->yflick() && qAbs(vVelocity) > MinimumFlickVelocity && qAbs(pos.y() - pressPos.y()) > flickThreshold;
+ const bool isVerticalFlickAllowed = anyPointGrabbed &&
+ q->yflick() && qAbs(vVelocity) > _q_MinimumFlickVelocity &&
+ qAbs(pos.y() - pressPos.y()) > flickThreshold;
if (isVerticalFlickAllowed) {
velocityTimeline.reset(vData.smoothVelocity);
vData.smoothVelocity.setValue(-vVelocity);
- flickedVertically = flickY(vVelocity);
+ flickedVertically = flickY(event->type(), vVelocity);
}
bool flickedHorizontally = false;
hVelocity *= flickBoost;
- bool isHorizontalFlickAllowed = q->xflick() && qAbs(hVelocity) > MinimumFlickVelocity && qAbs(pos.x() - pressPos.x()) > flickThreshold;
+ const bool isHorizontalFlickAllowed = anyPointGrabbed &&
+ q->xflick() && qAbs(hVelocity) > _q_MinimumFlickVelocity &&
+ qAbs(pos.x() - pressPos.x()) > flickThreshold;
if (isHorizontalFlickAllowed) {
velocityTimeline.reset(hData.smoothVelocity);
hData.smoothVelocity.setValue(-hVelocity);
- flickedHorizontally = flickX(hVelocity);
+ flickedHorizontally = flickX(event->type(), hVelocity);
}
if (!isVerticalFlickAllowed)
@@ -1474,8 +1478,15 @@ void QQuickFlickablePrivate::handleReleaseEvent(QPointerEvent *event)
fixupX();
flickingStarted(flickedHorizontally, flickedVertically);
- if (!isViewMoving())
+ if (!isViewMoving()) {
q->movementEnding();
+ } else {
+ if (flickedVertically)
+ vMoved = true;
+ if (flickedHorizontally)
+ hMoved = true;
+ q->movementStarting();
+ }
}
void QQuickFlickable::mousePressEvent(QMouseEvent *event)
@@ -1508,13 +1519,14 @@ void QQuickFlickable::mouseReleaseEvent(QMouseEvent *event)
if (d->delayedPressEvent) {
d->replayDelayedPress();
- // Now send the release
- if (auto grabber = qmlobject_cast<QQuickItem *>(event->exclusiveGrabber(event->point(0)))) {
- // not copying or detaching anything, so make sure we return the original event unchanged
- const auto oldPosition = event->point(0).position();
- QMutableEventPoint::setPosition(event->point(0), grabber->mapFromScene(event->scenePosition()));
+ auto &firstPoint = event->point(0);
+ if (const auto *grabber = event->exclusiveGrabber(firstPoint); grabber && grabber->isQuickItemType()) {
+ // Since we sent the delayed press to the window, we need to resend the release to the window too.
+ // We're not copying or detaching, so restore the original event position afterwards.
+ const auto oldPosition = firstPoint.position();
+ QMutableEventPoint::setPosition(firstPoint, event->scenePosition());
QCoreApplication::sendEvent(window(), event);
- QMutableEventPoint::setPosition(event->point(0), oldPosition);
+ QMutableEventPoint::setPosition(firstPoint, oldPosition);
}
// And the event has been consumed
@@ -1533,6 +1545,15 @@ void QQuickFlickable::mouseReleaseEvent(QMouseEvent *event)
void QQuickFlickable::touchEvent(QTouchEvent *event)
{
Q_D(QQuickFlickable);
+
+ if (event->type() == QEvent::TouchCancel) {
+ if (d->interactive && d->wantsPointerEvent(event))
+ d->cancelInteraction();
+ else
+ QQuickItem::touchEvent(event);
+ return;
+ }
+
bool unhandled = false;
const auto &firstPoint = event->points().first();
switch (firstPoint.state()) {
@@ -1558,11 +1579,11 @@ void QQuickFlickable::touchEvent(QTouchEvent *event)
if (d->delayedPressEvent) {
d->replayDelayedPress();
- // Now send the release
- auto &firstPoint = event->point(0);
- if (auto grabber = qmlobject_cast<QQuickItem *>(event->exclusiveGrabber(firstPoint))) {
- const auto localPos = grabber->mapFromScene(firstPoint.scenePosition());
- QScopedPointer<QPointerEvent> localizedEvent(QQuickDeliveryAgentPrivate::clonePointerEvent(event, localPos));
+ const auto &firstPoint = event->point(0);
+ if (const auto *grabber = event->exclusiveGrabber(firstPoint); grabber && grabber->isQuickItemType()) {
+ // Since we sent the delayed press to the window, we need to resend the release to the window too.
+ QScopedPointer<QPointerEvent> localizedEvent(
+ QQuickDeliveryAgentPrivate::clonePointerEvent(event, firstPoint.scenePosition()));
QCoreApplication::sendEvent(window(), localizedEvent.data());
}
@@ -1644,48 +1665,117 @@ void QQuickFlickable::wheelEvent(QWheelEvent *event)
// no pixel delta (physical mouse wheel, or "dumb" touchpad), so use angleDelta
int xDelta = event->angleDelta().x();
int yDelta = event->angleDelta().y();
- // For a single "clicky" wheel event (angleDelta +/- 120),
- // we want flick() to end up moving a distance proportional to QStyleHints::wheelScrollLines().
- // The decel algo from there is
- // qreal dist = v2 / (accel * 2.0);
- // i.e. initialWheelFlickDistance = (120 / dt)^2 / (deceleration * 2)
- // now solve for dt:
- // dt = 120 / sqrt(deceleration * 2 * initialWheelFlickDistance)
- if (!isMoving())
- elapsed = 120 / qSqrt(d->deceleration * 2 * d->initialWheelFlickDistance);
- if (yflick() && yDelta != 0) {
- qreal instVelocity = yDelta / elapsed;
- // if the direction has changed, start over with filtering, to allow instant movement in the opposite direction
- if ((instVelocity < 0 && d->vData.velocity > 0) || (instVelocity > 0 && d->vData.velocity < 0))
- d->vData.velocityBuffer.clear();
- d->vData.addVelocitySample(instVelocity, d->maxVelocity);
- d->vData.updateVelocity();
- if ((yDelta > 0 && contentY() > -minYExtent()) || (yDelta < 0 && contentY() < -maxYExtent())) {
- const bool newFlick = d->flickY(d->vData.velocity);
- if (newFlick && (d->vData.atBeginning != (yDelta > 0) || d->vData.atEnd != (yDelta < 0))) {
- d->flickingStarted(false, true);
- d->vMoved = true;
+
+ if (d->wheelDeceleration > _q_MaximumWheelDeceleration) {
+ const qreal wheelScroll = -qApp->styleHints()->wheelScrollLines() * 24;
+ // If wheelDeceleration is very large, i.e. the user or the platform does not want to have any mouse wheel
+ // acceleration behavior, we want to move a distance proportional to QStyleHints::wheelScrollLines()
+ if (yflick() && yDelta != 0) {
+ d->moveReason = QQuickFlickablePrivate::Mouse; // ItemViews will set fixupMode to Immediate in fixup() without this.
+ d->vMoved = true;
+ qreal scrollPixel = (-yDelta / 120.0 * wheelScroll);
+ if (scrollPixel > 0) { // Forward direction (away from user)
+ if (d->vData.move.value() >= minYExtent())
+ d->vMoved = false;
+ } else { // Backward direction (towards user)
+ if (d->vData.move.value() <= maxYExtent())
+ d->vMoved = false;
+ }
+ if (d->vMoved) {
+ if (d->boundsBehavior == QQuickFlickable::StopAtBounds) {
+ const qreal estContentPos = scrollPixel + d->vData.move.value();
+ if (scrollPixel > 0) { // Forward direction (away from user)
+ if (estContentPos > minYExtent())
+ scrollPixel = minYExtent() - d->vData.move.value();
+ } else { // Backward direction (towards user)
+ if (estContentPos < maxYExtent())
+ scrollPixel = maxYExtent() - d->vData.move.value();
+ }
+ }
+ d->resetTimeline(d->vData);
movementStarting();
+ d->timeline.moveBy(d->vData.move, scrollPixel, QEasingCurve(QEasingCurve::OutExpo), 3*d->fixupDuration/4);
+ d->vData.fixingUp = true;
+ d->timeline.callback(QQuickTimeLineCallback(&d->vData.move, QQuickFlickablePrivate::fixupY_callback, d));
}
event->accept();
}
- }
- if (xflick() && xDelta != 0) {
- qreal instVelocity = xDelta / elapsed;
- // if the direction has changed, start over with filtering, to allow instant movement in the opposite direction
- if ((instVelocity < 0 && d->hData.velocity > 0) || (instVelocity > 0 && d->hData.velocity < 0))
- d->hData.velocityBuffer.clear();
- d->hData.addVelocitySample(instVelocity, d->maxVelocity);
- d->hData.updateVelocity();
- if ((xDelta > 0 && contentX() > -minXExtent()) || (xDelta < 0 && contentX() < -maxXExtent())) {
- const bool newFlick = d->flickX(d->hData.velocity);
- if (newFlick && (d->hData.atBeginning != (xDelta > 0) || d->hData.atEnd != (xDelta < 0))) {
- d->flickingStarted(true, false);
- d->hMoved = true;
+ if (xflick() && xDelta != 0) {
+ d->moveReason = QQuickFlickablePrivate::Mouse; // ItemViews will set fixupMode to Immediate in fixup() without this.
+ d->hMoved = true;
+ qreal scrollPixel = (-xDelta / 120.0 * wheelScroll);
+ if (scrollPixel > 0) { // Forward direction (away from user)
+ if (d->hData.move.value() >= minXExtent())
+ d->hMoved = false;
+ } else { // Backward direction (towards user)
+ if (d->hData.move.value() <= maxXExtent())
+ d->hMoved = false;
+ }
+ if (d->hMoved) {
+ if (d->boundsBehavior == QQuickFlickable::StopAtBounds) {
+ const qreal estContentPos = scrollPixel + d->hData.move.value();
+ if (scrollPixel > 0) { // Forward direction (away from user)
+ if (estContentPos > minXExtent())
+ scrollPixel = minXExtent() - d->hData.move.value();
+ } else { // Backward direction (towards user)
+ if (estContentPos < maxXExtent())
+ scrollPixel = maxXExtent() - d->hData.move.value();
+ }
+ }
+ d->resetTimeline(d->hData);
movementStarting();
+ d->timeline.moveBy(d->hData.move, scrollPixel, QEasingCurve(QEasingCurve::OutExpo), 3*d->fixupDuration/4);
+ d->hData.fixingUp = true;
+ d->timeline.callback(QQuickTimeLineCallback(&d->hData.move, QQuickFlickablePrivate::fixupX_callback, d));
}
event->accept();
}
+ } else {
+ // wheelDeceleration is set to some reasonable value: the user or the platform wants to have
+ // the classic Qt Quick mouse wheel acceleration behavior.
+ // For a single "clicky" wheel event (angleDelta +/- 120),
+ // we want flick() to end up moving a distance proportional to QStyleHints::wheelScrollLines().
+ // The decel algo from there is
+ // qreal dist = v2 / (accel * 2.0);
+ // i.e. initialWheelFlickDistance = (120 / dt)^2 / (deceleration * 2)
+ // now solve for dt:
+ // dt = 120 / sqrt(deceleration * 2 * initialWheelFlickDistance)
+ if (!isMoving())
+ elapsed = 120 / qSqrt(d->wheelDeceleration * 2 * d->initialWheelFlickDistance);
+ if (yflick() && yDelta != 0) {
+ qreal instVelocity = yDelta / elapsed;
+ // if the direction has changed, start over with filtering, to allow instant movement in the opposite direction
+ if ((instVelocity < 0 && d->vData.velocity > 0) || (instVelocity > 0 && d->vData.velocity < 0))
+ d->vData.velocityBuffer.clear();
+ d->vData.addVelocitySample(instVelocity, d->maxVelocity);
+ d->vData.updateVelocity();
+ if ((yDelta > 0 && contentY() > -minYExtent()) || (yDelta < 0 && contentY() < -maxYExtent())) {
+ const bool newFlick = d->flickY(event->type(), d->vData.velocity);
+ if (newFlick && (d->vData.atBeginning != (yDelta > 0) || d->vData.atEnd != (yDelta < 0))) {
+ d->flickingStarted(false, true);
+ d->vMoved = true;
+ movementStarting();
+ }
+ event->accept();
+ }
+ }
+ if (xflick() && xDelta != 0) {
+ qreal instVelocity = xDelta / elapsed;
+ // if the direction has changed, start over with filtering, to allow instant movement in the opposite direction
+ if ((instVelocity < 0 && d->hData.velocity > 0) || (instVelocity > 0 && d->hData.velocity < 0))
+ d->hData.velocityBuffer.clear();
+ d->hData.addVelocitySample(instVelocity, d->maxVelocity);
+ d->hData.updateVelocity();
+ if ((xDelta > 0 && contentX() > -minXExtent()) || (xDelta < 0 && contentX() < -maxXExtent())) {
+ const bool newFlick = d->flickX(event->type(), d->hData.velocity);
+ if (newFlick && (d->hData.atBeginning != (xDelta > 0) || d->hData.atEnd != (xDelta < 0))) {
+ d->flickingStarted(true, false);
+ d->hMoved = true;
+ movementStarting();
+ }
+ event->accept();
+ }
+ }
}
} else {
// use pixelDelta (probably from a trackpad): this is where we want to be on most platforms eventually
@@ -1812,7 +1902,7 @@ void QQuickFlickablePrivate::replayDelayedPress()
void QQuickFlickablePrivate::setViewportX(qreal x)
{
Q_Q(QQuickFlickable);
- qreal effectiveX = pixelAligned ? -Round(-x) : x;
+ qreal effectiveX = pixelAligned ? -std::round(-x) : x;
const qreal maxX = q->maxXExtent();
const qreal minX = q->minXExtent();
@@ -1847,7 +1937,7 @@ void QQuickFlickablePrivate::setViewportX(qreal x)
void QQuickFlickablePrivate::setViewportY(qreal y)
{
Q_Q(QQuickFlickable);
- qreal effectiveY = pixelAligned ? -Round(-y) : y;
+ qreal effectiveY = pixelAligned ? -std::round(-y) : y;
const qreal maxY = q->maxYExtent();
const qreal minY = q->minYExtent();
@@ -2015,7 +2105,7 @@ void QQuickFlickable::geometryChange(const QRectF &newGeometry, const QRectF &ol
Flicks the content with \a xVelocity horizontally and \a yVelocity vertically in pixels/sec.
Calling this method will update the corresponding moving and flicking properties and signals,
- just like a real flick.
+ just like a real touchscreen flick.
*/
void QQuickFlickable::flick(qreal xVelocity, qreal yVelocity)
@@ -2027,8 +2117,8 @@ void QQuickFlickable::flick(qreal xVelocity, qreal yVelocity)
d->vData.velocity = yVelocity;
d->hData.vTime = d->vData.vTime = d->timeline.time();
- const bool flickedX = xflick() && !qFuzzyIsNull(xVelocity) && d->flickX(xVelocity);
- const bool flickedY = yflick() && !qFuzzyIsNull(yVelocity) && d->flickY(yVelocity);
+ const bool flickedX = xflick() && !qFuzzyIsNull(xVelocity) && d->flickX(QEvent::TouchUpdate, xVelocity);
+ const bool flickedY = yflick() && !qFuzzyIsNull(yVelocity) && d->flickY(QEvent::TouchUpdate, yVelocity);
if (flickedX)
d->hMoved = true;
@@ -2580,12 +2670,31 @@ bool QQuickFlickable::filterPointerEvent(QQuickItem *receiver, QPointerEvent *ev
QQuickDeliveryAgentPrivate::isTabletEvent(event)))
return false; // don't filter hover events or wheel events, for example
Q_ASSERT_X(receiver != this, "", "Flickable received a filter event for itself");
- qCDebug(lcFilter) << objectName() << "filtering" << event << "for" << receiver;
Q_D(QQuickFlickable);
// If a touch event contains a new press point, don't steal right away: watch the movements for a while
if (isTouch && static_cast<QTouchEvent *>(event)->touchPointStates().testFlag(QEventPoint::State::Pressed))
d->stealMouse = false;
+ // If multiple touchpoints are within bounds, don't grab: it's probably meant for multi-touch interaction in some child
+ if (event->pointCount() > 1) {
+ qCDebug(lcFilter) << objectName() << "ignoring multi-touch" << event << "for" << receiver;
+ d->stealMouse = false;
+ } else {
+ qCDebug(lcFilter) << objectName() << "filtering" << event << "for" << receiver;
+ }
+
const auto &firstPoint = event->points().first();
+
+ if (event->pointCount() == 1 && event->exclusiveGrabber(firstPoint) == this) {
+ // We have an exclusive grab (since we're e.g dragging), but at the same time, we have
+ // a child with a passive grab (which is why this filter is being called). And because
+ // of that, we end up getting the same pointer events twice; First in our own event
+ // handlers (because of the grab), then once more in here, since we filter the child.
+ // To avoid processing the event twice (e.g avoid calling handleReleaseEvent once more
+ // from below), we mark the event as filtered, and simply return.
+ event->setAccepted(true);
+ return true;
+ }
+
QPointF localPos = mapFromScene(firstPoint.scenePosition());
bool receiverDisabled = receiver && !receiver->isEnabled();
bool stealThisEvent = d->stealMouse;
@@ -2595,7 +2704,7 @@ bool QQuickFlickable::filterPointerEvent(QQuickItem *receiver, QPointerEvent *ev
// Special case for MouseArea, try to guess what it does with the event
if (auto *mouseArea = qmlobject_cast<QQuickMouseArea *>(receiver)) {
bool preventStealing = mouseArea->preventStealing();
-#if QT_CONFIG(draganddrop)
+#if QT_CONFIG(quick_draganddrop)
if (mouseArea->drag() && mouseArea->drag()->target())
preventStealing = true;
#endif
@@ -2720,21 +2829,10 @@ void QQuickFlickable::setMaximumFlickVelocity(qreal v)
\qmlproperty real QtQuick::Flickable::flickDeceleration
This property holds the rate at which a flick will decelerate:
the higher the number, the faster it slows down when the user stops
- flicking via touch, touchpad or mouse wheel. For example 0.0001 is nearly
+ flicking via touch. For example 0.0001 is nearly
"frictionless", and 10000 feels quite "sticky".
The default value is platform dependent. Values of zero or less are not allowed.
-
- \note For touchpad flicking, some platforms drive Flickable directly by
- sending QWheelEvents with QWheelEvent::phase() being \c Qt::ScrollMomentum,
- after the user has released all fingers from the touchpad. In that case,
- the operating system is controlling the deceleration, and this property has
- no effect.
-
- \note For mouse wheel scrolling, and for gesture scrolling on touchpads
- that do not have a momentum phase, extremely large values of
- flickDeceleration can make Flickable very resistant to scrolling,
- especially if \l maximumFlickVelocity is too small.
*/
qreal QQuickFlickable::flickDeceleration() const
{
diff --git a/src/quick/items/qquickflickable_p.h b/src/quick/items/qquickflickable_p.h
index a059924ed1..3f6a0e67fb 100644
--- a/src/quick/items/qquickflickable_p.h
+++ b/src/quick/items/qquickflickable_p.h
@@ -22,7 +22,7 @@ QT_BEGIN_NAMESPACE
class QQuickFlickablePrivate;
class QQuickFlickableVisibleArea;
-class Q_QUICK_PRIVATE_EXPORT QQuickFlickable : public QQuickItem
+class Q_QUICK_EXPORT QQuickFlickable : public QQuickItem
{
Q_OBJECT
@@ -287,6 +287,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickFlickable)
-
#endif // QQUICKFLICKABLE_P_H
diff --git a/src/quick/items/qquickflickable_p_p.h b/src/quick/items/qquickflickable_p_p.h
index 04d27c0b8e..e9f452f3d7 100644
--- a/src/quick/items/qquickflickable_p_p.h
+++ b/src/quick/items/qquickflickable_p_p.h
@@ -30,14 +30,11 @@
QT_BEGIN_NAMESPACE
-// Really slow flicks can be annoying.
-const qreal MinimumFlickVelocity = 75.0;
-
class QQuickFlickableVisibleArea;
class QQuickTransition;
class QQuickFlickableReboundTransition;
-class Q_QUICK_PRIVATE_EXPORT QQuickFlickablePrivate : public QQuickItemPrivate, public QQuickItemChangeListener
+class Q_QUICK_EXPORT QQuickFlickablePrivate : public QQuickItemPrivate, public QQuickItemChangeListener
{
Q_DECLARE_PUBLIC(QQuickFlickable)
@@ -60,21 +57,17 @@ public:
QQuickFlickablePrivate *parent;
};
+ enum MovementReason { Other, SetIndex, Mouse };
+
struct AxisData {
AxisData(QQuickFlickablePrivate *fp, void (QQuickFlickablePrivate::*func)(qreal))
: move(fp, func)
- , transitionToBounds(nullptr)
- , viewSize(-1), lastPos(0), previousDragDelta(0), velocity(0), startMargin(0), endMargin(0)
- , origin(0), overshoot(0)
- , transitionTo(0)
- , continuousFlickVelocity(0), velocityTime(), vTime(0)
, smoothVelocity(fp), atEnd(false), atBeginning(true)
, transitionToSet(false)
, fixingUp(false), inOvershoot(false), inRebound(false), moving(false), flicking(false)
, flickingWhenDragBegan(false), dragging(false), extentsChanged(false)
- , explicitValue(false), minExtentDirty(true), maxExtentDirty(true)
- , contentPositionChangedExternallyDuringDrag(false)
- , unused(0)
+ , explicitValue(false), contentPositionChangedExternallyDuringDrag(false)
+ , minExtentDirty(true), maxExtentDirty(true)
{}
~AxisData();
@@ -102,26 +95,27 @@ public:
void updateVelocity();
QQuickTimeLineValueProxy<QQuickFlickablePrivate> move;
- QQuickFlickableReboundTransition *transitionToBounds;
- qreal viewSize;
- qreal pressPos;
- qreal lastPos;
- qreal dragStartOffset;
- qreal dragMinBound;
- qreal dragMaxBound;
- qreal previousDragDelta;
- qreal velocity;
- qreal flickTarget;
- qreal startMargin;
- qreal endMargin;
- qreal origin;
- qreal overshoot;
- qreal transitionTo;
- qreal continuousFlickVelocity;
+ QQuickFlickableReboundTransition *transitionToBounds = nullptr;
+ qreal viewSize = -1;
+ qreal pressPos = 0;
+ qreal lastPos = 0;
+ qreal dragStartOffset = 0;
+ qreal dragMinBound = 0;
+ qreal dragMaxBound = 0;
+ qreal previousDragDelta = 0;
+ qreal velocity = 0;
+ qreal flickTarget = 0;
+ qreal startMargin = 0;
+ qreal endMargin = 0;
+ qreal origin = 0;
+ qreal overshoot = 0;
+ qreal transitionTo = 0;
+ qreal continuousFlickVelocity = 0;
QElapsedTimer velocityTime;
- int vTime;
+ int vTime = 0;
QQuickFlickablePrivate::Velocity smoothVelocity;
QPODVector<qreal,10> velocityBuffer;
+ // bitfield
uint atEnd : 1;
uint atBeginning : 1;
uint transitionToSet : 1;
@@ -134,16 +128,18 @@ public:
uint dragging : 1;
uint extentsChanged : 1;
uint explicitValue : 1;
+ uint contentPositionChangedExternallyDuringDrag : 1;
+ // mutable portion of bitfield
mutable uint minExtentDirty : 1;
mutable uint maxExtentDirty : 1;
- uint contentPositionChangedExternallyDuringDrag : 1;
- uint unused : 17;
+ // end bitfield
};
- bool flickX(qreal velocity);
- bool flickY(qreal velocity);
+ bool flickX(QEvent::Type eventType, qreal velocity);
+ bool flickY(QEvent::Type eventType, qreal velocity);
virtual bool flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize,
- QQuickTimeLineCallback::Callback fixupCallback, qreal velocity);
+ QQuickTimeLineCallback::Callback fixupCallback,
+ QEvent::Type eventType, qreal velocity);
void flickingStarted(bool flickingH, bool flickingV);
void fixupX();
@@ -184,6 +180,8 @@ public:
AxisData hData;
AxisData vData;
+ MovementReason moveReason = Other;
+
QQuickTimeLine timeline;
bool hMoved : 1;
bool vMoved : 1;
@@ -201,6 +199,7 @@ public:
QPointF pressPos;
QVector2D accumulatedWheelPixelDelta;
qreal deceleration;
+ qreal wheelDeceleration;
qreal maxVelocity;
QPointerEvent *delayedPressEvent;
QBasicTimer delayedPressTimer;
@@ -231,7 +230,8 @@ public:
void handleMoveEvent(QPointerEvent *);
void handleReleaseEvent(QPointerEvent *);
- void maybeBeginDrag(qint64 currentTimestamp, const QPointF &pressPosn);
+ void maybeBeginDrag(qint64 currentTimestamp, const QPointF &pressPosn,
+ Qt::MouseButtons buttons = Qt::NoButton);
void drag(qint64 currentTimestamp, QEvent::Type eventType, const QPointF &localPos,
const QVector2D &deltas, bool overThreshold, bool momentum,
bool velocitySensitiveOverBounds, const QVector2D &velocity);
@@ -247,14 +247,14 @@ public:
static void data_clear(QQmlListProperty<QObject> *);
};
-class Q_QUICK_PRIVATE_EXPORT QQuickFlickableVisibleArea : public QObject
+class Q_QUICK_EXPORT QQuickFlickableVisibleArea : public QObject
{
Q_OBJECT
- Q_PROPERTY(qreal xPosition READ xPosition NOTIFY xPositionChanged)
- Q_PROPERTY(qreal yPosition READ yPosition NOTIFY yPositionChanged)
- Q_PROPERTY(qreal widthRatio READ widthRatio NOTIFY widthRatioChanged)
- Q_PROPERTY(qreal heightRatio READ heightRatio NOTIFY heightRatioChanged)
+ Q_PROPERTY(qreal xPosition READ xPosition NOTIFY xPositionChanged FINAL)
+ Q_PROPERTY(qreal yPosition READ yPosition NOTIFY yPositionChanged FINAL)
+ Q_PROPERTY(qreal widthRatio READ widthRatio NOTIFY widthRatioChanged FINAL)
+ Q_PROPERTY(qreal heightRatio READ heightRatio NOTIFY heightRatioChanged FINAL)
QML_ANONYMOUS
QML_ADDED_IN_VERSION(2, 0)
@@ -284,6 +284,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickFlickableVisibleArea)
-
#endif // QQUICKFLICKABLE_P_P_H
diff --git a/src/quick/items/qquickflickablebehavior_p.h b/src/quick/items/qquickflickablebehavior_p.h
index d99fc988d1..ec43aaca7d 100644
--- a/src/quick/items/qquickflickablebehavior_p.h
+++ b/src/quick/items/qquickflickablebehavior_p.h
@@ -62,4 +62,11 @@
#define QML_FLICK_MULTIFLICK_MAXBOOST 3.0
#endif
+// Really slow flicks can be annoying.
+const qreal _q_MinimumFlickVelocity = 75.0;
+
+// If QQuickFlickablePrivate::wheelDeceleration (perhaps overridden via QT_QUICK_FLICKABLE_WHEEL_DECELERATION)
+// is greater than this, we switch to proportional wheel scrolling: no "acceleration" at all.
+const qreal _q_MaximumWheelDeceleration = 14999;
+
#endif //QQUICKFLICKABLEBEHAVIOR_H
diff --git a/src/quick/items/qquickflipable.cpp b/src/quick/items/qquickflipable.cpp
index d599618834..2e48566295 100644
--- a/src/quick/items/qquickflipable.cpp
+++ b/src/quick/items/qquickflipable.cpp
@@ -3,10 +3,12 @@
#include "qquickflipable_p.h"
#include "qquickitem_p.h"
-
+#include "qquicktranslate_p.h"
#include <QtQml/qqmlinfo.h>
+#include <QtCore/qpointer.h>
+
QT_BEGIN_NAMESPACE
// XXX todo - i think this needs work and a bit of a re-think
@@ -201,9 +203,20 @@ void QQuickFlipable::updatePolish()
d->updateSide();
}
-// determination on the currently visible side of the flipable
-// has to be done on the complete scene transform to give
-// correct results.
+/*! \internal
+ Flipable must use the complete scene transform to correctly determine the
+ currently visible side.
+
+ It must also be independent of camera distance, in case the contents are
+ too wide: for rotation transforms we simply call QMatrix4x4::rotate(),
+ whereas QQuickRotation::applyTo(QMatrix4x4*) calls
+ QMatrix4x4::projectedRotate() which by default assumes the camera distance
+ is 1024 virtual pixels. So for example if contents inside Flipable are to
+ be flipped around the y axis, and are wider than 1024*2, some of the
+ rendering goes behind the "camera". That's expected for rendering (since we
+ didn't provide API to change camera distance), but not ok for deciding when
+ to flip.
+*/
void QQuickFlipablePrivate::updateSide()
{
Q_Q(QQuickFlipable);
@@ -213,35 +226,50 @@ void QQuickFlipablePrivate::updateSide()
sideDirty = false;
- QTransform sceneTransform;
- itemToParentTransform(sceneTransform);
-
- QPointF p1(0, 0);
- QPointF p2(1, 0);
- QPointF p3(1, 1);
-
- QPointF scenep1 = sceneTransform.map(p1);
- QPointF scenep2 = sceneTransform.map(p2);
- QPointF scenep3 = sceneTransform.map(p3);
-#if 0
- p1 = q->mapToParent(p1);
- p2 = q->mapToParent(p2);
- p3 = q->mapToParent(p3);
-#endif
-
- qreal cross = (scenep1.x() - scenep2.x()) * (scenep3.y() - scenep2.y()) -
- (scenep1.y() - scenep2.y()) * (scenep3.x() - scenep2.x());
-
- wantBackYFlipped = scenep1.x() >= scenep2.x();
- wantBackXFlipped = scenep2.y() >= scenep3.y();
-
- QQuickFlipable::Side newSide;
- if (cross > 0) {
- newSide = QQuickFlipable::Back;
- } else {
- newSide = QQuickFlipable::Front;
+ QMatrix4x4 sceneTransform;
+
+ const qreal tx = x.value();
+ const qreal ty = y.value();
+ if (!qFuzzyIsNull(tx) || !qFuzzyIsNull(ty))
+ sceneTransform.translate(tx, ty);
+
+ for (const auto *transform : std::as_const(transforms)) {
+ if (const auto *rot = qobject_cast<const QQuickRotation *>(transform)) {
+ // rotation is a special case: we want to call rotate() instead of projectedRotate()
+ const auto angle = rot->angle();
+ const auto axis = rot->axis();
+ if (!(qFuzzyIsNull(angle) || axis.isNull())) {
+ sceneTransform.translate(rot->origin());
+ sceneTransform.rotate(angle, axis.x(), axis.y(), axis.z());
+ sceneTransform.translate(-rot->origin());
+ }
+ } else {
+ transform->applyTo(&sceneTransform);
+ }
}
+ const bool hasRotation = !qFuzzyIsNull(rotation());
+ const bool hasScale = !qFuzzyCompare(scale(), 1);
+ if (hasScale || hasRotation) {
+ QPointF tp = computeTransformOrigin();
+ sceneTransform.translate(tp.x(), tp.y());
+ if (hasScale)
+ sceneTransform.scale(scale(), scale());
+ if (hasRotation)
+ sceneTransform.rotate(rotation(), 0, 0, 1);
+ sceneTransform.translate(-tp.x(), -tp.y());
+ }
+
+ const QVector3D origin(sceneTransform.map(QPointF(0, 0)));
+ const QVector3D right = QVector3D(sceneTransform.map(QPointF(1, 0))) - origin;
+ const QVector3D top = QVector3D(sceneTransform.map(QPointF(0, 1))) - origin;
+
+ wantBackYFlipped = right.x() < 0;
+ wantBackXFlipped = top.y() < 0;
+
+ const QQuickFlipable::Side newSide =
+ QVector3D::crossProduct(top, right).z() > 0 ? QQuickFlipable::Back : QQuickFlipable::Front;
+
if (newSide != current) {
current = newSide;
if (current == QQuickFlipable::Back && back)
diff --git a/src/quick/items/qquickflipable_p.h b/src/quick/items/qquickflipable_p.h
index 78abbf740f..93f012a844 100644
--- a/src/quick/items/qquickflipable_p.h
+++ b/src/quick/items/qquickflipable_p.h
@@ -28,7 +28,7 @@ QT_REQUIRE_CONFIG(quick_flipable);
QT_BEGIN_NAMESPACE
class QQuickFlipablePrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickFlipable : public QQuickItem
+class Q_QUICK_EXPORT QQuickFlipable : public QQuickItem
{
Q_OBJECT
@@ -71,6 +71,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickFlipable)
-
#endif // QQUICKFLIPABLE_P_H
diff --git a/src/quick/items/qquickfocusscope_p.h b/src/quick/items/qquickfocusscope_p.h
index 530227f8ec..17b07d6a6e 100644
--- a/src/quick/items/qquickfocusscope_p.h
+++ b/src/quick/items/qquickfocusscope_p.h
@@ -21,7 +21,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QQuickFocusScope : public QQuickItem
+class Q_QUICK_EXPORT QQuickFocusScope : public QQuickItem
{
Q_OBJECT
QML_NAMED_ELEMENT(FocusScope)
@@ -32,6 +32,4 @@ public:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickFocusScope)
-
#endif // QQUICKFOCUSSCOPE_P_H
diff --git a/src/quick/items/qquickframebufferobject.cpp b/src/quick/items/qquickframebufferobject.cpp
index 0633d01ce4..cdae6fbec7 100644
--- a/src/quick/items/qquickframebufferobject.cpp
+++ b/src/quick/items/qquickframebufferobject.cpp
@@ -8,7 +8,7 @@
#include <private/qquickitem_p.h>
#include <private/qsgadaptationlayer_p.h>
#include <qsgtextureprovider.h>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
#include <QSGSimpleTextureNode>
#include <QSGRendererInterface>
@@ -81,7 +81,7 @@ public:
* and can be used directly in \l {ShaderEffect}{ShaderEffects} and other
* classes that consume texture providers.
*
- * \sa {Scene Graph - Rendering FBOs}, {Scene Graph and Rendering}
+ * \sa {Scene Graph and Rendering}
*/
/*!
diff --git a/src/quick/items/qquickgraphicsconfiguration.cpp b/src/quick/items/qquickgraphicsconfiguration.cpp
index ec0a9981dc..1cfa160e18 100644
--- a/src/quick/items/qquickgraphicsconfiguration.cpp
+++ b/src/quick/items/qquickgraphicsconfiguration.cpp
@@ -3,10 +3,7 @@
#include "qquickgraphicsconfiguration_p.h"
#include <QCoreApplication>
-
-#if QT_CONFIG(vulkan)
-#include <QtGui/private/qrhivulkan_p.h>
-#endif
+#include <rhi/qrhi.h>
QT_BEGIN_NAMESPACE
@@ -57,8 +54,10 @@ QT_BEGIN_NAMESPACE
Vulkan, or graphics APIs where the concept is applicable. Where some
concepts are not applicable, the related settings are simply ignored.
- Examples of functions in this category are preferredInstanceExtensions()
- and setDeviceExtensions().
+ Examples of functions in this category are setDeviceExtensions() and
+ preferredInstanceExtensions(). The latter is useful when the application
+ manages its own \l QVulkanInstance which is then associated with the
+ QQuickWindow via \l QWindow::setVulkanInstance().
\section1 Qt Quick Scene Graph Renderer Configuration
@@ -181,7 +180,8 @@ QT_BEGIN_NAMESPACE
- With pipeline cache saving enabled, Qt stores all render and compute
pipelines encountered into an MTLBinaryArchive. Saving the pipeline cache
stores the blob retrieved from the archive, with additional metadata to
- identify the device.
+ identify the device. \b{Note:} currently MTLBinaryArchive usage is disabled
+ on macOS and iOS due to various issues on some hardware and OS versions.
\li OpenGL - There is no native concept of pipelines, the "pipeline cache"
stores a collection of program binaries retrieved via
@@ -190,7 +190,7 @@ QT_BEGIN_NAMESPACE
metadata to identify the device, driver, and its version that the binaries
were retrieved from. Persistent caching of program binaries is not new in
Qt: Qt 5 already had similar functionality in QOpenGLShaderProgram, see
- \l{QOpenGLShaderProgram::addCacheableShaderFromSourceCode()}{addCacheableShaderFromSourceCode()}
+ \l{QOpenGLShaderProgram::}{addCacheableShaderFromSourceCode()}
for example. In fact that mechanism is always active in Qt 6 as well when
using Qt Quick with OpenGL. However, when using the new, graphics API
independent pipeline cache abstraction provided here, the Qt 5 era program
@@ -212,24 +212,25 @@ QT_BEGIN_NAMESPACE
HLSL shader. A good example is Qt Quick 3D, where the runtime-generated
shaders for materials imply having to deal with HLSL source code. Saving
and reloading the Qt Quick pipeline cache can therefore bring considerable
- improvements in scenes with one or more \l{QtQuick3D::}View3D items in
+ improvements in scenes with one or more \l{View3D} items in
them. A counterexample may be Qt Quick itself: as most built-in shaders for
2D content ship with DirectX bytecode generated at build time, the cache is
not going to present any significant improvements.
\endlist
- All this is independent from the shader processing performed by the \l
- QtShaderTools module and its command-line tools such as \c qsb. As an
- example, take Vulkan. Having the Vulkan-compatible GLSL source code
- compiled to SPIR-V either at offline or build time (directly via qsb or
- CMake) is good, because the expensive compilation from source form is
- avoided at run time. SPIR-V is however a vendor-independent intermediate
- format. At runtime, when constructing graphics or compute pipelines, there
- is likely another round of compilation happening, this time from the
- intermediate format to the vendor-specific instruction set of the GPU (and
- this may be dependent on certain state in the graphics pipeline and the
- render targets as well). The pipeline cache helps with this latter phase.
+ All this is independent from the shader processing performed by the
+ \l [QtShaderTools]{Qt Shader Tools} module and its command-line tools such
+ as \c qsb. As an example, take Vulkan. Having the Vulkan-compatible GLSL
+ source code compiled to SPIR-V either at offline or build time (directly
+ via qsb or CMake) is good, because the expensive compilation from source
+ form is avoided at run time. SPIR-V is however a vendor-independent
+ intermediate format. At runtime, when constructing graphics or compute
+ pipelines, there is likely another round of compilation happening, this
+ time from the intermediate format to the vendor-specific instruction set of
+ the GPU (and this may be dependent on certain state in the graphics
+ pipeline and the render targets as well). The pipeline cache helps with
+ this latter phase.
\note Many graphics API implementation employ their own persistent disk
cache transparently to the applications. Using the pipeline cache feature
@@ -572,6 +573,51 @@ bool QQuickGraphicsConfiguration::isDebugMarkersEnabled() const
}
/*!
+ When enabled, GPU timing data is collected from command buffers on
+ platforms and 3D APIs where this is supported. This data is then printed in
+ the renderer logs that can be enabled via \c{QSG_RENDER_TIMING} environment
+ variable or logging categories such as \c{qt.scenegraph.time.renderloop},
+ and may also be made visible to other modules, such as Qt Quick 3D's
+ \l DebugView item.
+
+ By default this is disabled, because collecting the data may involve
+ additional work, such as inserting timestamp queries in the command stream,
+ depending on the underlying graphics API. To enable, either call this
+ function with \a enable set to true, or set the \c{QSG_RHI_PROFILE}
+ environment variable to a non-zero value.
+
+ Graphics APIs where this can be expected to be supported are Direct 3D 11,
+ Direct 3D 12, Vulkan (as long as the underlying Vulkan implementation
+ supports timestamp queries), Metal, and OpenGL with a core or compatibility
+ profile context for version 3.3 or newer. Timestamps are not supported with
+ OpenGL ES.
+
+ \since 6.6
+
+ \sa timestampsEnabled(), setDebugMarkers()
+ */
+void QQuickGraphicsConfiguration::setTimestamps(bool enable)
+{
+ if (d->flags.testFlag(QQuickGraphicsConfigurationPrivate::EnableTimestamps) != enable) {
+ detach();
+ d->flags.setFlag(QQuickGraphicsConfigurationPrivate::EnableTimestamps, enable);
+ }
+}
+
+/*!
+ \return true if GPU timing collection is enabled.
+
+ By default the value is false.
+
+ \since 6.6
+ \sa setTimestamps()
+ */
+bool QQuickGraphicsConfiguration::timestampsEnabled() const
+{
+ return d->flags.testFlag(QQuickGraphicsConfigurationPrivate::EnableTimestamps);
+}
+
+/*!
Requests choosing an adapter or physical device that uses software-based
rasterization. Applicable only when the underlying API has support for
enumerating adapters (for example, Direct 3D or Vulkan), and is ignored
@@ -624,7 +670,7 @@ bool QQuickGraphicsConfiguration::prefersSoftwareDevice() const
\since 6.5
- \sa isAutomaticPipelineCacheEnbled()
+ \sa isAutomaticPipelineCacheEnabled()
*/
void QQuickGraphicsConfiguration::setAutomaticPipelineCache(bool enable)
{
@@ -779,9 +825,9 @@ QQuickGraphicsConfigurationPrivate::QQuickGraphicsConfigurationPrivate()
if (enableDebugLayer)
flags |= EnableDebugLayer;
- static const bool enableDebugMarkers = qEnvironmentVariableIntValue("QSG_RHI_PROFILE");
- if (enableDebugMarkers)
- flags |= EnableDebugMarkers;
+ static const bool enableProfilingRelated = qEnvironmentVariableIntValue("QSG_RHI_PROFILE");
+ if (enableProfilingRelated)
+ flags |= EnableDebugMarkers | EnableTimestamps;
static const bool preferSoftwareDevice = qEnvironmentVariableIntValue("QSG_RHI_PREFER_SOFTWARE_RENDERER");
if (preferSoftwareDevice)
@@ -801,12 +847,12 @@ QQuickGraphicsConfigurationPrivate::QQuickGraphicsConfigurationPrivate()
pipelineCacheLoadFile = pipelineCacheLoadFileEnv;
}
-QQuickGraphicsConfigurationPrivate::QQuickGraphicsConfigurationPrivate(const QQuickGraphicsConfigurationPrivate *other)
+QQuickGraphicsConfigurationPrivate::QQuickGraphicsConfigurationPrivate(const QQuickGraphicsConfigurationPrivate &other)
: ref(1),
- deviceExtensions(other->deviceExtensions),
- flags(other->flags),
- pipelineCacheSaveFile(other->pipelineCacheSaveFile),
- pipelineCacheLoadFile(other->pipelineCacheLoadFile)
+ deviceExtensions(other.deviceExtensions),
+ flags(other.flags),
+ pipelineCacheSaveFile(other.pipelineCacheSaveFile),
+ pipelineCacheLoadFile(other.pipelineCacheLoadFile)
{
}
diff --git a/src/quick/items/qquickgraphicsconfiguration.h b/src/quick/items/qquickgraphicsconfiguration.h
index d520a4a5ab..95777a1c51 100644
--- a/src/quick/items/qquickgraphicsconfiguration.h
+++ b/src/quick/items/qquickgraphicsconfiguration.h
@@ -34,6 +34,9 @@ public:
void setDebugMarkers(bool enable);
bool isDebugMarkersEnabled() const;
+ void setTimestamps(bool enable);
+ bool timestampsEnabled() const;
+
void setPreferSoftwareDevice(bool enable);
bool prefersSoftwareDevice() const;
diff --git a/src/quick/items/qquickgraphicsconfiguration_p.h b/src/quick/items/qquickgraphicsconfiguration_p.h
index 12980d154a..08b5e6026f 100644
--- a/src/quick/items/qquickgraphicsconfiguration_p.h
+++ b/src/quick/items/qquickgraphicsconfiguration_p.h
@@ -21,20 +21,21 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QQuickGraphicsConfigurationPrivate
+class Q_QUICK_EXPORT QQuickGraphicsConfigurationPrivate
{
public:
static QQuickGraphicsConfigurationPrivate *get(QQuickGraphicsConfiguration *p) { return p->d; }
static const QQuickGraphicsConfigurationPrivate *get(const QQuickGraphicsConfiguration *p) { return p->d; }
QQuickGraphicsConfigurationPrivate();
- QQuickGraphicsConfigurationPrivate(const QQuickGraphicsConfigurationPrivate *other);
+ QQuickGraphicsConfigurationPrivate(const QQuickGraphicsConfigurationPrivate &other);
enum Flag {
UseDepthBufferFor2D = 0x01,
EnableDebugLayer = 0x02,
EnableDebugMarkers = 0x04,
PreferSoftwareDevice = 0x08,
- AutoPipelineCache = 0x10
+ AutoPipelineCache = 0x10,
+ EnableTimestamps = 0x20
};
Q_DECLARE_FLAGS(Flags, Flag)
diff --git a/src/quick/items/qquickgraphicsdevice.cpp b/src/quick/items/qquickgraphicsdevice.cpp
index 5bd0865660..84eeb3e69e 100644
--- a/src/quick/items/qquickgraphicsdevice.cpp
+++ b/src/quick/items/qquickgraphicsdevice.cpp
@@ -93,11 +93,14 @@ QQuickGraphicsDevice QQuickGraphicsDevice::fromOpenGLContext(QOpenGLContext *con
/*!
\return a new QQuickGraphicsDevice describing a DXGI adapter and D3D feature level.
- This factory function is suitable for Direct3D 11, particularly in
+ This factory function is suitable for Direct3D 11 and 12, particularly in
combination with OpenXR. \a adapterLuidLow and \a adapterLuidHigh together
specify a LUID, while a featureLevel specifies a \c{D3D_FEATURE_LEVEL_}
value. \a featureLevel can be set to 0 if it is not intended to be
specified, in which case the scene graph's defaults will be used.
+
+ \note With Direct 3D 12 \a featureLevel specifies the \c minimum feature
+ level passed on to D3D12CreateDevice().
*/
#if defined(Q_OS_WIN) || defined(Q_QDOC)
QQuickGraphicsDevice QQuickGraphicsDevice::fromAdapter(quint32 adapterLuidLow,
@@ -120,6 +123,10 @@ QQuickGraphicsDevice QQuickGraphicsDevice::fromAdapter(quint32 adapterLuidLow,
be a \c{ID3D11Device*}, \a context is expected to be a
\c{ID3D11DeviceContext*}.
+ It also supports Direct 3D 12, if that is the 3D API used at run time. With
+ D3D12 \a context is unused and can be set to null. \a device is expected to
+ be a \c{ID3D12Device*}.
+
\note the resulting QQuickGraphicsDevice does not own any native resources,
it merely contains references. It is the caller's responsibility to ensure
that the native resource exists as long as necessary.
@@ -146,7 +153,7 @@ QQuickGraphicsDevice QQuickGraphicsDevice::fromDeviceAndContext(void *device, vo
that the native resource exists as long as necessary.
*/
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) || defined(Q_QDOC)
+#if QT_CONFIG(metal) || defined(Q_QDOC)
QQuickGraphicsDevice QQuickGraphicsDevice::fromDeviceAndCommandQueue(MTLDevice *device,
MTLCommandQueue *commandQueue)
{
@@ -205,12 +212,14 @@ QQuickGraphicsDevice QQuickGraphicsDevice::fromDeviceObjects(VkPhysicalDevice ph
#endif
/*!
- \internal
+ \return a new QQuickGraphicsDevice referencing an existing \a rhi object.
\note Similarly to fromOpenGLContext(), the caller must be careful to only
share a QRhi (and so the underlying graphics context or device) between
QQuickWindows that are known to be compatible, not breaking the underlying
graphics API's rules when it comes to threading, pixel formats, etc.
+
+ \since 6.6
*/
QQuickGraphicsDevice QQuickGraphicsDevice::fromRhi(QRhi *rhi)
{
@@ -226,10 +235,10 @@ QQuickGraphicsDevicePrivate::QQuickGraphicsDevicePrivate()
{
}
-QQuickGraphicsDevicePrivate::QQuickGraphicsDevicePrivate(const QQuickGraphicsDevicePrivate *other)
+QQuickGraphicsDevicePrivate::QQuickGraphicsDevicePrivate(const QQuickGraphicsDevicePrivate &other)
: ref(1),
- type(other->type),
- u(other->u)
+ type(other.type),
+ u(other.u)
{
}
diff --git a/src/quick/items/qquickgraphicsdevice.h b/src/quick/items/qquickgraphicsdevice.h
index 40c8020a2d..d668f5e576 100644
--- a/src/quick/items/qquickgraphicsdevice.h
+++ b/src/quick/items/qquickgraphicsdevice.h
@@ -10,7 +10,7 @@
#include <QtGui/qvulkaninstance.h>
#endif
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) || defined(Q_QDOC)
+#if QT_CONFIG(metal) || defined(Q_QDOC)
Q_FORWARD_DECLARE_OBJC_CLASS(MTLDevice);
Q_FORWARD_DECLARE_OBJC_CLASS(MTLCommandQueue);
#endif
@@ -40,7 +40,7 @@ public:
static QQuickGraphicsDevice fromDeviceAndContext(void *device, void *context);
#endif
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) || defined(Q_QDOC)
+#if QT_CONFIG(metal) || defined(Q_QDOC)
static QQuickGraphicsDevice fromDeviceAndCommandQueue(MTLDevice *device, MTLCommandQueue *commandQueue);
#endif
diff --git a/src/quick/items/qquickgraphicsdevice_p.h b/src/quick/items/qquickgraphicsdevice_p.h
index 74f1e3af6e..d1c3bf1284 100644
--- a/src/quick/items/qquickgraphicsdevice_p.h
+++ b/src/quick/items/qquickgraphicsdevice_p.h
@@ -21,13 +21,13 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QQuickGraphicsDevicePrivate
+class Q_QUICK_EXPORT QQuickGraphicsDevicePrivate
{
public:
static QQuickGraphicsDevicePrivate *get(QQuickGraphicsDevice *p) { return p->d; }
static const QQuickGraphicsDevicePrivate *get(const QQuickGraphicsDevice *p) { return p->d; }
QQuickGraphicsDevicePrivate();
- QQuickGraphicsDevicePrivate(const QQuickGraphicsDevicePrivate *other);
+ QQuickGraphicsDevicePrivate(const QQuickGraphicsDevicePrivate &other);
enum class Type {
Null,
diff --git a/src/quick/items/qquickgraphicsinfo.cpp b/src/quick/items/qquickgraphicsinfo.cpp
index 45c86f1b24..7b84a7eb54 100644
--- a/src/quick/items/qquickgraphicsinfo.cpp
+++ b/src/quick/items/qquickgraphicsinfo.cpp
@@ -56,16 +56,16 @@ QQuickGraphicsInfo *QQuickGraphicsInfo::qmlAttachedProperties(QObject *object)
This property describes the graphics API that is currently in use.
The possible values are:
- \list
- \li GraphicsInfo.Unknown - the default value when no active scenegraph is associated with the item
- \li GraphicsInfo.Software - Qt Quick's software renderer based on QPainter with the raster paint engine
- \li GraphicsInfo.OpenVG - OpenVG
- \li GraphicsInfo.OpenGL - OpenGL or OpenGL ES on top of QRhi, a graphics abstraction layer
- \li GraphicsInfo.Direct3D11 - Direct3D 11 on top of QRhi, a graphics abstraction layer
- \li GraphicsInfo.Vulkan - Vulkan on top of QRhi, a graphics abstraction layer
- \li GraphicsInfo.Metal - Metal on top of QRhi, a graphics abstraction layer
- \li GraphicsInfo.Null - Null (no output) on top of QRhi, a graphics abstraction layer
- \endlist
+
+ \value GraphicsInfo.Unknown the default value when no active scenegraph is associated with the item
+ \value GraphicsInfo.Software Qt Quick's software renderer based on QPainter with the raster paint engine
+ \value GraphicsInfo.OpenVG OpenVG
+ \value GraphicsInfo.OpenGL OpenGL or OpenGL ES on top of QRhi, a graphics abstraction layer
+ \value GraphicsInfo.Direct3D11 Direct3D 11 on top of QRhi, a graphics abstraction layer
+ \value GraphicsInfo.Direct3D12 Direct3D 12 on top of QRhi, a graphics abstraction layer
+ \value GraphicsInfo.Vulkan Vulkan on top of QRhi, a graphics abstraction layer
+ \value GraphicsInfo.Metal Metal on top of QRhi, a graphics abstraction layer
+ \value GraphicsInfo.Null Null (no output) on top of QRhi, a graphics abstraction layer
*/
/*!
@@ -74,12 +74,10 @@ QQuickGraphicsInfo *QQuickGraphicsInfo::qmlAttachedProperties(QObject *object)
This property contains the shading language supported by the Qt Quick
backend the application is using.
- \list
- \li GraphicsInfo.UnknownShadingLanguage - Not yet known due to no window and scenegraph associated
- \li GraphicsInfo.GLSL - GLSL or GLSL ES
- \li GraphicsInfo.HLSL - HLSL
- \li GraphicsInfo.RhiShader - QShader
- \endlist
+ \value GraphicsInfo.UnknownShadingLanguage Not yet known due to no window and scenegraph associated
+ \value GraphicsInfo.GLSL GLSL or GLSL ES
+ \value GraphicsInfo.HLSL HLSL
+ \value GraphicsInfo.RhiShader QShader
\note The value is only up-to-date once the item is associated with a
window. Bindings relying on the value have to keep this in mind since the
@@ -100,10 +98,8 @@ QQuickGraphicsInfo *QQuickGraphicsInfo::qmlAttachedProperties(QObject *object)
This property contains a bitmask of the shader compilation approaches
supported by the Qt Quick backend the application is using.
- \list
- \li GraphicsInfo.RuntimeCompilation
- \li GraphicsInfo.OfflineCompilation
- \endlist
+ \value GraphicsInfo.RuntimeCompilation
+ \value GraphicsInfo.OfflineCompilation
With OpenGL the value is GraphicsInfo.RuntimeCompilation, which corresponds
to the traditional way of using ShaderEffect. Non-OpenGL backends are
@@ -127,11 +123,9 @@ QQuickGraphicsInfo *QQuickGraphicsInfo::qmlAttachedProperties(QObject *object)
This property contains a bitmask of the supported ways of providing shader
sources.
- \list
- \li GraphicsInfo.ShaderSourceString
- \li GraphicsInfo.ShaderSourceFile
- \li GraphicsInfo.ShaderByteCode
- \endlist
+ \value GraphicsInfo.ShaderSourceString
+ \value GraphicsInfo.ShaderSourceFile
+ \value GraphicsInfo.ShaderByteCode
With OpenGL the value is GraphicsInfo.ShaderSourceString, which corresponds
to the traditional way of inlining GLSL source code into QML. Other,
@@ -182,11 +176,10 @@ QQuickGraphicsInfo *QQuickGraphicsInfo::qmlAttachedProperties(QObject *object)
This property holds the configured OpenGL context profile.
The possible values are:
- \list
- \li GraphicsInfo.OpenGLNoProfile (default) - OpenGL version is lower than 3.2 or OpenGL is not in use.
- \li GraphicsInfo.OpenGLCoreProfile - Functionality deprecated in OpenGL version 3.0 is not available.
- \li GraphicsInfo.OpenGLCompatibilityProfile - Functionality from earlier OpenGL versions is available.
- \endlist
+
+ \value GraphicsInfo.OpenGLNoProfile (default) OpenGL version is lower than 3.2 or OpenGL is not in use.
+ \value GraphicsInfo.OpenGLCoreProfile Functionality deprecated in OpenGL version 3.0 is not available.
+ \value GraphicsInfo.OpenGLCompatibilityProfile Functionality from earlier OpenGL versions is available.
Reusable QML components will typically use this property in bindings in order to
choose between core and non core profile compatible shader sources.
@@ -203,11 +196,10 @@ QQuickGraphicsInfo *QQuickGraphicsInfo::qmlAttachedProperties(QObject *object)
other than OpenGL.
The possible values are:
- \list
- \li GraphicsInfo.SurfaceFormatUnspecified (default) - Unspecified rendering method
- \li GraphicsInfo.SurfaceFormatOpenGL - Desktop OpenGL or other graphics API
- \li GraphicsInfo.SurfaceFormatOpenGLES - OpenGL ES
- \endlist
+
+ \value GraphicsInfo.SurfaceFormatUnspecified (default) Unspecified rendering method
+ \value GraphicsInfo.SurfaceFormatOpenGL Desktop OpenGL or other graphics API
+ \value GraphicsInfo.SurfaceFormatOpenGLES OpenGL ES
\note This is applicable only to OpenGL.
diff --git a/src/quick/items/qquickgraphicsinfo_p.h b/src/quick/items/qquickgraphicsinfo_p.h
index be3bef7fa9..a18694a83b 100644
--- a/src/quick/items/qquickgraphicsinfo_p.h
+++ b/src/quick/items/qquickgraphicsinfo_p.h
@@ -55,6 +55,7 @@ public:
Vulkan = QSGRendererInterface::Vulkan,
Metal = QSGRendererInterface::Metal,
Null = QSGRendererInterface::Null,
+ Direct3D12 = QSGRendererInterface::Direct3D12,
OpenGLRhi = QSGRendererInterface::OpenGLRhi,
Direct3D11Rhi = QSGRendererInterface::Direct3D11Rhi,
diff --git a/src/quick/items/qquickgridview.cpp b/src/quick/items/qquickgridview.cpp
index 7f45d45708..e67d80b2d6 100644
--- a/src/quick/items/qquickgridview.cpp
+++ b/src/quick/items/qquickgridview.cpp
@@ -162,7 +162,9 @@ public:
void setPosition(qreal pos) override;
void layoutVisibleItems(int fromModelIndex = 0) override;
bool applyInsertionChange(const QQmlChangeSet::Change &insert, ChangeResult *changeResult, QList<FxViewItem *> *addedItems, QList<MovedItem> *movingIntoView) override;
+#if QT_CONFIG(quick_viewtransitions)
void translateAndTransitionItemsAfter(int afterModelIndex, const ChangeResult &insertionResult, const ChangeResult &removalResult) override;
+#endif
bool needsRefillForAddedOrRemovedIndex(int index) const override;
qreal headerSize() const override;
@@ -181,7 +183,7 @@ public:
void fixupPosition() override;
void fixup(AxisData &data, qreal minExtent, qreal maxExtent) override;
bool flick(QQuickItemViewPrivate::AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize,
- QQuickTimeLineCallback::Callback fixupCallback, qreal velocity) override;
+ QQuickTimeLineCallback::Callback fixupCallback, QEvent::Type eventType, qreal velocity) override;
QQuickGridView::Flow flow;
qreal cellWidth;
@@ -503,8 +505,10 @@ bool QQuickGridViewPrivate::addVisibleItems(qreal fillFrom, qreal fillTo, qreal
qCDebug(lcItemViewDelegateLifecycle) << "refill: append item" << modelIndex << colPos << rowPos;
if (!(item = static_cast<FxGridItemSG*>(createItem(modelIndex, incubationMode))))
break;
+#if QT_CONFIG(quick_viewtransitions)
if (!transitioner || !transitioner->canTransition(QQuickItemViewTransitioner::PopulateTransition, true)) // pos will be set by layoutVisibleItems()
item->setPosition(colPos, rowPos, true);
+#endif
QQuickItemPrivate::get(item->item)->setCulled(doBuffer);
visibleItems.append(item);
if (++colNum >= columns) {
@@ -538,8 +542,10 @@ bool QQuickGridViewPrivate::addVisibleItems(qreal fillFrom, qreal fillTo, qreal
if (!(item = static_cast<FxGridItemSG*>(createItem(visibleIndex-1, incubationMode))))
break;
--visibleIndex;
+#if QT_CONFIG(quick_viewtransitions)
if (!transitioner || !transitioner->canTransition(QQuickItemViewTransitioner::PopulateTransition, true)) // pos will be set by layoutVisibleItems()
item->setPosition(colPos, rowPos, true);
+#endif
QQuickItemPrivate::get(item->item)->setCulled(doBuffer);
visibleItems.prepend(item);
if (--colNum < 0) {
@@ -555,11 +561,14 @@ bool QQuickGridViewPrivate::addVisibleItems(qreal fillFrom, qreal fillTo, qreal
void QQuickGridViewPrivate::removeItem(FxViewItem *item)
{
+#if QT_CONFIG(quick_viewtransitions)
if (item->transitionScheduledOrRunning()) {
qCDebug(lcItemViewDelegateLifecycle) << "\tnot releasing animating item:" << item->index << item->item->objectName();
item->releaseAfterTransition = true;
releasePendingTransition.append(item);
- } else {
+ } else
+#endif
+ {
releaseItem(item, QQmlDelegateModel::NotReusable);
}
}
@@ -883,7 +892,11 @@ void QQuickGridViewPrivate::initializeCurrentItem()
FxViewItem *actualItem = visibleItem(currentIndex);
// don't reposition the item if it's about to be transitioned to another position
- if ((!actualItem || !actualItem->transitionScheduledOrRunning()))
+ if ((!actualItem
+#if QT_CONFIG(quick_viewtransitions)
+ || !actualItem->transitionScheduledOrRunning()
+#endif
+ ))
gridItem->setPosition(colPosAt(currentIndex), rowPosAt(currentIndex));
}
}
@@ -995,13 +1008,13 @@ void QQuickGridViewPrivate::fixup(AxisData &data, qreal minExtent, qreal maxExte
}
bool QQuickGridViewPrivate::flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize,
- QQuickTimeLineCallback::Callback fixupCallback, qreal velocity)
+ QQuickTimeLineCallback::Callback fixupCallback, QEvent::Type eventType, qreal velocity)
{
data.fixingUp = false;
moveReason = Mouse;
if ((!haveHighlightRange || highlightRange != QQuickGridView::StrictlyEnforceRange)
&& snapMode == QQuickGridView::NoSnap) {
- return QQuickItemViewPrivate::flick(data, minExtent, maxExtent, vSize, fixupCallback, velocity);
+ return QQuickItemViewPrivate::flick(data, minExtent, maxExtent, vSize, fixupCallback, eventType, velocity);
}
qreal maxDistance = 0;
qreal dataValue = isContentFlowReversed() ? -data.move.value()+size() : data.move.value();
@@ -1051,7 +1064,7 @@ bool QQuickGridViewPrivate::flick(AxisData &data, qreal minExtent, qreal maxExte
else
v = maxVelocity;
}
- qreal accel = deceleration;
+ qreal accel = eventType == QEvent::Wheel ? wheelDeceleration : deceleration;
qreal v2 = v * v;
qreal overshootDist = 0.0;
if ((maxDistance > 0.0 && v2 / (2.0f * maxDistance) < accel) || snapMode == QQuickGridView::SnapOneRow) {
@@ -1253,23 +1266,28 @@ void QQuickGridView::setHighlightFollowsCurrentItem(bool autoHighlight)
/*!
\qmlattachedproperty bool QtQuick::GridView::isCurrentItem
+ \readonly
+
This attached property is true if this delegate is the current item; otherwise false.
It is attached to each instance of the delegate.
+
+ \snippet qml/gridview/gridview.qml isCurrentItem
*/
/*!
\qmlattachedproperty GridView QtQuick::GridView::view
+ \readonly
+
This attached property holds the view that manages this delegate instance.
It is attached to each instance of the delegate and also to the header, the footer
and the highlight delegates.
-
- \snippet qml/gridview/gridview.qml isCurrentItem
*/
/*!
\qmlattachedproperty bool QtQuick::GridView::delayRemove
+
This attached property holds whether the delegate may be destroyed. It
is attached to each instance of the delegate. The default value is false.
@@ -1364,9 +1382,53 @@ void QQuickGridView::setHighlightFollowsCurrentItem(bool autoHighlight)
/*!
\qmlproperty int QtQuick::GridView::count
- This property holds the number of items in the view.
+ This property holds the number of items in the model.
+*/
+
+/*!
+ \qmlproperty bool QtQuick::GridView::reuseItems
+
+ This property enables you to reuse items that are instantiated
+ from the \l delegate. If set to \c false, any currently
+ pooled items are destroyed.
+
+ This property is \c false by default.
+
+ \since 5.15
+
+ \sa {Reusing items}, pooled(), reused()
+*/
+
+/*!
+ \qmlattachedsignal QtQuick::GridView::pooled()
+
+ This signal is emitted after an item has been added to the reuse
+ pool. You can use it to pause ongoing timers or animations inside
+ the item, or free up resources that cannot be reused.
+
+ This signal is emitted only if the \l reuseItems property is \c true.
+
+ \sa {Reusing items}, reuseItems, reused()
*/
+/*!
+ \qmlattachedsignal QtQuick::GridView::reused()
+
+ This signal is emitted after an item has been reused. At this point, the
+ item has been taken out of the pool and placed inside the content view,
+ and the model properties such as \c index and \c row have been updated.
+
+ Other properties that are not provided by the model does not change when an
+ item is reused. You should avoid storing any state inside a delegate, but if
+ you do, manually reset that state on receiving this signal.
+
+ This signal is emitted when the item is reused, and not the first time the
+ item is created.
+
+ This signal is emitted only if the \l reuseItems property is \c true.
+
+ \sa {Reusing items}, reuseItems, pooled()
+*/
/*!
\qmlproperty Component QtQuick::GridView::highlight
@@ -1427,36 +1489,32 @@ void QQuickGridView::setHighlightFollowsCurrentItem(bool autoHighlight)
Valid values for \c highlightRangeMode are:
- \list
- \li GridView.ApplyRange - the view attempts to maintain the highlight within the range.
+ \value GridView.ApplyRange the view attempts to maintain the highlight within the range.
However, the highlight can move outside of the range at the ends of the view or due
to mouse interaction.
- \li GridView.StrictlyEnforceRange - the highlight never moves outside of the range.
+ \value GridView.StrictlyEnforceRange the highlight never moves outside of the range.
The current item changes if a keyboard or mouse action would cause the highlight to move
outside of the range.
- \li GridView.NoHighlightRange - this is the default value.
- \endlist
+ \value GridView.NoHighlightRange the default value
*/
/*!
- \qmlproperty enumeration QtQuick::GridView::layoutDirection
- This property holds the layout direction of the grid.
+ \qmlproperty enumeration QtQuick::GridView::layoutDirection
+ This property holds the layout direction of the grid.
Possible values:
- \list
- \li Qt.LeftToRight (default) - Items will be laid out starting in the top, left corner. The flow is
- dependent on the \l GridView::flow property.
- \li Qt.RightToLeft - Items will be laid out starting in the top, right corner. The flow is dependent
- on the \l GridView::flow property.
- \endlist
+ \value Qt.LeftToRight (default) Items will be laid out starting in the top, left corner. The flow is
+ dependent on the \l GridView::flow property.
+ \value Qt.RightToLeft Items will be laid out starting in the top, right corner. The flow is dependent
+ on the \l GridView::flow property.
- \b Note: If GridView::flow is set to GridView.FlowLeftToRight, this is not to be confused if
- GridView::layoutDirection is set to Qt.RightToLeft. The GridView.FlowLeftToRight flow value simply
- indicates that the flow is horizontal.
+ \b Note: If GridView::flow is set to GridView.FlowLeftToRight, this is not to be confused if
+ GridView::layoutDirection is set to Qt.RightToLeft. The GridView.FlowLeftToRight flow value simply
+ indicates that the flow is horizontal.
- \sa GridView::effectiveLayoutDirection, GridView::verticalLayoutDirection
+ \sa GridView::effectiveLayoutDirection, GridView::verticalLayoutDirection
*/
@@ -1477,10 +1535,8 @@ void QQuickGridView::setHighlightFollowsCurrentItem(bool autoHighlight)
Possible values:
- \list
- \li GridView.TopToBottom (default) - Items are laid out from the top of the view down to the bottom of the view.
- \li GridView.BottomToTop - Items are laid out from the bottom of the view up to the top of the view.
- \endlist
+ \value GridView.TopToBottom (default) Items are laid out from the top of the view down to the bottom of the view.
+ \value GridView.BottomToTop Items are laid out from the bottom of the view up to the top of the view.
\sa GridView::layoutDirection
*/
@@ -1588,10 +1644,8 @@ void QQuickGridView::setHighlightMoveDuration(int duration)
Possible values:
- \list
- \li GridView.FlowLeftToRight (default) - Items are laid out from left to right, and the view scrolls vertically
- \li GridView.FlowTopToBottom - Items are laid out from top to bottom, and the view scrolls horizontally
- \endlist
+ \value GridView.FlowLeftToRight (default) Items are laid out from left to right, and the view scrolls vertically
+ \value GridView.FlowTopToBottom Items are laid out from top to bottom, and the view scrolls horizontally
*/
QQuickGridView::Flow QQuickGridView::flow() const
{
@@ -1668,15 +1722,12 @@ void QQuickGridView::setCellHeight(qreal cellHeight)
This property determines how the view scrolling will settle following a drag or flick.
The possible values are:
- \list
- \li GridView.NoSnap (default) - the view stops anywhere within the visible area.
- \li GridView.SnapToRow - the view settles with a row (or column for \c GridView.FlowTopToBottom flow)
- aligned with the start of the view.
- \li GridView.SnapOneRow - the view will settle no more than one row (or column for \c GridView.FlowTopToBottom flow)
- away from the first visible row at the time the mouse button is released.
- This mode is particularly useful for moving one page at a time.
- \endlist
-
+ \value GridView.NoSnap (default) the view stops anywhere within the visible area.
+ \value GridView.SnapToRow the view settles with a row (or column for \c GridView.FlowTopToBottom flow)
+ aligned with the start of the view.
+ \value GridView.SnapOneRow the view will settle no more than one row (or column for \c GridView.FlowTopToBottom flow)
+ away from the first visible row at the time the mouse button is released.
+ This mode is particularly useful for moving one page at a time.
*/
QQuickGridView::SnapMode QQuickGridView::snapMode() const
{
@@ -2356,6 +2407,9 @@ bool QQuickGridViewPrivate::applyInsertionChange(const QQmlChangeSet::Change &ch
{
Q_Q(QQuickGridView);
+ if (q->size().isEmpty())
+ return false;
+
int modelIndex = change.index;
int count = change.count;
@@ -2404,6 +2458,7 @@ bool QQuickGridViewPrivate::applyInsertionChange(const QQmlChangeSet::Change &ch
}
}
+#if QT_CONFIG(quick_viewtransitions)
// Update the indexes of the following visible items.
for (FxViewItem *item : std::as_const(visibleItems)) {
if (item->index != -1 && item->index >= modelIndex) {
@@ -2414,6 +2469,7 @@ bool QQuickGridViewPrivate::applyInsertionChange(const QQmlChangeSet::Change &ch
item->transitionNextReposition(transitioner, QQuickItemViewTransitioner::AddTransition, false);
}
}
+#endif
int prevVisibleCount = visibleItems.size();
if (insertResult->visiblePos.isValid() && rowPos < insertResult->visiblePos) {
@@ -2443,9 +2499,11 @@ bool QQuickGridViewPrivate::applyInsertionChange(const QQmlChangeSet::Change &ch
insertResult->changedFirstItem = true;
if (!change.isMove()) {
addedItems->append(item);
+#if QT_CONFIG(quick_viewtransitions)
if (transitioner)
item->transitionNextReposition(transitioner, QQuickItemViewTransitioner::AddTransition, true);
else
+#endif
item->moveTo(QPointF(colPos, rowPos), true);
}
insertResult->sizeChangesBeforeVisiblePos += rowSize();
@@ -2497,13 +2555,19 @@ bool QQuickGridViewPrivate::applyInsertionChange(const QQmlChangeSet::Change &ch
if (change.isMove()) {
// we know this is a move target, since move displaced items that are
// shuffled into view due to a move would be added in refill()
- if (newItem && transitioner && transitioner->canTransition(QQuickItemViewTransitioner::MoveTransition, true))
+ if (newItem
+#if QT_CONFIG(quick_viewtransitions)
+ && transitioner && transitioner->canTransition(QQuickItemViewTransitioner::MoveTransition, true)
+#endif
+ )
movingIntoView->append(MovedItem(item, change.moveKey(item->index)));
} else {
addedItems->append(item);
+#if QT_CONFIG(quick_viewtransitions)
if (transitioner)
item->transitionNextReposition(transitioner, QQuickItemViewTransitioner::AddTransition, true);
else
+#endif
item->moveTo(QPointF(colPos, rowPos), true);
}
insertResult->sizeChangesAfterVisiblePos += rowSize();
@@ -2523,6 +2587,7 @@ bool QQuickGridViewPrivate::applyInsertionChange(const QQmlChangeSet::Change &ch
return visibleItems.size() > prevVisibleCount;
}
+#if QT_CONFIG(quick_viewtransitions)
void QQuickGridViewPrivate::translateAndTransitionItemsAfter(int afterModelIndex, const ChangeResult &insertionResult, const ChangeResult &removalResult)
{
if (!transitioner)
@@ -2562,6 +2627,7 @@ void QQuickGridViewPrivate::translateAndTransitionItemsAfter(int afterModelIndex
}
}
}
+#endif
bool QQuickGridViewPrivate::needsRefillForAddedOrRemovedIndex(int modelIndex) const
{
@@ -2576,18 +2642,15 @@ bool QQuickGridViewPrivate::needsRefillForAddedOrRemovedIndex(int modelIndex) co
Positions the view such that the \a index is at the position specified by
\a mode:
- \list
- \li GridView.Beginning - position item at the top (or left for \c GridView.FlowTopToBottom flow) of the view.
- \li GridView.Center - position item in the center of the view.
- \li GridView.End - position item at bottom (or right for horizontal orientation) of the view.
- \li GridView.Visible - if any part of the item is visible then take no action, otherwise
- bring the item into view.
- \li GridView.Contain - ensure the entire item is visible. If the item is larger than
- the view the item is positioned at the top (or left for \c GridView.FlowTopToBottom flow) of the view.
- \li GridView.SnapPosition - position the item at \l preferredHighlightBegin. This mode
- is only valid if \l highlightRangeMode is StrictlyEnforceRange or snapping is enabled
- via \l snapMode.
- \endlist
+ \value GridView.Beginning position item at the top (or left for \c GridView.FlowTopToBottom flow) of the view.
+ \value GridView.Center position item in the center of the view.
+ \value GridView.End position item at bottom (or right for horizontal orientation) of the view.
+ \value GridView.Visible if any part of the item is visible then take no action, otherwise
+ bring the item into view.
+ \value GridView.Contain ensure the entire item is visible. If the item is larger than the view, the item
+ is positioned at the top (or left for \c GridView.FlowTopToBottom flow) of the view.
+ \value GridView.SnapPosition position the item at \l preferredHighlightBegin. This mode is only valid if
+ \l highlightRangeMode is \c StrictlyEnforceRange or snapping is enabled via \l snapMode.
If positioning the view at the index would cause empty space to be displayed at
the beginning or end of the view, the view will be positioned at the boundary.
diff --git a/src/quick/items/qquickgridview_p.h b/src/quick/items/qquickgridview_p.h
index b90e5e6c9e..1f9698a706 100644
--- a/src/quick/items/qquickgridview_p.h
+++ b/src/quick/items/qquickgridview_p.h
@@ -25,7 +25,7 @@ QT_BEGIN_NAMESPACE
class QQuickGridViewAttached;
class QQuickGridViewPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickGridView : public QQuickItemView
+class Q_QUICK_EXPORT QQuickGridView : public QQuickItemView
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickGridView)
@@ -101,6 +101,4 @@ public:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickGridView)
-
#endif // QQUICKGRIDVIEW_P_H
diff --git a/src/quick/items/qquickimage.cpp b/src/quick/items/qquickimage.cpp
index 3b6ddf729f..3efe082332 100644
--- a/src/quick/items/qquickimage.cpp
+++ b/src/quick/items/qquickimage.cpp
@@ -115,6 +115,43 @@ QQuickImagePrivate::QQuickImagePrivate()
convert foo.png \( +clone -alpha Extract \) -channel RGB -compose Multiply -composite foo_pm.png
\endcode
+ Do not confuse container formats, such as, \c KTX, and the format of the
+ actual texture data stored in the container file. For example, reading a
+ \c KTX file is supported on all platforms, independently of what GPU driver is
+ used at run time. However, this does not guarantee that the compressed
+ texture format, used by the data in the file, is supported at run time. For
+ example, if the KTX file contains compressed data with the format
+ \c{ETC2 RGBA8}, and the 3D graphics API implementation used at run time does not
+ support \c ETC2 compressed textures, the Image item will not display
+ anything.
+
+ \note Compressed texture format support is not under Qt's control, and it
+ is up to the application or device developer to ensure the compressed
+ texture data is provided in the appropriate format for the target
+ environment(s).
+
+ Do not assume that compressed format support is specific to a platform. It
+ may also be specific to the driver and 3D API implementation in use on that
+ particular platform. In practice, implementations of different 3D graphics
+ APIs (e.g., Vulkan and OpenGL) on the same platform (e.g., Windows) from
+ the same vendor for the same hardware may offer a different set of
+ compressed texture formats.
+
+ When targeting desktop environments (Windows, macOS, Linux) only, a general
+ recommendation is to consider using the \c{DXTn}/\c{BCn} formats since
+ these tend to have the widest support amongst the implementations of Direct
+ 3D, Vulkan, OpenGL, and Metal on these platforms. In contrast, when
+ targeting mobile or embedded devices, the \c ETC2 or \c ASTC formats are
+ likely to be a better choice since these are typically the formats
+ supported by the OpenGL ES implementations on such hardware.
+
+ An application that intends to run across desktop, mobile, and embedded
+ hardware should plan and design its use of compressed textures carefully.
+ It is highly likely that relying on a single format is not going to be
+ sufficient, and therefore the application will likely need to branch based
+ on the platform to use compressed textures in a format appropriate there,
+ or perhaps to skip using compressed textures in some cases.
+
\section1 Automatic Detection of File Extension
If the \l source URL indicates a non-existing local file or resource, the
@@ -181,22 +218,16 @@ QQuickImage::~QQuickImage()
void QQuickImagePrivate::setImage(const QImage &image)
{
Q_Q(QQuickImage);
- pix.setImage(image);
-
+ currentPix->setImage(image);
q->pixmapChange();
- status = pix.isNull() ? QQuickImageBase::Null : QQuickImageBase::Ready;
-
q->update();
}
void QQuickImagePrivate::setPixmap(const QQuickPixmap &pixmap)
{
Q_Q(QQuickImage);
- pix.setPixmap(pixmap);
-
+ currentPix->setPixmap(pixmap);
q->pixmapChange();
- status = pix.isNull() ? QQuickImageBase::Null : QQuickImageBase::Ready;
-
q->update();
}
@@ -205,15 +236,15 @@ void QQuickImagePrivate::setPixmap(const QQuickPixmap &pixmap)
Set this property to define what happens when the source image has a different size
than the item.
- \list
- \li Image.Stretch - the image is scaled to fit
- \li Image.PreserveAspectFit - the image is scaled uniformly to fit without cropping
- \li Image.PreserveAspectCrop - the image is scaled uniformly to fill, cropping if necessary
- \li Image.Tile - the image is duplicated horizontally and vertically
- \li Image.TileVertically - the image is stretched horizontally and tiled vertically
- \li Image.TileHorizontally - the image is stretched vertically and tiled horizontally
- \li Image.Pad - the image is not transformed
- \endlist
+
+ \value Image.Stretch the image is scaled to fit
+ \value Image.PreserveAspectFit the image is scaled uniformly to fit without cropping
+ \value Image.PreserveAspectCrop the image is scaled uniformly to fill, cropping if necessary
+ \value Image.Tile the image is duplicated horizontally and vertically
+ \value Image.TileVertically the image is stretched horizontally and tiled vertically
+ \value Image.TileHorizontally the image is stretched vertically and tiled horizontally
+ \value Image.Pad the image is not transformed
+ \br
\table
@@ -348,12 +379,11 @@ qreal QQuickImage::paintedHeight() const
\readonly
This property holds the status of image loading. It can be one of:
- \list
- \li Image.Null - no image has been set
- \li Image.Ready - the image has been loaded
- \li Image.Loading - the image is currently being loaded
- \li Image.Error - an error occurred while loading the image
- \endlist
+
+ \value Image.Null No image has been set
+ \value Image.Ready The image has been loaded
+ \value Image.Loading The image is currently being loaded
+ \value Image.Error An error occurred while loading the image
Use this status to provide an update or respond to the status change in some way.
For example, you could:
@@ -458,6 +488,8 @@ qreal QQuickImage::paintedHeight() const
\note \e {Changing this property dynamically causes the image source to be reloaded,
potentially even from the network, if it is not in the disk cache.}
+
+ \sa {Qt Quick Examples - Pointer Handlers}
*/
/*!
@@ -573,12 +605,12 @@ void QQuickImage::updatePaintedGeometry()
Q_D(QQuickImage);
if (d->fillMode == PreserveAspectFit) {
- if (!d->pix.width() || !d->pix.height()) {
+ if (!d->currentPix->width() || !d->currentPix->height()) {
setImplicitSize(0, 0);
return;
}
- const qreal pixWidth = d->pix.width() / d->devicePixelRatio;
- const qreal pixHeight = d->pix.height() / d->devicePixelRatio;
+ const qreal pixWidth = d->currentPix->width() / d->devicePixelRatio;
+ const qreal pixHeight = d->currentPix->height() / d->devicePixelRatio;
const qreal w = widthValid() ? width() : pixWidth;
const qreal widthScale = w / pixWidth;
const qreal h = heightValid() ? height() : pixHeight;
@@ -595,10 +627,10 @@ void QQuickImage::updatePaintedGeometry()
setImplicitSize(iWidth, iHeight);
} else if (d->fillMode == PreserveAspectCrop) {
- if (!d->pix.width() || !d->pix.height())
+ if (!d->currentPix->width() || !d->currentPix->height())
return;
- const qreal pixWidth = d->pix.width() / d->devicePixelRatio;
- const qreal pixHeight = d->pix.height() / d->devicePixelRatio;
+ const qreal pixWidth = d->currentPix->width() / d->devicePixelRatio;
+ const qreal pixHeight = d->currentPix->height() / d->devicePixelRatio;
qreal widthScale = width() / pixWidth;
qreal heightScale = height() / pixHeight;
if (widthScale < heightScale) {
@@ -610,8 +642,8 @@ void QQuickImage::updatePaintedGeometry()
d->paintedHeight = heightScale * pixHeight;
d->paintedWidth = widthScale * pixWidth;
} else if (d->fillMode == Pad) {
- d->paintedWidth = d->pix.width() / d->devicePixelRatio;
- d->paintedHeight = d->pix.height() / d->devicePixelRatio;
+ d->paintedWidth = d->currentPix->width() / d->devicePixelRatio;
+ d->paintedHeight = d->currentPix->height() / d->devicePixelRatio;
} else {
d->paintedWidth = width();
d->paintedHeight = height();
@@ -653,7 +685,7 @@ QSGTextureProvider *QQuickImage::textureProvider() const
dd->provider = new QQuickImageTextureProvider;
dd->provider->m_smooth = d->smooth;
dd->provider->m_mipmap = d->mipmap;
- dd->provider->updateTexture(d->sceneGraphRenderContext()->textureForFactory(d->pix.textureFactory(), window()));
+ dd->provider->updateTexture(d->sceneGraphRenderContext()->textureForFactory(d->currentPix->textureFactory(), window()));
}
return d->provider;
@@ -679,7 +711,7 @@ QSGNode *QQuickImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
Q_D(QQuickImage);
- QSGTexture *texture = d->sceneGraphRenderContext()->textureForFactory(d->pix.textureFactory(), window());
+ QSGTexture *texture = d->sceneGraphRenderContext()->textureForFactory(d->currentPix->textureFactory(), window());
// Copy over the current texture state into the texture provider...
if (d->provider) {
@@ -704,8 +736,8 @@ QSGNode *QQuickImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
QSGTexture::WrapMode hWrap = QSGTexture::ClampToEdge;
QSGTexture::WrapMode vWrap = QSGTexture::ClampToEdge;
- qreal pixWidth = (d->fillMode == PreserveAspectFit) ? d->paintedWidth : d->pix.width() / d->devicePixelRatio;
- qreal pixHeight = (d->fillMode == PreserveAspectFit) ? d->paintedHeight : d->pix.height() / d->devicePixelRatio;
+ qreal pixWidth = (d->fillMode == PreserveAspectFit) ? d->paintedWidth : d->currentPix->width() / d->devicePixelRatio;
+ qreal pixHeight = (d->fillMode == PreserveAspectFit) ? d->paintedHeight : d->currentPix->height() / d->devicePixelRatio;
int xOffset = 0;
if (d->hAlign == QQuickImage::AlignHCenter)
@@ -722,36 +754,36 @@ QSGNode *QQuickImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
switch (d->fillMode) {
case Stretch:
targetRect = QRectF(0, 0, width(), height());
- sourceRect = d->pix.rect();
+ sourceRect = d->currentPix->rect();
break;
case PreserveAspectFit:
targetRect = QRectF(xOffset, yOffset, d->paintedWidth, d->paintedHeight);
- sourceRect = d->pix.rect();
+ sourceRect = d->currentPix->rect();
break;
case PreserveAspectCrop: {
targetRect = QRectF(0, 0, width(), height());
- qreal wscale = width() / qreal(d->pix.width());
- qreal hscale = height() / qreal(d->pix.height());
+ qreal wscale = width() / qreal(d->currentPix->width());
+ qreal hscale = height() / qreal(d->currentPix->height());
if (wscale > hscale) {
- int src = (hscale / wscale) * qreal(d->pix.height());
+ int src = (hscale / wscale) * qreal(d->currentPix->height());
int y = 0;
if (d->vAlign == QQuickImage::AlignVCenter)
- y = qCeil((d->pix.height() - src) / 2.);
+ y = qCeil((d->currentPix->height() - src) / 2.);
else if (d->vAlign == QQuickImage::AlignBottom)
- y = qCeil(d->pix.height() - src);
- sourceRect = QRectF(0, y, d->pix.width(), src);
+ y = qCeil(d->currentPix->height() - src);
+ sourceRect = QRectF(0, y, d->currentPix->width(), src);
} else {
- int src = (wscale / hscale) * qreal(d->pix.width());
+ int src = (wscale / hscale) * qreal(d->currentPix->width());
int x = 0;
if (d->hAlign == QQuickImage::AlignHCenter)
- x = qCeil((d->pix.width() - src) / 2.);
+ x = qCeil((d->currentPix->width() - src) / 2.);
else if (d->hAlign == QQuickImage::AlignRight)
- x = qCeil(d->pix.width() - src);
- sourceRect = QRectF(x, 0, src, d->pix.height());
+ x = qCeil(d->currentPix->width() - src);
+ sourceRect = QRectF(x, 0, src, d->currentPix->height());
}
}
break;
@@ -765,13 +797,13 @@ QSGNode *QQuickImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
case TileHorizontally:
targetRect = QRectF(0, 0, width(), height());
- sourceRect = QRectF(-xOffset, 0, width(), d->pix.height());
+ sourceRect = QRectF(-xOffset, 0, width(), d->currentPix->height());
hWrap = QSGTexture::Repeat;
break;
case TileVertically:
targetRect = QRectF(0, 0, width(), height());
- sourceRect = QRectF(0, -yOffset, d->pix.width(), height());
+ sourceRect = QRectF(0, -yOffset, d->currentPix->width(), height());
vWrap = QSGTexture::Repeat;
break;
@@ -785,8 +817,8 @@ QSGNode *QQuickImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
break;
}
- qreal nsWidth = (hWrap == QSGTexture::Repeat || d->fillMode == Pad) ? d->pix.width() / d->devicePixelRatio : d->pix.width();
- qreal nsHeight = (vWrap == QSGTexture::Repeat || d->fillMode == Pad) ? d->pix.height() / d->devicePixelRatio : d->pix.height();
+ qreal nsWidth = (hWrap == QSGTexture::Repeat || d->fillMode == Pad) ? d->currentPix->width() / d->devicePixelRatio : d->currentPix->width();
+ qreal nsHeight = (vWrap == QSGTexture::Repeat || d->fillMode == Pad) ? d->currentPix->height() / d->devicePixelRatio : d->currentPix->height();
QRectF nsrect(sourceRect.x() / nsWidth,
sourceRect.y() / nsHeight,
sourceRect.width() / nsWidth,
@@ -935,6 +967,25 @@ void QQuickImage::setMipmap(bool use)
frameCount is the number of frames in the image. Most images have only one frame.
*/
+/*!
+ \qmlproperty bool QtQuick::Image::retainWhileLoading
+ \since 6.8
+
+//! [qml-image-retainwhileloading]
+ This property defines the behavior when the \l source property is changed and loading happens
+ asynchronously. This is the case when the \l asynchronous property is set to \c true, or if the
+ image is not on the local file system.
+
+ If \c retainWhileLoading is \c false (the default), the old image is discarded immediately, and
+ the component is cleared while the new image is being loaded. If set to \c true, the old image
+ is retained and remains visible until the new one is ready.
+
+ Enabling this property can avoid flickering in cases where loading the new image takes a long
+ time. It comes at the cost of some extra memory use for double buffering while the new image is
+ being loaded.
+//! [qml-image-retainwhileloading]
+ */
+
QT_END_NAMESPACE
#include "moc_qquickimage_p_p.cpp"
diff --git a/src/quick/items/qquickimage_p.h b/src/quick/items/qquickimage_p.h
index d7a55e529b..73dd3e2eb7 100644
--- a/src/quick/items/qquickimage_p.h
+++ b/src/quick/items/qquickimage_p.h
@@ -21,7 +21,7 @@
QT_BEGIN_NAMESPACE
class QQuickImagePrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickImage : public QQuickImageBase
+class Q_QUICK_EXPORT QQuickImage : public QQuickImageBase
{
Q_OBJECT
@@ -30,6 +30,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickImage : public QQuickImageBase
Q_PROPERTY(qreal paintedHeight READ paintedHeight NOTIFY paintedGeometryChanged)
Q_PROPERTY(HAlignment horizontalAlignment READ horizontalAlignment WRITE setHorizontalAlignment NOTIFY horizontalAlignmentChanged)
Q_PROPERTY(VAlignment verticalAlignment READ verticalAlignment WRITE setVerticalAlignment NOTIFY verticalAlignmentChanged)
+ Q_PROPERTY(QSize sourceSize READ sourceSize WRITE setSourceSize RESET resetSourceSize NOTIFY sourceSizeChanged)
Q_PROPERTY(bool mipmap READ mipmap WRITE setMipmap NOTIFY mipmapChanged REVISION(2, 3))
Q_PROPERTY(bool autoTransform READ autoTransform WRITE setAutoTransform NOTIFY autoTransformChanged REVISION(2, 5))
Q_PROPERTY(QRectF sourceClipRect READ sourceClipRect WRITE setSourceClipRect RESET resetSourceClipRect NOTIFY sourceClipRectChanged REVISION(2, 15))
@@ -100,5 +101,5 @@ private:
};
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickImage)
+
#endif // QQUICKIMAGE_P_H
diff --git a/src/quick/items/qquickimage_p_p.h b/src/quick/items/qquickimage_p_p.h
index 7b6dc00d6f..8f910385e8 100644
--- a/src/quick/items/qquickimage_p_p.h
+++ b/src/quick/items/qquickimage_p_p.h
@@ -21,7 +21,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QQuickImageTextureProvider : public QSGTextureProvider
+class Q_QUICK_EXPORT QQuickImageTextureProvider : public QSGTextureProvider
{
Q_OBJECT
public:
@@ -38,7 +38,7 @@ public:
bool m_mipmap;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickImagePrivate : public QQuickImageBasePrivate
+class Q_QUICK_EXPORT QQuickImagePrivate : public QQuickImageBasePrivate
{
Q_DECLARE_PUBLIC(QQuickImage)
diff --git a/src/quick/items/qquickimagebase.cpp b/src/quick/items/qquickimagebase.cpp
index 78dfecc42a..b00baf7f7d 100644
--- a/src/quick/items/qquickimagebase.cpp
+++ b/src/quick/items/qquickimagebase.cpp
@@ -15,16 +15,7 @@
QT_BEGIN_NAMESPACE
-bool isScalableImageFormat(const QUrl &url)
-{
- if (url.scheme() == QLatin1String("image"))
- return true;
-
- const QString stringUrl = url.path(QUrl::PrettyDecoded);
- return stringUrl.endsWith(QLatin1String("svg"))
- || stringUrl.endsWith(QLatin1String("svgz"))
- || stringUrl.endsWith(QLatin1String("pdf"));
-}
+using namespace Qt::Literals::StringLiterals;
// This function gives derived classes the chance set the devicePixelRatio
// if they're not happy with our implementation of it.
@@ -33,7 +24,7 @@ bool QQuickImageBasePrivate::updateDevicePixelRatio(qreal targetDevicePixelRatio
// QQuickImageProvider and SVG and PDF can generate a high resolution image when
// sourceSize is set. If sourceSize is not set then the provider default size will
// be used, as usual.
- const bool setDevicePixelRatio = isScalableImageFormat(url);
+ const bool setDevicePixelRatio = QQuickPixmap::isScalableImageFormat(url);
if (setDevicePixelRatio)
devicePixelRatio = targetDevicePixelRatio;
@@ -41,6 +32,28 @@ bool QQuickImageBasePrivate::updateDevicePixelRatio(qreal targetDevicePixelRatio
return setDevicePixelRatio;
}
+void QQuickImageBasePrivate::setStatus(QQuickImageBase::Status value)
+{
+ Q_Q(QQuickImageBase);
+
+ if (status == value)
+ return;
+
+ status = value;
+ emit q->statusChanged(status);
+}
+
+void QQuickImageBasePrivate::setProgress(qreal value)
+{
+ Q_Q(QQuickImageBase);
+
+ if (qFuzzyCompare(progress, value))
+ return;
+
+ progress = value;
+ emit q->progressChanged(progress);
+}
+
QQuickImageBase::QQuickImageBase(QQuickItem *parent)
: QQuickImplicitSizeItem(*(new QQuickImageBasePrivate), parent)
{
@@ -63,14 +76,12 @@ QQuickImageBase::Status QQuickImageBase::status() const
return d->status;
}
-
qreal QQuickImageBase::progress() const
{
Q_D(const QQuickImageBase);
return d->progress;
}
-
bool QQuickImageBase::asynchronous() const
{
Q_D(const QQuickImageBase);
@@ -124,7 +135,7 @@ QSize QQuickImageBase::sourceSize() const
int width = d->sourcesize.width();
int height = d->sourcesize.height();
- return QSize(width != -1 ? width : d->pix.width(), height != -1 ? height : d->pix.height());
+ return QSize(width != -1 ? width : d->currentPix->width(), height != -1 ? height : d->currentPix->height());
}
void QQuickImageBase::resetSourceSize()
@@ -145,6 +156,7 @@ void QQuickImageBase::setSourceClipRect(const QRectF &r)
return;
d->sourceClipRect = r;
+ d->providerOptions.setSourceClipRect(r);
emit sourceClipRectChanged();
if (isComponentComplete())
load();
@@ -176,7 +188,7 @@ void QQuickImageBase::setCache(bool cache)
QImage QQuickImageBase::image() const
{
Q_D(const QQuickImageBase);
- return d->pix.image();
+ return d->currentPix->image();
}
void QQuickImageBase::setMirror(bool mirror)
@@ -222,7 +234,7 @@ bool QQuickImageBase::mirrorVertically() const
void QQuickImageBase::setCurrentFrame(int frame)
{
Q_D(QQuickImageBase);
- if (frame == d->currentFrame || frame < 0 || (isComponentComplete() && frame >= d->pix.frameCount()))
+ if (frame == d->currentFrame || frame < 0 || (isComponentComplete() && frame >= d->currentPix->frameCount()))
return;
d->currentFrame = frame;
@@ -252,12 +264,10 @@ int QQuickImageBase::frameCount() const
void QQuickImageBase::loadEmptyUrl()
{
Q_D(QQuickImageBase);
- d->pix.clear(this);
- if (d->progress != 0.0) {
- d->progress = 0.0;
- emit progressChanged(d->progress);
- }
- d->status = Null;
+ d->currentPix->clear(this);
+ d->pendingPix->clear(this);
+ d->setProgress(0);
+ d->status = Null; // do not emit statusChanged until after setImplicitSize
setImplicitSize(0, 0); // also called in QQuickImageBase::pixmapChange, but not QQuickImage/QQuickBorderImage overrides
pixmapChange(); // This calls update() in QQuickBorderImage and QQuickImage, not in QQuickImageBase...
@@ -281,7 +291,7 @@ void QQuickImageBase::loadPixmap(const QUrl &url, LoadPixmapOptions loadOptions)
options |= QQuickPixmap::Asynchronous;
if (d->cache)
options |= QQuickPixmap::Cache;
- d->pix.clear(this);
+ d->pendingPix->clear(this);
QUrl loadUrl = url;
const QQmlContext *context = qmlContext(this);
if (context)
@@ -292,7 +302,7 @@ void QQuickImageBase::loadPixmap(const QUrl &url, LoadPixmapOptions loadOptions)
d->devicePixelRatio = 1.0;
bool updatedDevicePixelRatio = false;
if (d->sourcesize.isValid()
- || (isScalableImageFormat(d->url) && d->url.scheme() != QLatin1String("image"))) {
+ || (QQuickPixmap::isScalableImageFormat(d->url) && d->url.scheme() != "image"_L1)) {
updatedDevicePixelRatio = d->updateDevicePixelRatio(targetDevicePixelRatio);
}
@@ -304,24 +314,20 @@ void QQuickImageBase::loadPixmap(const QUrl &url, LoadPixmapOptions loadOptions)
}
}
- d->pix.load(qmlEngine(this),
- loadUrl,
- d->sourceClipRect.toRect(),
- (loadOptions & HandleDPR) ? d->sourcesize * d->devicePixelRatio : QSize(),
- options,
- (loadOptions & UseProviderOptions) ? d->providerOptions : QQuickImageProviderOptions(),
- d->currentFrame, d->frameCount,
- d->devicePixelRatio);
-
- if (d->pix.isLoading()) {
- if (d->progress != 0.0) {
- d->progress = 0.0;
- emit progressChanged(d->progress);
- }
- if (d->status != Loading) {
- d->status = Loading;
- emit statusChanged(d->status);
- }
+ d->status = Null; // reset status, no emit
+
+ d->pendingPix->load(qmlEngine(this),
+ loadUrl,
+ d->sourceClipRect.toRect(),
+ (loadOptions & HandleDPR) ? d->sourcesize * d->devicePixelRatio : QSize(),
+ options,
+ (loadOptions & UseProviderOptions) ? d->providerOptions : QQuickImageProviderOptions(),
+ d->currentFrame, d->frameCount,
+ d->devicePixelRatio);
+
+ if (d->pendingPix->isLoading()) {
+ d->setProgress(0);
+ d->setStatus(Loading);
static int thisRequestProgress = -1;
static int thisRequestFinished = -1;
@@ -332,9 +338,10 @@ void QQuickImageBase::loadPixmap(const QUrl &url, LoadPixmapOptions loadOptions)
QQuickImageBase::staticMetaObject.indexOfSlot("requestFinished()");
}
- d->pix.connectFinished(this, thisRequestFinished);
- d->pix.connectDownloadProgress(this, thisRequestProgress);
- update(); //pixmap may have invalidated texture, updatePaintNode needs to be called before the next repaint
+ d->pendingPix->connectFinished(this, thisRequestFinished);
+ d->pendingPix->connectDownloadProgress(this, thisRequestProgress);
+ if (!d->retainWhileLoading)
+ update(); //pixmap may have invalidated texture, updatePaintNode needs to be called before the next repaint
} else {
requestFinished();
}
@@ -355,24 +362,25 @@ void QQuickImageBase::load()
void QQuickImageBase::requestFinished()
{
Q_D(QQuickImageBase);
+ if (d->pendingPix != d->currentPix
+ && d->pendingPix->status() != QQuickPixmap::Null
+ && d->pendingPix->status() != QQuickPixmap::Loading) {
+ std::swap(d->pendingPix, d->currentPix);
+ d->pendingPix->clear(this); // Clear the old image
+ }
- if (d->pix.isError()) {
- qmlWarning(this) << d->pix.error();
- d->pix.clear(this);
+ if (d->currentPix->isError()) {
+ qmlWarning(this) << d->currentPix->error();
d->status = Error;
- if (d->progress != 0.0) {
- d->progress = 0.0;
- emit progressChanged(d->progress);
- }
+ d->setProgress(0);
} else {
- d->status = Ready;
- if (d->progress != 1.0) {
- d->progress = 1.0;
- emit progressChanged(d->progress);
- }
+ d->status = Ready; // do not emit statusChanged until after setImplicitSize
+ d->setProgress(1);
}
+
pixmapChange();
emit statusChanged(d->status);
+
if (sourceSize() != d->oldSourceSize) {
d->oldSourceSize = sourceSize();
emit sourceSizeChanged();
@@ -381,12 +389,12 @@ void QQuickImageBase::requestFinished()
d->oldAutoTransform = autoTransform();
emitAutoTransformBaseChanged();
}
- if (d->frameCount != d->pix.frameCount()) {
- d->frameCount = d->pix.frameCount();
+ if (d->frameCount != d->currentPix->frameCount()) {
+ d->frameCount = d->currentPix->frameCount();
emit frameCountChanged();
}
- if (d->colorSpace != d->pix.colorSpace()) {
- d->colorSpace = d->pix.colorSpace();
+ if (d->colorSpace != d->currentPix->colorSpace()) {
+ d->colorSpace = d->currentPix->colorSpace();
emit colorSpaceChanged();
}
@@ -396,10 +404,8 @@ void QQuickImageBase::requestFinished()
void QQuickImageBase::requestProgress(qint64 received, qint64 total)
{
Q_D(QQuickImageBase);
- if (d->status == Loading && total > 0) {
- d->progress = qreal(received)/total;
- emit progressChanged(d->progress);
- }
+ if (d->status == Loading && total > 0)
+ d->setProgress(qreal(received) / total);
}
void QQuickImageBase::itemChange(ItemChange change, const ItemChangeData &value)
@@ -431,7 +437,7 @@ void QQuickImageBase::componentComplete()
void QQuickImageBase::pixmapChange()
{
Q_D(QQuickImageBase);
- setImplicitSize(d->pix.width() / d->devicePixelRatio, d->pix.height() / d->devicePixelRatio);
+ setImplicitSize(d->currentPix->width() / d->devicePixelRatio, d->currentPix->height() / d->devicePixelRatio);
}
void QQuickImageBase::resolve2xLocalFile(const QUrl &url, qreal targetDevicePixelRatio, QUrl *sourceUrl, qreal *sourceDevicePixelRatio)
@@ -471,7 +477,7 @@ bool QQuickImageBase::autoTransform() const
{
Q_D(const QQuickImageBase);
if (d->providerOptions.autoTransform() == QQuickImageProviderOptions::UsePluginDefaultTransform)
- return d->pix.autoTransform() == QQuickImageProviderOptions::ApplyTransform;
+ return d->currentPix->autoTransform() == QQuickImageProviderOptions::ApplyTransform;
return d->providerOptions.autoTransform() == QQuickImageProviderOptions::ApplyTransform;
}
@@ -501,6 +507,30 @@ void QQuickImageBase::setColorSpace(const QColorSpace &colorSpace)
emit colorSpaceChanged();
}
+bool QQuickImageBase::retainWhileLoading() const
+{
+ Q_D(const QQuickImageBase);
+ return d->retainWhileLoading;
+}
+
+void QQuickImageBase::setRetainWhileLoading(bool retainWhileLoading)
+{
+ Q_D(QQuickImageBase);
+ if (d->retainWhileLoading == retainWhileLoading)
+ return;
+
+ d->retainWhileLoading = retainWhileLoading;
+ if (d->retainWhileLoading) {
+ if (d->currentPix == &d->pix1)
+ d->pendingPix = &d->pix2;
+ else
+ d->pendingPix = &d->pix1;
+ } else {
+ d->pendingPix->clear();
+ d->pendingPix = d->currentPix;
+ }
+}
+
QT_END_NAMESPACE
#include "moc_qquickimagebase_p.cpp"
diff --git a/src/quick/items/qquickimagebase_p.h b/src/quick/items/qquickimagebase_p.h
index 5ad698816d..5a33db43b8 100644
--- a/src/quick/items/qquickimagebase_p.h
+++ b/src/quick/items/qquickimagebase_p.h
@@ -22,7 +22,7 @@
QT_BEGIN_NAMESPACE
class QQuickImageBasePrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickImageBase : public QQuickImplicitSizeItem
+class Q_QUICK_EXPORT QQuickImageBase : public QQuickImplicitSizeItem
{
Q_OBJECT
@@ -31,9 +31,9 @@ class Q_QUICK_PRIVATE_EXPORT QQuickImageBase : public QQuickImplicitSizeItem
Q_PROPERTY(qreal progress READ progress NOTIFY progressChanged)
Q_PROPERTY(bool asynchronous READ asynchronous WRITE setAsynchronous NOTIFY asynchronousChanged)
Q_PROPERTY(bool cache READ cache WRITE setCache NOTIFY cacheChanged)
- Q_PROPERTY(QSize sourceSize READ sourceSize WRITE setSourceSize RESET resetSourceSize NOTIFY sourceSizeChanged)
Q_PROPERTY(bool mirror READ mirror WRITE setMirror NOTIFY mirrorChanged)
Q_PROPERTY(bool mirrorVertically READ mirrorVertically WRITE setMirrorVertically NOTIFY mirrorVerticallyChanged REVISION(6, 2))
+ Q_PROPERTY(bool retainWhileLoading READ retainWhileLoading WRITE setRetainWhileLoading NOTIFY retainWhileLoadingChanged REVISION(6, 8))
Q_PROPERTY(int currentFrame READ currentFrame WRITE setCurrentFrame NOTIFY currentFrameChanged REVISION(2, 14))
Q_PROPERTY(int frameCount READ frameCount NOTIFY frameCountChanged REVISION(2, 14))
Q_PROPERTY(QColorSpace colorSpace READ colorSpace WRITE setColorSpace NOTIFY colorSpaceChanged REVISION(2, 15))
@@ -95,6 +95,9 @@ public:
QColorSpace colorSpace() const;
virtual void setColorSpace(const QColorSpace &colorSpace);
+ bool retainWhileLoading() const;
+ void setRetainWhileLoading(bool retain);
+
static void resolve2xLocalFile(const QUrl &url, qreal targetDevicePixelRatio, QUrl *sourceUrl, qreal *sourceDevicePixelRatio);
// Use a virtual rather than a signal->signal to avoid the huge
@@ -114,6 +117,7 @@ Q_SIGNALS:
Q_REVISION(2, 15) void sourceClipRectChanged();
Q_REVISION(2, 15) void colorSpaceChanged();
Q_REVISION(6, 2) void mirrorVerticallyChanged();
+ Q_REVISION(6, 8) void retainWhileLoadingChanged();
protected:
void loadEmptyUrl();
diff --git a/src/quick/items/qquickimagebase_p_p.h b/src/quick/items/qquickimagebase_p_p.h
index 04113da181..d53712b779 100644
--- a/src/quick/items/qquickimagebase_p_p.h
+++ b/src/quick/items/qquickimagebase_p_p.h
@@ -18,49 +18,56 @@
#include "qquickimplicitsizeitem_p_p.h"
#include "qquickimagebase_p.h"
-#include <QtQuick/private/qquickpixmapcache_p.h>
+#include <QtQuick/private/qquickpixmap_p.h>
QT_BEGIN_NAMESPACE
class QNetworkReply;
-class Q_QUICK_PRIVATE_EXPORT QQuickImageBasePrivate : public QQuickImplicitSizeItemPrivate
+class Q_QUICK_EXPORT QQuickImageBasePrivate : public QQuickImplicitSizeItemPrivate
{
Q_DECLARE_PUBLIC(QQuickImageBase)
public:
QQuickImageBasePrivate()
- : status(QQuickImageBase::Null),
- progress(0.0),
- devicePixelRatio(1.0),
- currentFrame(0),
- frameCount(0),
- async(false),
+ : async(false),
cache(true),
mirrorHorizontally(false),
mirrorVertically(false),
- oldAutoTransform(false)
+ oldAutoTransform(false),
+ retainWhileLoading(false)
{
+ pendingPix = &pix1;
+ currentPix = &pix1;
}
virtual bool updateDevicePixelRatio(qreal targetDevicePixelRatio);
- QQuickPixmap pix;
- QQuickImageBase::Status status;
+ void setStatus(QQuickImageBase::Status value);
+ void setProgress(qreal value);
+
QUrl url;
- qreal progress;
+ QQuickPixmap *pendingPix = nullptr;
+ QQuickPixmap *currentPix = nullptr;
+ QQuickPixmap pix1;
+ QQuickPixmap pix2;
QSize sourcesize;
QSize oldSourceSize;
- qreal devicePixelRatio;
QRectF sourceClipRect;
QQuickImageProviderOptions providerOptions;
QColorSpace colorSpace;
- int currentFrame;
- int frameCount;
+
+ int currentFrame = 0;
+ int frameCount = 0;
+ qreal progress = 0;
+ qreal devicePixelRatio = 1;
+ QQuickImageBase::Status status = QQuickImageBase::Null;
+
bool async : 1;
bool cache : 1;
bool mirrorHorizontally: 1;
bool mirrorVertically : 1;
bool oldAutoTransform : 1;
+ bool retainWhileLoading : 1;
};
QT_END_NAMESPACE
diff --git a/src/quick/items/qquickimplicitsizeitem_p.h b/src/quick/items/qquickimplicitsizeitem_p.h
index 507ad537ac..198c82f52c 100644
--- a/src/quick/items/qquickimplicitsizeitem_p.h
+++ b/src/quick/items/qquickimplicitsizeitem_p.h
@@ -21,7 +21,7 @@
QT_BEGIN_NAMESPACE
class QQuickImplicitSizeItemPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickImplicitSizeItem : public QQuickItem
+class Q_QUICK_EXPORT QQuickImplicitSizeItem : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(qreal implicitWidth READ implicitWidth NOTIFY implicitWidthChanged)
diff --git a/src/quick/items/qquickimplicitsizeitem_p_p.h b/src/quick/items/qquickimplicitsizeitem_p_p.h
index c1ac09f850..4510391e71 100644
--- a/src/quick/items/qquickimplicitsizeitem_p_p.h
+++ b/src/quick/items/qquickimplicitsizeitem_p_p.h
@@ -21,7 +21,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QQuickImplicitSizeItemPrivate : public QQuickItemPrivate
+class Q_QUICK_EXPORT QQuickImplicitSizeItemPrivate : public QQuickItemPrivate
{
Q_DECLARE_PUBLIC(QQuickImplicitSizeItem)
diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp
index cc860c9ae3..6c4ab28768 100644
--- a/src/quick/items/qquickitem.cpp
+++ b/src/quick/items/qquickitem.cpp
@@ -48,6 +48,8 @@
# include <QtGui/qcursor.h>
#endif
+#include <QtCore/qpointer.h>
+
#include <algorithm>
#include <limits>
@@ -55,13 +57,9 @@
QT_BEGIN_NAMESPACE
-Q_DECLARE_LOGGING_CATEGORY(lcMouseTarget)
-Q_DECLARE_LOGGING_CATEGORY(lcHoverTrace)
-Q_DECLARE_LOGGING_CATEGORY(lcPtr)
-Q_DECLARE_LOGGING_CATEGORY(lcTransient)
Q_LOGGING_CATEGORY(lcHandlerParent, "qt.quick.handler.parent")
Q_LOGGING_CATEGORY(lcVP, "qt.quick.viewport")
-Q_LOGGING_CATEGORY(lcChangeListeners, "qt.quick.item.changelisteners")
+Q_STATIC_LOGGING_CATEGORY(lcChangeListeners, "qt.quick.item.changelisteners")
// after 100ms, a mouse/non-mouse cursor conflict is resolved in favor of the mouse handler
static const quint64 kCursorOverrideTimeout = 100;
@@ -86,6 +84,14 @@ void debugFocusTree(QQuickItem *item, QQuickItem *scope = nullptr, int depth = 1
}
}
+static void setActiveFocus(QQuickItem *item, Qt::FocusReason reason)
+{
+ QQuickItemPrivate *d = QQuickItemPrivate::get(item);
+ if (d->subFocusItem && d->window && d->flags & QQuickItem::ItemIsFocusScope)
+ QQuickWindowPrivate::get(d->window)->clearFocusInScope(item, d->subFocusItem, reason);
+ item->forceActiveFocus(reason);
+}
+
/*!
\qmltype Transform
\instantiates QQuickTransform
@@ -319,10 +325,10 @@ QVariant QQuickItemKeyFilter::inputMethodQuery(Qt::InputMethodQuery query) const
}
#endif // im
-void QQuickItemKeyFilter::shortcutOverride(QKeyEvent *event)
+void QQuickItemKeyFilter::shortcutOverrideEvent(QKeyEvent *event)
{
if (m_next)
- m_next->shortcutOverride(event);
+ m_next->shortcutOverrideEvent(event);
else
event->ignore();
}
@@ -568,14 +574,12 @@ void QQuickKeyNavigationAttached::setBacktab(QQuickItem *i)
This property determines whether the keys are processed before
or after the attached item's own key handling.
- \list
- \li KeyNavigation.BeforeItem - process the key events before normal
- item key processing. If the event is used for key navigation, it will be accepted and will not
- be passed on to the item.
- \li KeyNavigation.AfterItem (default) - process the key events after normal item key
- handling. If the item accepts the key event it will not be
- handled by the KeyNavigation attached property handler.
- \endlist
+ \value KeyNavigation.BeforeItem process the key events before normal
+ item key processing. If the event is used for key navigation, it will be accepted and
+ will not be passed on to the item.
+ \value KeyNavigation.AfterItem (default) process the key events after normal item key
+ handling. If the item accepts the key event it will not be
+ handled by the KeyNavigation attached property handler.
*/
QQuickKeyNavigationAttached::Priority QQuickKeyNavigationAttached::priority() const
{
@@ -868,14 +872,11 @@ bool QQuickKeysAttached::isConnected(const char *signalName) const
This property determines whether the keys are processed before
or after the attached item's own key handling.
- \list
- \li Keys.BeforeItem (default) - process the key events before normal
- item key processing. If the event is accepted it will not
- be passed on to the item.
- \li Keys.AfterItem - process the key events after normal item key
- handling. If the item accepts the key event it will not be
- handled by the Keys attached property handler.
- \endlist
+ \value Keys.BeforeItem (default) process the key events before normal item key processing.
+ If the event is accepted, it will not be passed on to the item.
+ \value Keys.AfterItem process the key events after normal item key handling. If the item
+ accepts the key event, it will not be handled by the
+ Keys attached property handler.
\sa {Key Handling Priorities}
*/
@@ -1385,7 +1386,7 @@ QVariant QQuickKeysAttached::inputMethodQuery(Qt::InputMethodQuery query) const
}
#endif // im
-void QQuickKeysAttached::shortcutOverride(QKeyEvent *event)
+void QQuickKeysAttached::shortcutOverrideEvent(QKeyEvent *event)
{
Q_D(QQuickKeysAttached);
QQuickKeyEvent &keyEvent = d->theKeyEvent;
@@ -1700,6 +1701,54 @@ void QQuickItemPrivate::updateSubFocusItem(QQuickItem *scope, bool focus)
}
}
+
+bool QQuickItemPrivate::setFocusIfNeeded(QEvent::Type eventType)
+{
+ Q_Q(QQuickItem);
+ const bool setFocusOnRelease = QGuiApplication::styleHints()->setFocusOnTouchRelease();
+ Qt::FocusPolicy policy = Qt::ClickFocus;
+
+ switch (eventType) {
+ case QEvent::MouseButtonPress:
+ case QEvent::MouseButtonDblClick:
+ case QEvent::TouchBegin:
+ if (setFocusOnRelease)
+ return false;
+ break;
+ case QEvent::MouseButtonRelease:
+ case QEvent::TouchEnd:
+ if (!setFocusOnRelease)
+ return false;
+ break;
+ case QEvent::Wheel:
+ policy = Qt::WheelFocus;
+ break;
+ default:
+ break;
+ }
+
+ if ((focusPolicy & policy) == policy) {
+ setActiveFocus(q, Qt::MouseFocusReason);
+ return true;
+ }
+
+ return false;
+}
+
+Qt::FocusReason QQuickItemPrivate::lastFocusChangeReason() const
+{
+ return static_cast<Qt::FocusReason>(focusReason);
+}
+
+bool QQuickItemPrivate::setLastFocusChangeReason(Qt::FocusReason reason)
+{
+ if (focusReason == reason)
+ return false;
+
+ focusReason = reason;
+ return true;
+}
+
/*!
\class QQuickItem
\brief The QQuickItem class provides the most basic of all visual items in \l {Qt Quick}.
@@ -2211,6 +2260,11 @@ void QQuickItemPrivate::updateSubFocusItem(QQuickItem *scope, bool focus)
*/
/*!
+ \fn void QQuickItem::focusPolicyChanged(Qt::FocusPolicy)
+ \internal
+*/
+
+/*!
\fn void QQuickItem::activeFocusOnTabChanged(bool)
\internal
*/
@@ -2381,7 +2435,7 @@ bool QQuickItemPrivate::canAcceptTabFocus(QQuickItem *item)
return true;
#if QT_CONFIG(accessibility)
- QAccessible::Role role = QQuickItemPrivate::get(item)->accessibleRole();
+ QAccessible::Role role = QQuickItemPrivate::get(item)->effectiveAccessibleRole();
if (role == QAccessible::EditableText || role == QAccessible::Table || role == QAccessible::List) {
return true;
} else if (role == QAccessible::ComboBox || role == QAccessible::SpinBox) {
@@ -2413,12 +2467,32 @@ bool QQuickItemPrivate::canAcceptTabFocus(QQuickItem *item)
*/
bool QQuickItemPrivate::focusNextPrev(QQuickItem *item, bool forward)
{
- QQuickItem *next = QQuickItemPrivate::nextPrevItemInTabFocusChain(item, forward);
+ QQuickWindow *window = item->window();
+ const bool wrap = !window || window->isTopLevel();
+
+ QQuickItem *next = QQuickItemPrivate::nextPrevItemInTabFocusChain(item, forward, wrap);
if (next == item)
return false;
- next->forceActiveFocus(forward ? Qt::TabFocusReason : Qt::BacktabFocusReason);
+ const auto reason = forward ? Qt::TabFocusReason : Qt::BacktabFocusReason;
+
+ if (!wrap && !next) {
+ // Focus chain wrapped and we are not top-level window
+ // Give focus to parent window
+ Q_ASSERT(window);
+ Q_ASSERT(window->parent());
+
+
+ qt_window_private(window->parent())->setFocusToTarget(
+ forward ? QWindowPrivate::FocusTarget::Next
+ : QWindowPrivate::FocusTarget::Prev,
+ reason);
+ window->parent()->requestActivate();
+ return true;
+ }
+
+ next->forceActiveFocus(reason);
return true;
}
@@ -2467,7 +2541,7 @@ QQuickItem *QQuickItemPrivate::prevTabChildItem(const QQuickItem *item, int star
return nullptr;
}
-QQuickItem* QQuickItemPrivate::nextPrevItemInTabFocusChain(QQuickItem *item, bool forward)
+QQuickItem* QQuickItemPrivate::nextPrevItemInTabFocusChain(QQuickItem *item, bool forward, bool wrap)
{
Q_ASSERT(item);
qCDebug(lcFocus) << "QQuickItemPrivate::nextPrevItemInTabFocusChain: item:" << item << ", forward:" << forward;
@@ -2561,6 +2635,14 @@ QQuickItem* QQuickItemPrivate::nextPrevItemInTabFocusChain(QQuickItem *item, boo
}
current = parent;
} else if (hasChildren) {
+ if (!wrap && (forward || firstFromItem != from)) {
+ qCDebug(lcFocus) << "QQuickItemPrivate::nextPrevItemInTabFocusChain:"
+ << "Focus chain about to wrap.";
+ // If focus chain wraps, we should give the parent window
+ // a chance to get focus, so we should stop here
+ return nullptr;
+ }
+
// Wrap around after checking all items forward
if (forward) {
current = firstChild;
@@ -2806,8 +2888,8 @@ void QQuickItem::stackBefore(const QQuickItem *sibling)
parentPrivate->childItems.move(myIndex, myIndex < siblingIndex ? siblingIndex - 1 : siblingIndex);
- parentPrivate->dirty(QQuickItemPrivate::ChildrenStackingChanged);
parentPrivate->markSortedChildrenDirty(this);
+ parentPrivate->dirty(QQuickItemPrivate::ChildrenStackingChanged);
for (int ii = qMin(siblingIndex, myIndex); ii < parentPrivate->childItems.size(); ++ii)
QQuickItemPrivate::get(parentPrivate->childItems.at(ii))->siblingOrderChanged();
@@ -2851,8 +2933,8 @@ void QQuickItem::stackAfter(const QQuickItem *sibling)
parentPrivate->childItems.move(myIndex, myIndex > siblingIndex ? siblingIndex + 1 : siblingIndex);
- parentPrivate->dirty(QQuickItemPrivate::ChildrenStackingChanged);
parentPrivate->markSortedChildrenDirty(this);
+ parentPrivate->dirty(QQuickItemPrivate::ChildrenStackingChanged);
for (int ii = qMin(myIndex, siblingIndex + 1); ii < parentPrivate->childItems.size(); ++ii)
QQuickItemPrivate::get(parentPrivate->childItems.at(ii))->siblingOrderChanged();
@@ -3050,8 +3132,8 @@ void QQuickItemPrivate::derefWindow()
paintNode = nullptr;
for (int ii = 0; ii < childItems.size(); ++ii) {
- QQuickItem *child = childItems.at(ii);
- QQuickItemPrivate::get(child)->derefWindow();
+ if (QQuickItem *child = childItems.at(ii))
+ QQuickItemPrivate::get(child)->derefWindow();
}
dirty(Window);
@@ -3063,7 +3145,7 @@ void QQuickItemPrivate::derefWindow()
/*!
-Returns a transform that maps points from window space into item space.
+ Returns a transform that maps points from window space into item space.
*/
QTransform QQuickItemPrivate::windowToItemTransform() const
{
@@ -3072,21 +3154,21 @@ QTransform QQuickItemPrivate::windowToItemTransform() const
}
/*!
-Returns a transform that maps points from item space into window space.
+ Returns a transform that maps points from item space into window space.
*/
QTransform QQuickItemPrivate::itemToWindowTransform() const
{
// item's parent must not be itself, otherwise calling itemToWindowTransform() on it is infinite recursion
Q_ASSERT(!parentItem || QQuickItemPrivate::get(parentItem) != this);
QTransform rv = parentItem ? QQuickItemPrivate::get(parentItem)->itemToWindowTransform() : QTransform();
- itemToParentTransform(rv);
+ itemToParentTransform(&rv);
return rv;
}
/*!
-Motifies \a t with this items local transform relative to its parent.
+ Modifies \a t with this item's local transform relative to its parent.
*/
-void QQuickItemPrivate::itemToParentTransform(QTransform &t) const
+void QQuickItemPrivate::itemToParentTransform(QTransform *t) const
{
/* Read the current x and y values. As this is an internal method,
we don't care about it being usable in bindings. Instead, we
@@ -3098,21 +3180,21 @@ void QQuickItemPrivate::itemToParentTransform(QTransform &t) const
qreal x = this->x.valueBypassingBindings();
qreal y = this->y.valueBypassingBindings();
if (x || y)
- t.translate(x, y);
+ t->translate(x, y);
if (!transforms.isEmpty()) {
- QMatrix4x4 m(t);
+ QMatrix4x4 m(*t);
for (int ii = transforms.size() - 1; ii >= 0; --ii)
transforms.at(ii)->applyTo(&m);
- t = m.toTransform();
+ *t = m.toTransform();
}
if (scale() != 1. || rotation() != 0.) {
QPointF tp = computeTransformOrigin();
- t.translate(tp.x(), tp.y());
- t.scale(scale(), scale());
- t.rotate(rotation());
- t.translate(-tp.x(), -tp.y());
+ t->translate(tp.x(), tp.y());
+ t->scale(scale(), scale());
+ t->rotate(rotation());
+ t->translate(-tp.x(), -tp.y());
}
}
@@ -3201,6 +3283,8 @@ QQuickItemPrivate::QQuickItemPrivate()
, maybeHasSubsceneDeliveryAgent(true)
, subtreeTransformChangedEnabled(true)
, inDestructor(false)
+ , focusReason(Qt::OtherFocusReason)
+ , focusPolicy(Qt::NoFocus)
, dirtyAttributes(0)
, nextDirtyItem(nullptr)
, prevDirtyItem(nullptr)
@@ -3218,6 +3302,7 @@ QQuickItemPrivate::QQuickItemPrivate()
, baselineOffset(0)
, itemNodeInstance(nullptr)
, paintNode(nullptr)
+ , szPolicy(QLayoutPolicy::Fixed, QLayoutPolicy::Fixed)
{
}
@@ -3242,6 +3327,17 @@ void QQuickItemPrivate::init(QQuickItem *parent)
}
}
+QLayoutPolicy QQuickItemPrivate::sizePolicy() const
+{
+ return szPolicy;
+}
+
+void QQuickItemPrivate::setSizePolicy(const QLayoutPolicy::Policy& horizontalPolicy, const QLayoutPolicy::Policy& verticalPolicy)
+{
+ szPolicy.setHorizontalPolicy(horizontalPolicy);
+ szPolicy.setVerticalPolicy(verticalPolicy);
+}
+
void QQuickItemPrivate::data_append(QQmlListProperty<QObject> *prop, QObject *o)
{
if (!o)
@@ -3251,34 +3347,15 @@ void QQuickItemPrivate::data_append(QQmlListProperty<QObject> *prop, QObject *o)
if (QQuickItem *item = qmlobject_cast<QQuickItem *>(o)) {
item->setParentItem(that);
- } else {
- if (QQuickPointerHandler *pointerHandler = qmlobject_cast<QQuickPointerHandler *>(o)) {
- if (pointerHandler->parent() != that) {
- qCDebug(lcHandlerParent) << "reparenting handler" << pointerHandler << ":" << pointerHandler->parent() << "->" << that;
- pointerHandler->setParent(that);
- }
- QQuickItemPrivate::get(that)->addPointerHandler(pointerHandler);
- } else {
- QQuickWindow *thisWindow = qmlobject_cast<QQuickWindow *>(o);
- QQuickItem *item = that;
- QQuickWindow *itemWindow = that->window();
- while (!itemWindow && item && item->parentItem()) {
- item = item->parentItem();
- itemWindow = item->window();
- }
-
- if (thisWindow) {
- if (itemWindow) {
- qCDebug(lcTransient) << thisWindow << "is transient for" << itemWindow;
- thisWindow->setTransientParent(itemWindow);
- } else {
- QObject::connect(item, SIGNAL(windowChanged(QQuickWindow*)),
- thisWindow, SLOT(setTransientParent_helper(QQuickWindow*)));
- }
- }
- o->setParent(that);
- resources_append(prop, o);
+ } else if (QQuickPointerHandler *pointerHandler = qmlobject_cast<QQuickPointerHandler *>(o)) {
+ if (pointerHandler->parent() != that) {
+ qCDebug(lcHandlerParent) << "reparenting handler" << pointerHandler << ":" << pointerHandler->parent() << "->" << that;
+ pointerHandler->setParent(that);
}
+ QQuickItemPrivate::get(that)->addPointerHandler(pointerHandler);
+ } else {
+ o->setParent(that);
+ resources_append(prop, o);
}
}
@@ -3354,6 +3431,22 @@ void QQuickItemPrivate::data_clear(QQmlListProperty<QObject> *property)
children_clear(&childrenProperty);
}
+void QQuickItemPrivate::data_removeLast(QQmlListProperty<QObject> *property)
+{
+ QQuickItem *item = static_cast<QQuickItem*>(property->object);
+ QQuickItemPrivate *privateItem = QQuickItemPrivate::get(item);
+
+ QQmlListProperty<QQuickItem> childrenProperty = privateItem->children();
+ if (children_count(&childrenProperty) > 0) {
+ children_removeLast(&childrenProperty);
+ return;
+ }
+
+ QQmlListProperty<QObject> resourcesProperty = privateItem->resources();
+ if (resources_count(&resourcesProperty) > 0)
+ resources_removeLast(&resourcesProperty);
+}
+
QObject *QQuickItemPrivate::resources_at(QQmlListProperty<QObject> *prop, qsizetype index)
{
QQuickItemPrivate *quickItemPrivate = QQuickItemPrivate::get(static_cast<QQuickItem *>(prop->object));
@@ -3390,6 +3483,21 @@ void QQuickItemPrivate::resources_clear(QQmlListProperty<QObject> *prop)
}
}
+void QQuickItemPrivate::resources_removeLast(QQmlListProperty<QObject> *prop)
+{
+ QQuickItem *quickItem = static_cast<QQuickItem *>(prop->object);
+ QQuickItemPrivate *quickItemPrivate = QQuickItemPrivate::get(quickItem);
+ if (quickItemPrivate->extra.isAllocated()) {//If extra is not allocated resources is empty.
+ QList<QObject *> *resources = &quickItemPrivate->extra->resourcesList;
+ if (resources->isEmpty())
+ return;
+
+ qmlobject_disconnect(resources->last(), QObject, SIGNAL(destroyed(QObject*)),
+ quickItem, QQuickItem, SLOT(_q_resourceObjectDeleted(QObject*)));
+ resources->removeLast();
+ }
+}
+
QQuickItem *QQuickItemPrivate::children_at(QQmlListProperty<QQuickItem> *prop, qsizetype index)
{
QQuickItemPrivate *p = QQuickItemPrivate::get(static_cast<QQuickItem *>(prop->object));
@@ -3425,6 +3533,14 @@ void QQuickItemPrivate::children_clear(QQmlListProperty<QQuickItem> *prop)
p->childItems.at(0)->setParentItem(nullptr);
}
+void QQuickItemPrivate::children_removeLast(QQmlListProperty<QQuickItem> *prop)
+{
+ QQuickItem *that = static_cast<QQuickItem *>(prop->object);
+ QQuickItemPrivate *p = QQuickItemPrivate::get(that);
+ if (!p->childItems.isEmpty())
+ p->childItems.last()->setParentItem(nullptr);
+}
+
qsizetype QQuickItemPrivate::visibleChildren_count(QQmlListProperty<QQuickItem> *prop)
{
QQuickItemPrivate *p = QQuickItemPrivate::get(static_cast<QQuickItem *>(prop->object));
@@ -3653,10 +3769,16 @@ void QQuickItemPrivate::siblingOrderChanged()
QQmlListProperty<QObject> QQuickItemPrivate::data()
{
- return QQmlListProperty<QObject>(q_func(), nullptr, QQuickItemPrivate::data_append,
- QQuickItemPrivate::data_count,
- QQuickItemPrivate::data_at,
- QQuickItemPrivate::data_clear);
+ // Do not synthesize replace().
+ // It would be extremely expensive and wouldn't work with most methods.
+ QQmlListProperty<QObject> result;
+ result.object = q_func();
+ result.append = QQuickItemPrivate::data_append;
+ result.count = QQuickItemPrivate::data_count;
+ result.at = QQuickItemPrivate::data_at;
+ result.clear = QQuickItemPrivate::data_clear;
+ result.removeLast = QQuickItemPrivate::data_removeLast;
+ return result;
}
/*!
@@ -4021,7 +4143,7 @@ void QQuickItem::inputMethodEvent(QInputMethodEvent *event)
/*!
This event handler can be reimplemented in a subclass to receive focus-in
- events for an item. The event information is provided by the \c event
+ events for an item. The event information is provided by the \a event
parameter.
\input item.qdocinc accepting-events
@@ -4029,8 +4151,9 @@ void QQuickItem::inputMethodEvent(QInputMethodEvent *event)
If you do reimplement this function, you should call the base class
implementation.
*/
-void QQuickItem::focusInEvent(QFocusEvent * /*event*/)
+void QQuickItem::focusInEvent(QFocusEvent *event)
{
+ Q_D(QQuickItem);
#if QT_CONFIG(accessibility)
if (QAccessible::isActive()) {
if (QObject *acc = QQuickAccessibleAttached::findAccessible(this)) {
@@ -4039,17 +4162,20 @@ void QQuickItem::focusInEvent(QFocusEvent * /*event*/)
}
}
#endif
+ d->setLastFocusChangeReason(event->reason());
}
/*!
This event handler can be reimplemented in a subclass to receive focus-out
- events for an item. The event information is provided by the \c event
+ events for an item. The event information is provided by the \a event
parameter.
\input item.qdocinc accepting-events
*/
-void QQuickItem::focusOutEvent(QFocusEvent * /*event*/)
+void QQuickItem::focusOutEvent(QFocusEvent *event)
{
+ Q_D(QQuickItem);
+ d->setLastFocusChangeReason(event->reason());
}
/*!
@@ -4274,7 +4400,11 @@ void QQuickItem::dropEvent(QDropEvent *event)
This method will only be called if filtersChildMouseEvents() is \c true.
Return \c true if the specified \a event should not be passed on to the
- specified child \a item, and \c false otherwise.
+ specified child \a item, and \c false otherwise. If you return \c true, you
+ should also \l {QEvent::accept()}{accept} or \l {QEvent::ignore()}{ignore}
+ the \a event, to signal if event propagation should stop or continue.
+ The \a event will, however, always be sent to all childMouseEventFilters
+ up the parent chain.
\note Despite the name, this function filters all QPointerEvent instances
during delivery to all children (typically mouse, touch, and tablet
@@ -4516,7 +4646,7 @@ void QQuickItem::ensurePolished()
}
#if QT_DEPRECATED_SINCE(6, 5)
-static bool unwrapMapFromToFromItemArgs(QQmlV4Function *args, const QQuickItem *itemForWarning, const QString &functionNameForWarning,
+static bool unwrapMapFromToFromItemArgs(QQmlV4FunctionPtr args, const QQuickItem *itemForWarning, const QString &functionNameForWarning,
QQuickItem **itemObj, qreal *x, qreal *y, qreal *w, qreal *h, bool *isRect)
{
QV4::ExecutionEngine *v4 = args->v4engine();
@@ -4613,7 +4743,7 @@ static bool unwrapMapFromToFromItemArgs(QQmlV4Function *args, const QQuickItem *
\input item.qdocinc mapping
If \a item is a \c null value, this maps the point or rect from the coordinate system of
- the root QML view.
+ the \l{Scene Coordinates}{scene}.
The versions accepting point and rect are since Qt 5.15.
*/
@@ -4622,7 +4752,7 @@ static bool unwrapMapFromToFromItemArgs(QQmlV4Function *args, const QQuickItem *
/*!
\internal
*/
-void QQuickItem::mapFromItem(QQmlV4Function *args) const
+void QQuickItem::mapFromItem(QQmlV4FunctionPtr args) const
{
QV4::ExecutionEngine *v4 = args->v4engine();
QV4::Scope scope(v4);
@@ -4671,7 +4801,7 @@ QTransform QQuickItem::itemTransform(QQuickItem *other, bool *ok) const
\input item.qdocinc mapping
If \a item is a \c null value, this maps the point or rect to the coordinate system of the
- root QML view.
+ \l{Scene Coordinates}{scene}.
The versions accepting point and rect are since Qt 5.15.
*/
@@ -4680,7 +4810,7 @@ QTransform QQuickItem::itemTransform(QQuickItem *other, bool *ok) const
/*!
\internal
*/
-void QQuickItem::mapToItem(QQmlV4Function *args) const
+void QQuickItem::mapToItem(QQmlV4FunctionPtr args) const
{
QV4::ExecutionEngine *v4 = args->v4engine();
QV4::Scope scope(v4);
@@ -4698,7 +4828,7 @@ void QQuickItem::mapToItem(QQmlV4Function *args) const
args->setReturnValue(rv.asReturnedValue());
}
-static bool unwrapMapFromToFromGlobalArgs(QQmlV4Function *args, const QQuickItem *itemForWarning, const QString &functionNameForWarning, qreal *x, qreal *y)
+static bool unwrapMapFromToFromGlobalArgs(QQmlV4FunctionPtr args, const QQuickItem *itemForWarning, const QString &functionNameForWarning, qreal *x, qreal *y)
{
QV4::ExecutionEngine *v4 = args->v4engine();
if (args->length() != 1 && args->length() != 2) {
@@ -4756,7 +4886,7 @@ static bool unwrapMapFromToFromGlobalArgs(QQmlV4Function *args, const QQuickItem
/*!
\internal
*/
-void QQuickItem::mapFromGlobal(QQmlV4Function *args) const
+void QQuickItem::mapFromGlobal(QQmlV4FunctionPtr args) const
{
QV4::ExecutionEngine *v4 = args->v4engine();
QV4::Scope scope(v4);
@@ -4786,7 +4916,7 @@ void QQuickItem::mapFromGlobal(QQmlV4Function *args) const
/*!
\internal
*/
-void QQuickItem::mapToGlobal(QQmlV4Function *args) const
+void QQuickItem::mapToGlobal(QQmlV4FunctionPtr args) const
{
QV4::ExecutionEngine *v4 = args->v4engine();
QV4::Scope scope(v4);
@@ -4860,7 +4990,6 @@ void QQuickItem::forceActiveFocus()
void QQuickItem::forceActiveFocus(Qt::FocusReason reason)
{
- Q_D(QQuickItem);
setFocus(true, reason);
QQuickItem *parent = parentItem();
QQuickItem *scope = nullptr;
@@ -4872,14 +5001,6 @@ void QQuickItem::forceActiveFocus(Qt::FocusReason reason)
}
parent = parent->parentItem();
}
- // In certain reparenting scenarios, d->focus might be true and the scope
- // might also have focus, so that setFocus() returns early without actually
- // acquiring active focus, because it thinks it already has it. In that
- // case, try to set the DeliveryAgent's active focus. (QTBUG-89736).
- if (scope && !d->activeFocus) {
- if (auto da = d->deliveryAgentPrivate())
- da->setFocusInScope(scope, this, Qt::OtherFocusReason);
- }
}
/*!
@@ -4982,12 +5103,17 @@ void QQuickItemPrivate::dumpItemTree(int indent) const
{
Q_Q(const QQuickItem);
- qDebug().nospace().noquote() << QString(indent * 4, QLatin1Char(' ')) <<
+ const auto indentStr = QString(indent * 4, QLatin1Char(' '));
+ qDebug().nospace().noquote() << indentStr <<
#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
const_cast<QQuickItem *>(q);
#else
q;
#endif
+ if (extra.isAllocated()) {
+ for (const auto handler : extra->pointerHandlers)
+ qDebug().nospace().noquote() << indentStr << u" \u26ee " << handler;
+ }
for (const QQuickItem *ch : childItems) {
auto itemPriv = QQuickItemPrivate::get(ch);
itemPriv->dumpItemTree(indent + 1);
@@ -4996,10 +5122,16 @@ void QQuickItemPrivate::dumpItemTree(int indent) const
QQmlListProperty<QObject> QQuickItemPrivate::resources()
{
- return QQmlListProperty<QObject>(q_func(), nullptr, QQuickItemPrivate::resources_append,
- QQuickItemPrivate::resources_count,
- QQuickItemPrivate::resources_at,
- QQuickItemPrivate::resources_clear);
+ // Do not synthesize replace().
+ // It would be extremely expensive and wouldn't work with most methods.
+ QQmlListProperty<QObject> result;
+ result.object = q_func();
+ result.append = QQuickItemPrivate::resources_append;
+ result.count = QQuickItemPrivate::resources_count;
+ result.at = QQuickItemPrivate::resources_at;
+ result.clear = QQuickItemPrivate::resources_clear;
+ result.removeLast = QQuickItemPrivate::resources_removeLast;
+ return result;
}
/*!
@@ -5021,11 +5153,16 @@ QQmlListProperty<QObject> QQuickItemPrivate::resources()
*/
QQmlListProperty<QQuickItem> QQuickItemPrivate::children()
{
- return QQmlListProperty<QQuickItem>(q_func(), nullptr, QQuickItemPrivate::children_append,
- QQuickItemPrivate::children_count,
- QQuickItemPrivate::children_at,
- QQuickItemPrivate::children_clear);
-
+ // Do not synthesize replace().
+ // It would be extremely expensive and wouldn't work with most methods.
+ QQmlListProperty<QQuickItem> result;
+ result.object = q_func();
+ result.append = QQuickItemPrivate::children_append;
+ result.count = QQuickItemPrivate::children_count;
+ result.at = QQuickItemPrivate::children_at;
+ result.clear = QQuickItemPrivate::children_clear;
+ result.removeLast = QQuickItemPrivate::children_removeLast;
+ return result;
}
/*!
@@ -5319,7 +5456,8 @@ bool QQuickItemPrivate::transformChanged(QQuickItem *transformedItem)
if (subtreeTransformChangedEnabled) {
// Inform the children in paint order: by the time we visit leaf items,
// they can see any consequences in their parents
- for (auto child : paintOrderChildItems())
+ const auto children = paintOrderChildItems();
+ for (QQuickItem *child : children)
childWantsIt |= QQuickItemPrivate::get(child)->transformChanged(transformedItem);
}
@@ -5337,18 +5475,35 @@ bool QQuickItemPrivate::transformChanged(QQuickItem *transformedItem)
}
// If ItemObservesViewport, clipRect() calculates the intersection with the viewport;
// so each time the item moves in the viewport, its clipnode needs to be updated.
- if (thisWantsIt && q->clip())
+ if (thisWantsIt && q->clip() && !(dirtyAttributes & QQuickItemPrivate::Clip))
dirty(QQuickItemPrivate::Clip);
return ret;
}
+/*! \internal
+ Returns the new position (proposed values for the x and y properties)
+ to which this item should be moved to compensate for the given change
+ in scale from \a startScale to \a activeScale and in rotation from
+ \a startRotation to \a activeRotation. \a centroidParentPos is the
+ point that we wish to hold in place (and then apply \a activeTranslation to),
+ in this item's parent's coordinate system. \a startPos is this item's
+ position in its parent's coordinate system when the gesture began.
+ \a activeTranslation is the amount of translation that should be added to
+ the return value, i.e. the displacement by which the centroid is expected
+ to move.
+
+ If \a activeTranslation is \c (0, 0) the centroid is to be held in place.
+ If \a activeScale is \c 1, it means scale is intended to be held constant,
+ the same as \a startScale. If \a activeRotation is \c 0, it means rotation
+ is intended to be held constant, the same as \a startRotation.
+*/
QPointF QQuickItemPrivate::adjustedPosForTransform(const QPointF &centroidParentPos,
const QPointF &startPos,
- const QVector2D &activeTranslation, //[0,0] means no additional translation from startPos
+ const QVector2D &activeTranslation,
qreal startScale,
- qreal activeScale, // 1.0 means no additional scale from startScale
+ qreal activeScale,
qreal startRotation,
- qreal activeRotation) // 0.0 means no additional rotation from startRotation
+ qreal activeRotation)
{
Q_Q(QQuickItem);
QVector3D xformOrigin(q->transformOriginPoint());
@@ -5474,6 +5629,41 @@ bool QQuickItemPrivate::filterKeyEvent(QKeyEvent *e, bool post)
return e->isAccepted();
}
+void QQuickItemPrivate::deliverPointerEvent(QEvent *event)
+{
+ Q_Q(QQuickItem);
+ const auto eventType = event->type();
+ const bool focusAccepted = setFocusIfNeeded(eventType);
+
+ switch (eventType) {
+ case QEvent::MouseButtonPress:
+ q->mousePressEvent(static_cast<QMouseEvent *>(event));
+ break;
+ case QEvent::MouseButtonRelease:
+ q->mouseReleaseEvent(static_cast<QMouseEvent *>(event));
+ break;
+ case QEvent::MouseButtonDblClick:
+ q->mouseDoubleClickEvent(static_cast<QMouseEvent *>(event));
+ break;
+#if QT_CONFIG(wheelevent)
+ case QEvent::Wheel:
+ q->wheelEvent(static_cast<QWheelEvent*>(event));
+ break;
+#endif
+ case QEvent::TouchBegin:
+ case QEvent::TouchUpdate:
+ case QEvent::TouchEnd:
+ case QEvent::TouchCancel:
+ q->touchEvent(static_cast<QTouchEvent *>(event));
+ break;
+ default:
+ break;
+ }
+
+ if (focusAccepted)
+ event->accept();
+}
+
void QQuickItemPrivate::deliverKeyEvent(QKeyEvent *e)
{
Q_Q(QQuickItem);
@@ -5542,7 +5732,7 @@ void QQuickItemPrivate::deliverInputMethodEvent(QInputMethodEvent *e)
void QQuickItemPrivate::deliverShortcutOverrideEvent(QKeyEvent *event)
{
if (extra.isAllocated() && extra->keyHandler)
- extra->keyHandler->shortcutOverride(event);
+ extra->keyHandler->shortcutOverrideEvent(event);
else
event->ignore();
}
@@ -5945,8 +6135,8 @@ void QQuickItem::setZ(qreal v)
d->dirty(QQuickItemPrivate::ZValue);
if (d->parentItem) {
- QQuickItemPrivate::get(d->parentItem)->dirty(QQuickItemPrivate::ChildrenStackingChanged);
QQuickItemPrivate::get(d->parentItem)->markSortedChildrenDirty(this);
+ QQuickItemPrivate::get(d->parentItem)->dirty(QQuickItemPrivate::ChildrenStackingChanged);
}
emit zChanged();
@@ -6310,7 +6500,11 @@ void QQuickItem::setOpacity(qreal newOpacity)
\note This property's value is only affected by changes to this property or
the parent's \c visible property. It does not change, for example, if this
- item moves off-screen, or if the \l opacity changes to 0.
+ item moves off-screen, or if the \l opacity changes to 0. However, for
+ historical reasons, this property is true after the item's construction, even
+ if the item hasn't been added to a scene yet. Changing or reading this
+ property of an item that has not been added to a scene might not produce
+ the expected results.
\note The notification signal for this property gets emitted during destruction
of the visual parent. C++ signal handlers cannot assume that items in the
@@ -6380,9 +6574,9 @@ void QQuickItem::setVisible(bool v)
Thus, a disabled item can continue to receive hover events, even when this
property is \c false. This makes it possible to show informational feedback
(such as \l ToolTip) even when an interactive item is disabled.
- The same is also true for any \l {HoverHandlers}{QQuickHoverHandler}
+ The same is also true for any \l {HoverHandler}{HoverHandlers}
added as children of the item. A HoverHandler can, however, be
- \l{disabled}{QQuickHoverHandler::enabled} explicitly, or for example
+ \l {PointerHandler::enabled}{disabled} explicitly, or for example
be bound to the \c enabled state of the item.
\sa visible
@@ -6561,9 +6755,6 @@ QString QQuickItemPrivate::dirtyToString() const
void QQuickItemPrivate::dirty(DirtyType type)
{
Q_Q(QQuickItem);
- if (type & (TransformOrigin | Transform | BasicTransform | Position | Size))
- transformChanged(q);
-
if (!(dirtyAttributes & type) || (window && !prevDirtyItem)) {
dirtyAttributes |= type;
if (window && componentComplete) {
@@ -6571,6 +6762,8 @@ void QQuickItemPrivate::dirty(DirtyType type)
QQuickWindowPrivate::get(window)->dirtyItem(q);
}
}
+ if (type & (TransformOrigin | Transform | BasicTransform | Position | Size | Clip))
+ transformChanged(q);
}
void QQuickItemPrivate::addToDirtyList()
@@ -6666,8 +6859,18 @@ void QQuickItemPrivate::itemChange(QQuickItem::ItemChange change, const QQuickIt
switch (change) {
case QQuickItem::ItemChildAddedChange: {
q->itemChange(change, data);
- if (!subtreeTransformChangedEnabled)
- subtreeTransformChangedEnabled = true;
+ // The newly added child or any of its descendants may have
+ // ItemObservesViewport set, in which case we need to both
+ // inform the item that the transform has changed, and re-apply
+ // subtreeTransformChangedEnabled to both this item and its
+ // ancestors.
+ if (QQuickItemPrivate::get(data.item)->transformChanged(q)) {
+ if (!subtreeTransformChangedEnabled) {
+ qCDebug(lcVP) << "turned on transformChanged notification for subtree of" << q;
+ subtreeTransformChangedEnabled = true;
+ }
+ enableSubtreeChangeNotificationsForParentHierachy();
+ }
notifyChangeListeners(QQuickItemPrivate::Children, &QQuickItemChangeListener::itemChildAdded, q, data.item);
break;
}
@@ -6779,6 +6982,7 @@ void QQuickItem::setSmooth(bool smooth)
This property holds whether the item wants to be in the tab focus
chain. By default, this is set to \c false.
*/
+// TODO FOCUS: Deprecate
bool QQuickItem::activeFocusOnTab() const
{
Q_D(const QQuickItem);
@@ -6895,15 +7099,23 @@ void QQuickItem::setFlag(Flag flag, bool enabled)
else
setFlags((Flags)(d->flags & ~(quint32)flag));
- if (enabled && flag == ItemObservesViewport) {
- QQuickItem *par = parentItem();
- while (par) {
- auto parPriv = QQuickItemPrivate::get(par);
- if (!parPriv->subtreeTransformChangedEnabled)
- qCDebug(lcVP) << "turned on transformChanged notification for subtree of" << par;
- parPriv->subtreeTransformChangedEnabled = true;
- par = par->parentItem();
- }
+ // We don't return early if the flag did not change. That's useful in case
+ // we need to intentionally trigger this parent-chain traversal again.
+ if (enabled && flag == ItemObservesViewport)
+ d->enableSubtreeChangeNotificationsForParentHierachy();
+}
+
+void QQuickItemPrivate::enableSubtreeChangeNotificationsForParentHierachy()
+{
+ Q_Q(QQuickItem);
+
+ QQuickItem *par = q->parentItem();
+ while (par) {
+ auto parPriv = QQuickItemPrivate::get(par);
+ if (!parPriv->subtreeTransformChangedEnabled)
+ qCDebug(lcVP) << "turned on transformChanged notification for subtree of" << par;
+ parPriv->subtreeTransformChangedEnabled = true;
+ par = par->parentItem();
}
}
@@ -6994,15 +7206,17 @@ void QQuickItem::setX(qreal v)
if (qt_is_nan(v))
return;
- const qreal oldx = d->x;
+ const qreal oldx = d->x.valueBypassingBindings();
if (oldx == v)
return;
- d->x = v;
+ d->x.setValueBypassingBindings(v);
d->dirty(QQuickItemPrivate::Position);
- const qreal y = d->y, w = d->width, h = d->height;
+ const qreal y = d->y.valueBypassingBindings();
+ const qreal w = d->width.valueBypassingBindings();
+ const qreal h = d->height.valueBypassingBindings();
geometryChange(QRectF(v, y, w, h), QRectF(oldx, y, w, h));
}
@@ -7013,17 +7227,19 @@ void QQuickItem::setY(qreal v)
if (qt_is_nan(v))
return;
- const qreal oldy = d->y;
+ const qreal oldy = d->y.valueBypassingBindings();
if (oldy == v)
return;
- d->y = v;
+ d->y.setValueBypassingBindings(v);
d->dirty(QQuickItemPrivate::Position);
// we use v instead of d->y, as that avoid a method call
// and we have v anyway in scope
- const qreal x = d->x, w = d->width, h = d->height;
+ const qreal x = d->x.valueBypassingBindings();
+ const qreal w = d->width.valueBypassingBindings();
+ const qreal h = d->height.valueBypassingBindings();
geometryChange(QRectF(x, v, w, h), QRectF(x, oldy, w, h));
}
@@ -7033,11 +7249,12 @@ void QQuickItem::setY(qreal v)
void QQuickItem::setPosition(const QPointF &pos)
{
Q_D(QQuickItem);
- if (QPointF(d->x, d->y) == pos)
- return;
- const qreal oldx = d->x;
- const qreal oldy = d->y;
+ const qreal oldx = d->x.valueBypassingBindings();
+ const qreal oldy = d->y.valueBypassingBindings();
+
+ if (QPointF(oldx, oldy) == pos)
+ return;
/* This preserves the bindings, because that was what the code used to do
The effect of this is that you can have
@@ -7057,7 +7274,8 @@ void QQuickItem::setPosition(const QPointF &pos)
d->dirty(QQuickItemPrivate::Position);
- const qreal w = d->width, h = d->height;
+ const qreal w = d->width.valueBypassingBindings();
+ const qreal h = d->height.valueBypassingBindings();
geometryChange(QRectF(pos.x(), pos.y(), w, h), QRectF(oldx, oldy, w, h));
}
@@ -7093,15 +7311,17 @@ void QQuickItem::setWidth(qreal w)
return;
d->widthValidFlag = true;
- const qreal oldWidth = d->width;
+ const qreal oldWidth = d->width.valueBypassingBindings();
if (oldWidth == w)
return;
- d->width = w;
+ d->width.setValueBypassingBindings(w);
d->dirty(QQuickItemPrivate::Size);
- const qreal x = d->x, y = d->y, h = d->height;
+ const qreal x = d->x.valueBypassingBindings();
+ const qreal y = d->y.valueBypassingBindings();
+ const qreal h = d->height.valueBypassingBindings();
geometryChange(QRectF(x, y, w, h), QRectF(x, y, oldWidth, h));
}
@@ -7299,15 +7519,17 @@ void QQuickItem::setHeight(qreal h)
return;
d->heightValidFlag = true;
- const qreal oldHeight = d->height;
+ const qreal oldHeight = d->height.valueBypassingBindings();
if (oldHeight == h)
return;
- d->height = h;
+ d->height.setValueBypassingBindings(h);
d->dirty(QQuickItemPrivate::Size);
- const qreal x = d->x, y = d->y, w = d->width;
+ const qreal x = d->x.valueBypassingBindings();
+ const qreal y = d->y.valueBypassingBindings();
+ const qreal w = d->width.valueBypassingBindings();
geometryChange(QRectF(x, y, w, h), QRectF(x, y, w, oldHeight));
}
@@ -7411,11 +7633,11 @@ void QQuickItem::setImplicitSize(qreal w, qreal h)
const qreal oldHeight = height;
if (!wDone) {
width = w;
- d->width = w;
+ d->width.setValueBypassingBindings(w);
}
if (!hDone) {
height = h;
- d->height = h;
+ d->height.setValueBypassingBindings(h);
}
d->dirty(QQuickItemPrivate::Size);
@@ -7471,17 +7693,19 @@ void QQuickItem::setSize(const QSizeF &size)
d->heightValidFlag = true;
d->widthValidFlag = true;
- if (d->width == size.width() && d->height == size.height())
+ const qreal oldHeight = d->height.valueBypassingBindings();
+ const qreal oldWidth = d->width.valueBypassingBindings();
+
+ if (oldWidth == size.width() && oldHeight == size.height())
return;
- const qreal oldHeight = d->height;
- const qreal oldWidth = d->width;
d->height.setValueBypassingBindings(size.height());
d->width.setValueBypassingBindings(size.width());
d->dirty(QQuickItemPrivate::Size);
- const qreal x = d->x, y = d->y;
+ const qreal x = d->x.valueBypassingBindings();
+ const qreal y = d->y.valueBypassingBindings();
geometryChange(QRectF(x, y, size.width(), size.height()), QRectF(x, y, oldWidth, oldHeight));
}
@@ -7662,15 +7886,16 @@ void QQuickItem::setFocus(bool focus)
void QQuickItem::setFocus(bool focus, Qt::FocusReason reason)
{
Q_D(QQuickItem);
- if (d->focus == focus)
+ // Need to find our nearest focus scope
+ QQuickItem *scope = parentItem();
+ while (scope && !scope->isFocusScope() && scope->parentItem())
+ scope = scope->parentItem();
+
+ if (d->focus == focus && (!focus || !scope || QQuickItemPrivate::get(scope)->subFocusItem == this))
return;
bool notifyListeners = false;
if (d->window || d->parentItem) {
- // Need to find our nearest focus scope
- QQuickItem *scope = parentItem();
- while (scope && !scope->isFocusScope() && scope->parentItem())
- scope = scope->parentItem();
if (d->window) {
auto da = d->deliveryAgentPrivate();
Q_ASSERT(da);
@@ -7744,6 +7969,52 @@ QQuickItem *QQuickItem::scopedFocusItem() const
}
/*!
+ \qmlproperty enumeration QtQuick::Item::focusPolicy
+ \since 6.7
+
+ This property determines the way the item accepts focus.
+
+ \value Qt.TabFocus The item accepts focus by tabbing.
+ \value Qt.ClickFocus The item accepts focus by clicking.
+ \value Qt.StrongFocus The item accepts focus by both tabbing and clicking.
+ \value Qt.WheelFocus The item accepts focus by tabbing, clicking, and using the mouse wheel.
+ \value Qt.NoFocus The item does not accept focus.
+
+ \note This property was a member of the \l[QML]{Control} QML type in Qt 6.6 and earlier.
+*/
+/*!
+ \property QQuickItem::focusPolicy
+ \since 6.7
+
+ This property determines the way the item accepts focus.
+
+*/
+Qt::FocusPolicy QQuickItem::focusPolicy() const
+{
+ Q_D(const QQuickItem);
+ uint policy = d->focusPolicy;
+ if (activeFocusOnTab())
+ policy |= Qt::TabFocus;
+ return static_cast<Qt::FocusPolicy>(policy);
+}
+
+/*!
+ Sets the focus policy of this item to \a policy.
+
+ \sa focusPolicy()
+*/
+void QQuickItem::setFocusPolicy(Qt::FocusPolicy policy)
+{
+ Q_D(QQuickItem);
+ if (d->focusPolicy == policy)
+ return;
+
+ d->focusPolicy = policy;
+ setActiveFocusOnTab(policy & Qt::TabFocus);
+ emit focusPolicyChanged(policy);
+}
+
+/*!
Returns \c true if this item is an ancestor of \a child (i.e., if this item
is \a child's parent, or one of \a child's parent's ancestors).
@@ -8343,7 +8614,7 @@ void QQuickItem::setKeepTouchGrab(bool keep)
Returns \c true if this item contains \a point, which is in local coordinates;
returns \c false otherwise. This is the same check that is used for
hit-testing a QEventPoint during event delivery, and is affected by
- containmentMask() if it is set.
+ \l containmentMask if it is set.
*/
/*!
Returns \c true if this item contains \a point, which is in local coordinates;
@@ -8351,7 +8622,7 @@ void QQuickItem::setKeepTouchGrab(bool keep)
This function can be overridden in order to handle point collisions in items
with custom shapes. The default implementation checks whether the point is inside
- containmentMask() if it is set, or inside the bounding box otherwise.
+ \l containmentMask() if it is set, or inside the bounding box otherwise.
\note This method is used for hit-testing each QEventPoint during event
delivery, so the implementation should be kept as lightweight as possible.
@@ -8381,10 +8652,10 @@ bool QQuickItem::contains(const QPointF &point) const
\qmlproperty QObject* QtQuick::Item::containmentMask
\since 5.11
This property holds an optional mask for the Item to be used in the
- QtQuick::Item::contains() method. Its main use is currently to determine
+ \l contains() method. Its main use is currently to determine
whether a \l {QPointerEvent}{pointer event} has landed into the item or not.
- By default the \l contains method will return true for any point
+ By default the \c contains() method will return true for any point
within the Item's bounding box. \c containmentMask allows for
more fine-grained control. For example, if a custom C++
QQuickItem subclass with a specialized contains() method
@@ -8495,7 +8766,7 @@ void QQuickItem::setContainmentMask(QObject *mask)
\input item.qdocinc mapping
- If \a item is 0, this maps \a point to the coordinate system of the
+ If \a item is \nullptr, this maps \a point to the coordinate system of the
scene.
\sa {Concepts - Visual Coordinates in Qt Quick}
@@ -8503,8 +8774,13 @@ void QQuickItem::setContainmentMask(QObject *mask)
QPointF QQuickItem::mapToItem(const QQuickItem *item, const QPointF &point) const
{
QPointF p = mapToScene(point);
- if (item)
+ if (item) {
+ const QQuickWindow *itemWindow = item->window();
+ if (itemWindow != nullptr && itemWindow != window())
+ p = itemWindow->mapFromGlobal(window()->mapToGlobal(p));
+
p = item->mapFromScene(p);
+ }
return p;
}
@@ -8553,7 +8829,7 @@ QPointF QQuickItem::mapToGlobal(const QPointF &point) const
\input item.qdocinc mapping
- If \a item is 0, this maps \a rect to the coordinate system of the
+ If \a item is \nullptr, this maps \a rect to the coordinate system of the
scene.
\sa {Concepts - Visual Coordinates in Qt Quick}
@@ -8589,14 +8865,20 @@ QRectF QQuickItem::mapRectToScene(const QRectF &rect) const
\input item.qdocinc mapping
- If \a item is 0, this maps \a point from the coordinate system of the
+ If \a item is \nullptr, this maps \a point from the coordinate system of the
scene.
\sa {Concepts - Visual Coordinates in Qt Quick}
*/
QPointF QQuickItem::mapFromItem(const QQuickItem *item, const QPointF &point) const
{
- QPointF p = item?item->mapToScene(point):point;
+ QPointF p = point;
+ if (item) {
+ p = item->mapToScene(point);
+
+ if (item->window() != window())
+ p = window()->mapFromGlobal(item->window()->mapToGlobal(p));
+ }
return mapFromScene(p);
}
@@ -8656,7 +8938,7 @@ QPointF QQuickItem::mapFromGlobal(const QPointF &point) const
\input item.qdocinc mapping
- If \a item is 0, this maps \a rect from the coordinate system of the
+ If \a item is \nullptr, this maps \a rect from the coordinate system of the
scene.
\sa {Concepts - Visual Coordinates in Qt Quick}
@@ -8764,8 +9046,14 @@ bool QQuickItem::event(QEvent *ev)
case QEvent::TouchUpdate:
case QEvent::TouchEnd:
case QEvent::TouchCancel:
- touchEvent(static_cast<QTouchEvent*>(ev));
- break;
+ case QEvent::MouseButtonPress:
+ case QEvent::MouseButtonRelease:
+ case QEvent::MouseButtonDblClick:
+#if QT_CONFIG(wheelevent)
+ case QEvent::Wheel:
+#endif
+ d->deliverPointerEvent(ev);
+ break;
case QEvent::StyleAnimationUpdate:
if (isVisible()) {
ev->accept();
@@ -8797,20 +9085,6 @@ bool QQuickItem::event(QEvent *ev)
case QEvent::MouseMove:
mouseMoveEvent(static_cast<QMouseEvent*>(ev));
break;
- case QEvent::MouseButtonPress:
- mousePressEvent(static_cast<QMouseEvent*>(ev));
- break;
- case QEvent::MouseButtonRelease:
- mouseReleaseEvent(static_cast<QMouseEvent*>(ev));
- break;
- case QEvent::MouseButtonDblClick:
- mouseDoubleClickEvent(static_cast<QMouseEvent*>(ev));
- break;
-#if QT_CONFIG(wheelevent)
- case QEvent::Wheel:
- wheelEvent(static_cast<QWheelEvent*>(ev));
- break;
-#endif
#if QT_CONFIG(quick_draganddrop)
case QEvent::DragEnter:
dragEnterEvent(static_cast<QDragEnterEvent*>(ev));
@@ -8870,6 +9144,16 @@ QDebug operator<<(QDebug debug,
const QRectF rect(item->position(), QSizeF(item->width(), item->height()));
debug << item->metaObject()->className() << '(' << static_cast<void *>(item);
+
+ // Deferred properties will cause recursion when calling nameForObject
+ // before the component is completed, so guard against this situation.
+ if (item->isComponentComplete()) {
+ if (QQmlContext *context = qmlContext(item)) {
+ const auto objectId = context->nameForObject(item);
+ if (!objectId.isEmpty())
+ debug << ", id=" << objectId;
+ }
+ }
if (!item->objectName().isEmpty())
debug << ", name=" << item->objectName();
debug << ", parent=" << static_cast<void *>(item->parentItem())
@@ -9030,12 +9314,12 @@ void QQuickItemPrivate::localizedTouchEvent(const QTouchEvent *event, bool isFil
bool hasAnotherGrabber = pointGrabber && pointGrabber != q;
// if there's no exclusive grabber, look for passive grabbers during filtering
if (isFiltering && !pointGrabber) {
- auto pg = event->passiveGrabbers(p);
+ const auto pg = event->passiveGrabbers(p);
if (!pg.isEmpty()) {
// It seems unlikely to have multiple passive grabbers of one eventpoint with different grandparents.
// So hopefully if we start from one passive grabber and go up the parent chain from there,
// we will find any filtering parent items that exist.
- auto handler = qmlobject_cast<QQuickPointerHandler *>(pg.first());
+ auto handler = qmlobject_cast<QQuickPointerHandler *>(pg.constFirst());
if (handler)
pointGrabber = handler->parentItem();
}
@@ -9368,14 +9652,12 @@ void QQuickItemLayer::setMipmap(bool mipmap)
Modifying this property makes most sense when the \a layer.effect is also
specified.
- \list
- \li ShaderEffectSource.RGBA8
- \li ShaderEffectSource.RGBA16F
- \li ShaderEffectSource.RGBA32F
- \li ShaderEffectSource.Alpha - Starting with Qt 6.0, this value is not in use and has the same effect as RGBA8 in practice.
- \li ShaderEffectSource.RGB - Starting with Qt 6.0, this value is not in use and has the same effect as RGBA8 in practice.
- \li ShaderEffectSource.RGBA - Starting with Qt 6.0, this value is not in use and has the same effect as RGBA8 in practice.
- \endlist
+ \value ShaderEffectSource.RGBA8
+ \value ShaderEffectSource.RGBA16F
+ \value ShaderEffectSource.RGBA32F
+ \value ShaderEffectSource.Alpha Starting with Qt 6.0, this value is not in use and has the same effect as \c RGBA8 in practice.
+ \value ShaderEffectSource.RGB Starting with Qt 6.0, this value is not in use and has the same effect as \c RGBA8 in practice.
+ \value ShaderEffectSource.RGBA Starting with Qt 6.0, this value is not in use and has the same effect as \c RGBA8 in practice.
\sa {Item Layers}
*/
@@ -9496,12 +9778,10 @@ void QQuickItemLayer::setSize(const QSize &size)
Modifying this property makes most sense when the \a layer.effect is
specified.
- \list
- \li ShaderEffectSource.ClampToEdge - GL_CLAMP_TO_EDGE both horizontally and vertically
- \li ShaderEffectSource.RepeatHorizontally - GL_REPEAT horizontally, GL_CLAMP_TO_EDGE vertically
- \li ShaderEffectSource.RepeatVertically - GL_CLAMP_TO_EDGE horizontally, GL_REPEAT vertically
- \li ShaderEffectSource.Repeat - GL_REPEAT both horizontally and vertically
- \endlist
+ \value ShaderEffectSource.ClampToEdge GL_CLAMP_TO_EDGE both horizontally and vertically
+ \value ShaderEffectSource.RepeatHorizontally GL_REPEAT horizontally, GL_CLAMP_TO_EDGE vertically
+ \value ShaderEffectSource.RepeatVertically GL_CLAMP_TO_EDGE horizontally, GL_REPEAT vertically
+ \value ShaderEffectSource.Repeat GL_REPEAT both horizontally and vertically
\note Some OpenGL ES 2 implementations do not support the GL_REPEAT
wrap mode with non-power-of-two textures.
@@ -9531,11 +9811,9 @@ void QQuickItemLayer::setWrapMode(QQuickShaderEffectSource::WrapMode mode)
such as those specified by ShaderEffect. If no effect is specified for the layered
item, mirroring has no effect on the UI representation of the item.
- \list
- \li ShaderEffectSource.NoMirroring - No mirroring
- \li ShaderEffectSource.MirrorHorizontally - The generated texture is flipped along X-axis.
- \li ShaderEffectSource.MirrorVertically - The generated texture is flipped along Y-axis.
- \endlist
+ \value ShaderEffectSource.NoMirroring No mirroring
+ \value ShaderEffectSource.MirrorHorizontally The generated texture is flipped along X-axis.
+ \value ShaderEffectSource.MirrorVertically The generated texture is flipped along Y-axis.
*/
void QQuickItemLayer::setTextureMirroring(QQuickShaderEffectSource::TextureMirroring mirroring)
@@ -9721,13 +9999,20 @@ QQuickItemPrivate::ExtraData::ExtraData()
#if QT_CONFIG(accessibility)
-QAccessible::Role QQuickItemPrivate::accessibleRole() const
+QAccessible::Role QQuickItemPrivate::effectiveAccessibleRole() const
{
Q_Q(const QQuickItem);
- QQuickAccessibleAttached *accessibleAttached = qobject_cast<QQuickAccessibleAttached *>(qmlAttachedPropertiesObject<QQuickAccessibleAttached>(q, false));
- if (accessibleAttached)
- return accessibleAttached->role();
+ auto *attached = qmlAttachedPropertiesObject<QQuickAccessibleAttached>(q, false);
+ auto role = QAccessible::NoRole;
+ if (auto *accessibleAttached = qobject_cast<QQuickAccessibleAttached *>(attached))
+ role = accessibleAttached->role();
+ if (role == QAccessible::NoRole)
+ role = accessibleRole();
+ return role;
+}
+QAccessible::Role QQuickItemPrivate::accessibleRole() const
+{
return QAccessible::NoRole;
}
#endif
@@ -9758,7 +10043,7 @@ void QV4::Heap::QQuickItemWrapper::markObjects(QV4::Heap::Base *that, QV4::MarkS
QObjectWrapper::markObjects(that, markStack);
}
-quint64 QQuickItemPrivate::_q_createJSWrapper(QV4::ExecutionEngine *engine)
+quint64 QQuickItemPrivate::_q_createJSWrapper(QQmlV4ExecutionEnginePtr engine)
{
return (engine->memoryManager->allocate<QQuickItemWrapper>(q_func()))->asReturnedValue();
}
@@ -9802,6 +10087,9 @@ QPointF QQuickItem::mapToGlobal(qreal x, qreal y) const
QPointF QQuickItem::mapFromGlobal(qreal x, qreal y) const
{ return mapFromGlobal(QPointF(x, y)); }
+//! \internal
+QQuickItemChangeListener::~QQuickItemChangeListener() = default;
+
QT_END_NAMESPACE
#include <moc_qquickitem.cpp>
diff --git a/src/quick/items/qquickitem.h b/src/quick/items/qquickitem.h
index 6a1f8db8c8..6b54b1af24 100644
--- a/src/quick/items/qquickitem.h
+++ b/src/quick/items/qquickitem.h
@@ -45,7 +45,6 @@ private:
class QCursor;
class QQuickItemLayer;
-class QQmlV4Function;
class QQuickState;
class QQuickAnchorLine;
class QQuickTransition;
@@ -102,6 +101,8 @@ class Q_QUICK_EXPORT QQuickItem : public QObject, public QQmlParserStatus
Q_PROPERTY(bool activeFocus READ hasActiveFocus NOTIFY activeFocusChanged FINAL)
Q_PROPERTY(bool activeFocusOnTab READ activeFocusOnTab WRITE setActiveFocusOnTab NOTIFY activeFocusOnTabChanged FINAL REVISION(2, 1))
+ Q_PROPERTY(Qt::FocusPolicy focusPolicy READ focusPolicy WRITE setFocusPolicy NOTIFY focusPolicyChanged REVISION(6, 7))
+
Q_PROPERTY(qreal rotation READ rotation WRITE setRotation NOTIFY rotationChanged)
Q_PROPERTY(qreal scale READ scale WRITE setScale NOTIFY scaleChanged)
Q_PROPERTY(TransformOrigin transformOrigin READ transformOrigin WRITE setTransformOrigin NOTIFY transformOriginChanged)
@@ -120,7 +121,7 @@ class Q_QUICK_EXPORT QQuickItem : public QObject, public QQmlParserStatus
Q_CLASSINFO("DefaultProperty", "data")
Q_CLASSINFO("ParentProperty", "parent")
- Q_CLASSINFO("qt_QmlJSWrapperFactoryMethod", "_q_createJSWrapper(QV4::ExecutionEngine*)")
+ Q_CLASSINFO("qt_QmlJSWrapperFactoryMethod", "_q_createJSWrapper(QQmlV4ExecutionEnginePtr)")
QML_NAMED_ELEMENT(Item)
QML_ADDED_IN_VERSION(2, 0)
@@ -270,6 +271,9 @@ public:
bool isFocusScope() const;
QQuickItem *scopedFocusItem() const;
+ Qt::FocusPolicy focusPolicy() const;
+ void setFocusPolicy(Qt::FocusPolicy policy);
+
bool isAncestorOf(const QQuickItem *child) const;
Qt::MouseButtons acceptedMouseButtons() const;
@@ -318,7 +322,7 @@ public:
#if QT_DEPRECATED_SINCE(6, 5)
QT_DEPRECATED_VERSION_X_6_5("Use typed overload or mapRectFromItem")
- void mapFromItem(QQmlV4Function*) const;
+ void mapFromItem(QQmlV4FunctionPtr) const;
#endif
Q_INVOKABLE QPointF mapFromItem(const QQuickItem *item, const QPointF &point) const;
// overloads mainly exist for QML
@@ -328,7 +332,7 @@ public:
#if QT_DEPRECATED_SINCE(6, 5)
QT_DEPRECATED_VERSION_X_6_5("Use typed overload or mapRectToItem")
- void mapToItem(QQmlV4Function*) const;
+ void mapToItem(QQmlV4FunctionPtr) const;
#endif
Q_INVOKABLE QPointF mapToItem(const QQuickItem *item, const QPointF &point) const;
// overloads mainly exist for QML
@@ -338,7 +342,7 @@ public:
#if QT_DEPRECATED_SINCE(6, 5)
QT_DEPRECATED_VERSION_X_6_5("Use the typed overload")
- Q_REVISION(2, 7) void mapFromGlobal(QQmlV4Function*) const;
+ Q_REVISION(2, 7) void mapFromGlobal(QQmlV4FunctionPtr) const;
#endif
Q_REVISION(2, 7) Q_INVOKABLE QPointF mapFromGlobal(qreal x, qreal y) const;
// overload mainly exists for QML
@@ -346,7 +350,7 @@ public:
#if QT_DEPRECATED_SINCE(6, 5)
QT_DEPRECATED_VERSION_X_6_5("Use the typed overload")
- Q_REVISION(2, 7) void mapToGlobal(QQmlV4Function*) const;
+ Q_REVISION(2, 7) void mapToGlobal(QQmlV4FunctionPtr) const;
#endif
Q_REVISION(2, 7) Q_INVOKABLE QPointF mapToGlobal(qreal x, qreal y) const;
// overload only exist for QML
@@ -383,6 +387,7 @@ Q_SIGNALS:
void stateChanged(const QString &);
void focusChanged(bool);
void activeFocusChanged(bool);
+ Q_REVISION(6, 7) void focusPolicyChanged(Qt::FocusPolicy);
Q_REVISION(2, 1) void activeFocusOnTabChanged(bool);
void parentChanged(QQuickItem *);
void transformOriginChanged(TransformOrigin);
@@ -465,7 +470,7 @@ protected:
private:
Q_PRIVATE_SLOT(d_func(), void _q_resourceObjectDeleted(QObject *))
- Q_PRIVATE_SLOT(d_func(), quint64 _q_createJSWrapper(QV4::ExecutionEngine *))
+ Q_PRIVATE_SLOT(d_func(), quint64 _q_createJSWrapper(QQmlV4ExecutionEnginePtr))
friend class QQuickWindowPrivate;
friend class QQuickDeliveryAgentPrivate;
@@ -473,6 +478,10 @@ private:
friend class QAccessibleQuickItem;
friend class QQuickAccessibleAttached;
friend class QQuickAnchorChanges;
+#ifndef QT_NO_DEBUG_STREAM
+ friend Q_QUICK_EXPORT QDebug operator<<(QDebug debug, QQuickItem *item);
+#endif
+
Q_DISABLE_COPY(QQuickItem)
Q_DECLARE_PRIVATE(QQuickItem)
};
@@ -504,7 +513,4 @@ QDebug Q_QUICK_EXPORT operator<<(QDebug debug,
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickItem)
-QML_DECLARE_TYPE(QQuickTransform)
-
#endif // QQUICKITEM_H
diff --git a/src/quick/items/qquickitem_p.h b/src/quick/items/qquickitem_p.h
index 8fe7686a62..8c99d0656c 100644
--- a/src/quick/items/qquickitem_p.h
+++ b/src/quick/items/qquickitem_p.h
@@ -42,9 +42,15 @@
#include <QtCore/qlist.h>
#include <QtCore/qdebug.h>
#include <QtCore/qelapsedtimer.h>
+#include <QtCore/qpointer.h>
+
+#include <QtGui/private/qlayoutpolicy_p.h>
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcHandlerParent)
+Q_DECLARE_LOGGING_CATEGORY(lcVP)
+
class QNetworkReply;
class QQuickItemKeyFilter;
class QQuickLayoutMirroringAttached;
@@ -100,21 +106,21 @@ public:
#if QT_CONFIG(quick_shadereffect)
-class Q_QUICK_PRIVATE_EXPORT QQuickItemLayer : public QObject, public QQuickItemChangeListener
+class Q_QUICK_EXPORT QQuickItemLayer : public QObject, public QQuickItemChangeListener
{
Q_OBJECT
- Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
- Q_PROPERTY(QSize textureSize READ size WRITE setSize NOTIFY sizeChanged)
- Q_PROPERTY(QRectF sourceRect READ sourceRect WRITE setSourceRect NOTIFY sourceRectChanged)
- Q_PROPERTY(bool mipmap READ mipmap WRITE setMipmap NOTIFY mipmapChanged)
- Q_PROPERTY(bool smooth READ smooth WRITE setSmooth NOTIFY smoothChanged)
- Q_PROPERTY(bool live READ live WRITE setLive NOTIFY liveChanged REVISION(6, 5))
- Q_PROPERTY(QQuickShaderEffectSource::WrapMode wrapMode READ wrapMode WRITE setWrapMode NOTIFY wrapModeChanged)
- Q_PROPERTY(QQuickShaderEffectSource::Format format READ format WRITE setFormat NOTIFY formatChanged)
- Q_PROPERTY(QByteArray samplerName READ name WRITE setName NOTIFY nameChanged)
- Q_PROPERTY(QQmlComponent *effect READ effect WRITE setEffect NOTIFY effectChanged)
- Q_PROPERTY(QQuickShaderEffectSource::TextureMirroring textureMirroring READ textureMirroring WRITE setTextureMirroring NOTIFY textureMirroringChanged)
- Q_PROPERTY(int samples READ samples WRITE setSamples NOTIFY samplesChanged)
+ Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged FINAL)
+ Q_PROPERTY(QSize textureSize READ size WRITE setSize NOTIFY sizeChanged FINAL)
+ Q_PROPERTY(QRectF sourceRect READ sourceRect WRITE setSourceRect NOTIFY sourceRectChanged FINAL)
+ Q_PROPERTY(bool mipmap READ mipmap WRITE setMipmap NOTIFY mipmapChanged FINAL)
+ Q_PROPERTY(bool smooth READ smooth WRITE setSmooth NOTIFY smoothChanged FINAL)
+ Q_PROPERTY(bool live READ live WRITE setLive NOTIFY liveChanged REVISION(6, 5) FINAL)
+ Q_PROPERTY(QQuickShaderEffectSource::WrapMode wrapMode READ wrapMode WRITE setWrapMode NOTIFY wrapModeChanged FINAL)
+ Q_PROPERTY(QQuickShaderEffectSource::Format format READ format WRITE setFormat NOTIFY formatChanged FINAL)
+ Q_PROPERTY(QByteArray samplerName READ name WRITE setName NOTIFY nameChanged FINAL)
+ Q_PROPERTY(QQmlComponent *effect READ effect WRITE setEffect NOTIFY effectChanged FINAL)
+ Q_PROPERTY(QQuickShaderEffectSource::TextureMirroring textureMirroring READ textureMirroring WRITE setTextureMirroring NOTIFY textureMirroringChanged FINAL)
+ Q_PROPERTY(int samples READ samples WRITE setSamples NOTIFY samplesChanged FINAL)
QML_ANONYMOUS
QML_ADDED_IN_VERSION(2, 0)
@@ -217,7 +223,7 @@ private:
#endif
-class Q_QUICK_PRIVATE_EXPORT QQuickItemPrivate
+class Q_QUICK_EXPORT QQuickItemPrivate
: public QObjectPrivate
, public QQuickPaletteProviderPrivateBase<QQuickItem, QQuickItemPrivate>
{
@@ -265,21 +271,23 @@ public:
static qsizetype data_count(QQmlListProperty<QObject> *);
static QObject *data_at(QQmlListProperty<QObject> *, qsizetype);
static void data_clear(QQmlListProperty<QObject> *);
+ static void data_removeLast(QQmlListProperty<QObject> *);
// resources property
static QObject *resources_at(QQmlListProperty<QObject> *, qsizetype);
static void resources_append(QQmlListProperty<QObject> *, QObject *);
static qsizetype resources_count(QQmlListProperty<QObject> *);
static void resources_clear(QQmlListProperty<QObject> *);
+ static void resources_removeLast(QQmlListProperty<QObject> *);
// children property
static void children_append(QQmlListProperty<QQuickItem> *, QQuickItem *);
static qsizetype children_count(QQmlListProperty<QQuickItem> *);
static QQuickItem *children_at(QQmlListProperty<QQuickItem> *, qsizetype);
static void children_clear(QQmlListProperty<QQuickItem> *);
+ static void children_removeLast(QQmlListProperty<QQuickItem> *);
// visibleChildren property
- static void visibleChildren_append(QQmlListProperty<QQuickItem> *prop, QQuickItem *o);
static qsizetype visibleChildren_count(QQmlListProperty<QQuickItem> *prop);
static QQuickItem *visibleChildren_at(QQmlListProperty<QQuickItem> *prop, qsizetype index);
@@ -290,7 +298,7 @@ public:
static void transform_clear(QQmlListProperty<QQuickTransform> *list);
void _q_resourceObjectDeleted(QObject *);
- quint64 _q_createJSWrapper(QV4::ExecutionEngine *engine);
+ quint64 _q_createJSWrapper(QQmlV4ExecutionEnginePtr engine);
enum ChangeType {
Geometry = 0x01,
@@ -338,7 +346,7 @@ public:
#endif // QT_NO_DEBUG_STREAM
};
- // call QQuickItemChangeListener PMF
+ // call QQuickItemChangeListener
template <typename Fn, typename ...Args>
void notifyChangeListeners(QQuickItemPrivate::ChangeTypes changeTypes, Fn &&function, Args &&...args)
{
@@ -347,20 +355,12 @@ public:
const auto listeners = changeListeners; // NOTE: intentional copy (QTBUG-54732)
for (const QQuickItemPrivate::ChangeListener &change : listeners) {
- if (change.types & changeTypes)
- (change.listener->*function)(args...);
- }
- }
- // call functor
- template <typename Fn>
- void notifyChangeListeners(QQuickItemPrivate::ChangeTypes changeTypes, Fn &&function) {
- if (changeListeners.isEmpty())
- return;
-
- const auto listeners = changeListeners; // NOTE: intentional copy (QTBUG-54732)
- for (const QQuickItemPrivate::ChangeListener &change : listeners) {
- if (change.types & changeTypes)
- function(change);
+ if (change.types & changeTypes) {
+ if constexpr (std::is_member_function_pointer_v<Fn>)
+ (change.listener->*function)(args...);
+ else
+ function(change, args...);
+ }
}
}
@@ -488,6 +488,7 @@ public:
// focus chain and prevents tabbing outside.
quint32 isTabFence:1;
quint32 replayingPressEvent:1;
+ // Bit 40
quint32 touchEnabled:1;
quint32 hasCursorHandler:1;
// set true when this item does not expect events via a subscene delivery agent; false otherwise
@@ -496,6 +497,9 @@ public:
// (e.g. when parent has ItemIsViewport and child has ItemObservesViewport)
quint32 subtreeTransformChangedEnabled:1;
quint32 inDestructor:1; // has entered ~QQuickItem
+ quint32 focusReason:4;
+ quint32 focusPolicy:4;
+ // Bit 53
enum DirtyType {
TransformOrigin = 0x00000001,
@@ -561,16 +565,20 @@ public:
QPointer<QQuickItem> subFocusItem;
void updateSubFocusItem(QQuickItem *scope, bool focus);
+ bool setFocusIfNeeded(QEvent::Type);
+ Qt::FocusReason lastFocusChangeReason() const;
+ virtual bool setLastFocusChangeReason(Qt::FocusReason reason);
+
QTransform windowToItemTransform() const;
QTransform itemToWindowTransform() const;
- void itemToParentTransform(QTransform &) const;
+ void itemToParentTransform(QTransform *) const;
QTransform globalToWindowTransform() const;
QTransform windowToGlobalTransform() const;
static bool focusNextPrev(QQuickItem *item, bool forward);
static QQuickItem *nextTabChildItem(const QQuickItem *item, int start);
static QQuickItem *prevTabChildItem(const QQuickItem *item, int start);
- static QQuickItem *nextPrevItemInTabFocusChain(QQuickItem *item, bool forward);
+ static QQuickItem *nextPrevItemInTabFocusChain(QQuickItem *item, bool forward, bool wrap = true);
static bool canAcceptTabFocus(QQuickItem *item);
@@ -609,7 +617,10 @@ public:
virtual void implicitHeightChanged();
#if QT_CONFIG(accessibility)
+ QAccessible::Role effectiveAccessibleRole() const;
+private:
virtual QAccessible::Role accessibleRole() const;
+public:
#endif
void setImplicitAntialiasing(bool antialiasing);
@@ -645,6 +656,8 @@ public:
#endif
void deliverShortcutOverrideEvent(QKeyEvent *);
+ void deliverPointerEvent(QEvent *);
+
bool anyPointerHandlerWants(const QPointerEvent *event, const QEventPoint &point) const;
virtual bool handlePointerEvent(QPointerEvent *, bool avoidGrabbers = false);
@@ -687,6 +700,8 @@ public:
void itemChange(QQuickItem::ItemChange, const QQuickItem::ItemChangeData &);
+ void enableSubtreeChangeNotificationsForParentHierachy();
+
virtual void mirrorChange() {}
void setHasCursorInChild(bool hasCursor);
@@ -698,6 +713,10 @@ public:
virtual void updatePolish() { }
virtual void dumpItemTree(int indent) const;
+
+ QLayoutPolicy sizePolicy() const;
+ void setSizePolicy(const QLayoutPolicy::Policy &horizontalPolicy, const QLayoutPolicy::Policy &verticalPolicy);
+ QLayoutPolicy szPolicy;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickItemPrivate::ExtraDataTags)
@@ -720,7 +739,7 @@ public:
virtual void inputMethodEvent(QInputMethodEvent *event, bool post);
virtual QVariant inputMethodQuery(Qt::InputMethodQuery query) const;
#endif
- virtual void shortcutOverride(QKeyEvent *event);
+ virtual void shortcutOverrideEvent(QKeyEvent *event);
virtual void componentComplete();
bool m_processPost;
@@ -750,18 +769,18 @@ public:
bool backtabSet : 1;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickKeyNavigationAttached : public QObject, public QQuickItemKeyFilter
+class Q_QUICK_EXPORT QQuickKeyNavigationAttached : public QObject, public QQuickItemKeyFilter
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickKeyNavigationAttached)
- Q_PROPERTY(QQuickItem *left READ left WRITE setLeft NOTIFY leftChanged)
- Q_PROPERTY(QQuickItem *right READ right WRITE setRight NOTIFY rightChanged)
- Q_PROPERTY(QQuickItem *up READ up WRITE setUp NOTIFY upChanged)
- Q_PROPERTY(QQuickItem *down READ down WRITE setDown NOTIFY downChanged)
- Q_PROPERTY(QQuickItem *tab READ tab WRITE setTab NOTIFY tabChanged)
- Q_PROPERTY(QQuickItem *backtab READ backtab WRITE setBacktab NOTIFY backtabChanged)
- Q_PROPERTY(Priority priority READ priority WRITE setPriority NOTIFY priorityChanged)
+ Q_PROPERTY(QQuickItem *left READ left WRITE setLeft NOTIFY leftChanged FINAL)
+ Q_PROPERTY(QQuickItem *right READ right WRITE setRight NOTIFY rightChanged FINAL)
+ Q_PROPERTY(QQuickItem *up READ up WRITE setUp NOTIFY upChanged FINAL)
+ Q_PROPERTY(QQuickItem *down READ down WRITE setDown NOTIFY downChanged FINAL)
+ Q_PROPERTY(QQuickItem *tab READ tab WRITE setTab NOTIFY tabChanged FINAL)
+ Q_PROPERTY(QQuickItem *backtab READ backtab WRITE setBacktab NOTIFY backtabChanged FINAL)
+ Q_PROPERTY(Priority priority READ priority WRITE setPriority NOTIFY priorityChanged FINAL)
QML_NAMED_ELEMENT(KeyNavigation)
QML_ADDED_IN_VERSION(2, 0)
@@ -810,8 +829,8 @@ private:
class QQuickLayoutMirroringAttached : public QObject
{
Q_OBJECT
- Q_PROPERTY(bool enabled READ enabled WRITE setEnabled RESET resetEnabled NOTIFY enabledChanged)
- Q_PROPERTY(bool childrenInherit READ childrenInherit WRITE setChildrenInherit NOTIFY childrenInheritChanged)
+ Q_PROPERTY(bool enabled READ enabled WRITE setEnabled RESET resetEnabled NOTIFY enabledChanged FINAL)
+ Q_PROPERTY(bool childrenInherit READ childrenInherit WRITE setChildrenInherit NOTIFY childrenInheritChanged FINAL)
QML_NAMED_ELEMENT(LayoutMirroring)
QML_ADDED_IN_VERSION(2, 0)
@@ -840,7 +859,7 @@ private:
class QQuickEnterKeyAttached : public QObject
{
Q_OBJECT
- Q_PROPERTY(Qt::EnterKeyType type READ type WRITE setType NOTIFY typeChanged)
+ Q_PROPERTY(Qt::EnterKeyType type READ type WRITE setType NOTIFY typeChanged FINAL)
QML_NAMED_ELEMENT(EnterKey)
QML_UNCREATABLE("EnterKey is only available via attached properties")
@@ -883,14 +902,14 @@ public:
QQuickKeyEvent theKeyEvent;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickKeysAttached : public QObject, public QQuickItemKeyFilter
+class Q_QUICK_EXPORT QQuickKeysAttached : public QObject, public QQuickItemKeyFilter
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickKeysAttached)
- Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
- Q_PROPERTY(QQmlListProperty<QQuickItem> forwardTo READ forwardTo)
- Q_PROPERTY(Priority priority READ priority WRITE setPriority NOTIFY priorityChanged)
+ Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged FINAL)
+ Q_PROPERTY(QQmlListProperty<QQuickItem> forwardTo READ forwardTo FINAL)
+ Q_PROPERTY(Priority priority READ priority WRITE setPriority NOTIFY priorityChanged FINAL)
QML_NAMED_ELEMENT(Keys)
QML_ADDED_IN_VERSION(2, 0)
@@ -978,7 +997,7 @@ private:
void inputMethodEvent(QInputMethodEvent *, bool post) override;
QVariant inputMethodQuery(Qt::InputMethodQuery query) const override;
#endif
- void shortcutOverride(QKeyEvent *event) override;
+ void shortcutOverrideEvent(QKeyEvent *event) override;
static QByteArray keyToSignal(int key);
bool isConnected(const char *signalName) const;
@@ -1049,12 +1068,4 @@ Q_DECLARE_TYPEINFO(QQuickItemPrivate::ChangeListener, Q_PRIMITIVE_TYPE);
QT_END_NAMESPACE
-#if QT_CONFIG(quick_shadereffect)
-QML_DECLARE_TYPE(QQuickItemLayer)
-#endif
-QML_DECLARE_TYPE(QQuickKeysAttached)
-QML_DECLARE_TYPE(QQuickKeyNavigationAttached)
-QML_DECLARE_TYPE(QQuickLayoutMirroringAttached)
-QML_DECLARE_TYPE(QQuickEnterKeyAttached)
-
#endif // QQUICKITEM_P_H
diff --git a/src/quick/items/qquickitemanimation.cpp b/src/quick/items/qquickitemanimation.cpp
index 51fd2a3588..11b2642748 100644
--- a/src/quick/items/qquickitemanimation.cpp
+++ b/src/quick/items/qquickitemanimation.cpp
@@ -770,13 +770,13 @@ void QQuickPathAnimation::setOrientationExitDuration(int duration)
qreal QQuickPathAnimation::endRotation() const
{
Q_D(const QQuickPathAnimation);
- return d->endRotation.isNull ? qreal(0) : d->endRotation.value;
+ return d->endRotation.isValid() ? d->endRotation.value() : qreal(0);
}
void QQuickPathAnimation::setEndRotation(qreal rotation)
{
Q_D(QQuickPathAnimation);
- if (!d->endRotation.isNull && d->endRotation == rotation)
+ if (d->endRotation.isValid() && d->endRotation == rotation)
return;
d->endRotation = rotation;
@@ -946,11 +946,11 @@ void QQuickPathAnimationUpdater::setValue(qreal v)
//shortest distance to correct orientation
qreal diff = angle - startRotation;
while (diff > 180.0) {
- startRotation.value += 360.0;
+ startRotation = startRotation.value() + 360.0;
diff -= 360.0;
}
while (diff < -180.0) {
- startRotation.value -= 360.0;
+ startRotation = startRotation.value() - 360.0;
diff += 360.0;
}
}
@@ -993,9 +993,8 @@ QQuickPathAnimationAnimator::QQuickPathAnimationAnimator(QQuickPathAnimationPriv
QQuickPathAnimationAnimator::~QQuickPathAnimationAnimator()
{
if (animationTemplate && pathUpdater()) {
- QHash<QQuickItem*, QQuickPathAnimationAnimator* >::iterator it =
- animationTemplate->activeAnimations.find(pathUpdater()->target);
- if (it != animationTemplate->activeAnimations.end() && it.value() == this)
+ auto it = animationTemplate->activeAnimations.constFind(pathUpdater()->target);
+ if (it != animationTemplate->activeAnimations.cend() && it.value() == this)
animationTemplate->activeAnimations.erase(it);
}
}
diff --git a/src/quick/items/qquickitemanimation_p.h b/src/quick/items/qquickitemanimation_p.h
index 25fc83883f..e2ea93dd60 100644
--- a/src/quick/items/qquickitemanimation_p.h
+++ b/src/quick/items/qquickitemanimation_p.h
@@ -22,7 +22,7 @@
QT_BEGIN_NAMESPACE
class QQuickParentAnimationPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickParentAnimation : public QQuickAnimationGroup
+class Q_QUICK_EXPORT QQuickParentAnimation : public QQuickAnimationGroup
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickParentAnimation)
@@ -58,7 +58,7 @@ protected:
};
class QQuickAnchorAnimationPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickAnchorAnimation : public QQuickAbstractAnimation
+class Q_QUICK_EXPORT QQuickAnchorAnimation : public QQuickAbstractAnimation
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickAnchorAnimation)
@@ -95,7 +95,7 @@ protected:
class QQuickItem;
class QQuickPath;
class QQuickPathAnimationPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickPathAnimation : public QQuickAbstractAnimation
+class Q_QUICK_EXPORT QQuickPathAnimation : public QQuickAbstractAnimation
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(QQuickPathAnimation)
@@ -174,10 +174,4 @@ Q_SIGNALS:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickParentAnimation)
-QML_DECLARE_TYPE(QQuickAnchorAnimation)
-#if QT_CONFIG(quick_path)
-QML_DECLARE_TYPE(QQuickPathAnimation)
-#endif
-
#endif // QQUICKITEMANIMATION_H
diff --git a/src/quick/items/qquickitemchangelistener_p.h b/src/quick/items/qquickitemchangelistener_p.h
index 57bbf77f70..aed00218e5 100644
--- a/src/quick/items/qquickitemchangelistener_p.h
+++ b/src/quick/items/qquickitemchangelistener_p.h
@@ -15,7 +15,7 @@
// We mean it.
//
-#include <QtCore/private/qglobal_p.h>
+#include <QtQuick/private/qtquickglobal_p.h>
QT_BEGIN_NAMESPACE
@@ -81,10 +81,10 @@ private:
#define QT_QUICK_NEW_GEOMETRY_CHANGED_HANDLING
-class QQuickItemChangeListener
+class Q_QUICK_EXPORT QQuickItemChangeListener
{
public:
- virtual ~QQuickItemChangeListener() {}
+ virtual ~QQuickItemChangeListener();
virtual void itemGeometryChanged(QQuickItem *, QQuickGeometryChange, const QRectF & /* oldGeometry */) {}
virtual void itemSiblingOrderChanged(QQuickItem *) {}
diff --git a/src/quick/items/qquickitemgrabresult.cpp b/src/quick/items/qquickitemgrabresult.cpp
index bcd1afc19d..e2164129be 100644
--- a/src/quick/items/qquickitemgrabresult.cpp
+++ b/src/quick/items/qquickitemgrabresult.cpp
@@ -14,11 +14,13 @@
#include <QtQml/QQmlEngine>
#include <QtQml/QQmlInfo>
-#include <private/qquickpixmapcache_p.h>
+#include <private/qquickpixmap_p.h>
#include <private/qquickitem_p.h>
#include <private/qsgcontext_p.h>
#include <private/qsgadaptationlayer_p.h>
+#include <QtCore/qpointer.h>
+
QT_BEGIN_NAMESPACE
const QEvent::Type Event_Grab_Completed = static_cast<QEvent::Type>(QEvent::User + 1);
@@ -30,6 +32,7 @@ public:
: cacheEntry(nullptr)
, qmlEngine(nullptr)
, texture(nullptr)
+ , devicePixelRatio(1.0)
{
}
@@ -63,6 +66,7 @@ public:
QSGLayer *texture;
QSizeF itemSize;
QSize textureSize;
+ qreal devicePixelRatio;
};
/*!
@@ -235,7 +239,9 @@ void QQuickItemGrabResult::setup()
}
QSGRenderContext *rc = QQuickWindowPrivate::get(d->window.data())->context;
+ d->devicePixelRatio = d->window->effectiveDevicePixelRatio();
d->texture = rc->sceneGraphContext()->createLayer(rc);
+ d->texture->setDevicePixelRatio(d->devicePixelRatio);
d->texture->setItem(QQuickItemPrivate::get(d->item)->itemNode());
d->itemSize = QSizeF(d->item->width(), d->item->height());
}
@@ -248,11 +254,13 @@ void QQuickItemGrabResult::render()
d->texture->setRect(QRectF(0, d->itemSize.height(), d->itemSize.width(), -d->itemSize.height()));
const QSize minSize = QQuickWindowPrivate::get(d->window.data())->context->sceneGraphContext()->minimumFBOSize();
- d->texture->setSize(QSize(qMax(minSize.width(), d->textureSize.width()),
- qMax(minSize.height(), d->textureSize.height())));
+ const QSize effectiveTextureSize = d->textureSize * d->devicePixelRatio;
+ d->texture->setSize(QSize(qMax(minSize.width(), effectiveTextureSize.width()),
+ qMax(minSize.height(), effectiveTextureSize.height())));
d->texture->scheduleUpdate();
d->texture->updateTexture();
- d->image = d->texture->toImage();
+ d->image = d->texture->toImage();
+ d->image.setDevicePixelRatio(d->devicePixelRatio);
delete d->texture;
d->texture = nullptr;
diff --git a/src/quick/items/qquickitemsmodule.cpp b/src/quick/items/qquickitemsmodule.cpp
index 9cff9c4c4f..d08c43ed22 100644
--- a/src/quick/items/qquickitemsmodule.cpp
+++ b/src/quick/items/qquickitemsmodule.cpp
@@ -85,30 +85,15 @@
#include "handlers/qquicktaphandler_p.h"
#include "handlers/qquickwheelhandler_p.h"
-QT_BEGIN_NAMESPACE
-Q_DECLARE_LOGGING_CATEGORY(lcTransient)
-QT_END_NAMESPACE
-
#include "moc_qquickitemsmodule_p.cpp"
static QQmlPrivate::AutoParentResult qquickitem_autoParent(QObject *obj, QObject *parent)
{
- // When setting a parent (especially during dynamic object creation) in QML,
- // also try to set up the analogous item/window relationship.
if (QQuickItem *parentItem = qmlobject_cast<QQuickItem *>(parent)) {
- QQuickItem *item = qmlobject_cast<QQuickItem *>(obj);
- if (item) {
+ if (QQuickItem *item = qmlobject_cast<QQuickItem *>(obj)) {
// An Item has another Item
item->setParentItem(parentItem);
return QQmlPrivate::Parented;
- } else if (parentItem->window()) {
- QQuickWindow *win = qmlobject_cast<QQuickWindow *>(obj);
- if (win) {
- // A Window inside an Item should be transient for that item's window
- qCDebug(lcTransient) << win << "is transient for" << parentItem->window();
- win->setTransientParent(parentItem->window());
- return QQmlPrivate::Parented;
- }
} else if (QQuickPointerHandler *handler = qmlobject_cast<QQuickPointerHandler *>(obj)) {
QQuickItemPrivate::get(parentItem)->addPointerHandler(handler);
handler->setParent(parent);
@@ -116,13 +101,7 @@ static QQmlPrivate::AutoParentResult qquickitem_autoParent(QObject *obj, QObject
}
return QQmlPrivate::IncompatibleObject;
} else if (QQuickWindow *parentWindow = qmlobject_cast<QQuickWindow *>(parent)) {
- QQuickWindow *win = qmlobject_cast<QQuickWindow *>(obj);
- if (win) {
- // A Window inside a Window should be transient for it
- qCDebug(lcTransient) << win << "is transient for" << parentWindow;
- win->setTransientParent(parentWindow);
- return QQmlPrivate::Parented;
- } else if (QQuickItem *item = qmlobject_cast<QQuickItem *>(obj)) {
+ if (QQuickItem *item = qmlobject_cast<QQuickItem *>(obj)) {
// The parent of an Item inside a Window is actually the implicit content Item
item->setParentItem(parentWindow->contentItem());
return QQmlPrivate::Parented;
@@ -160,4 +139,139 @@ void QQuickItemsModule::defineModule()
qt_quickitems_defineModule();
}
+/*!
+ \qmltype PointerEvent
+ \instantiates QPointerEvent
+ \inqmlmodule QtQuick
+ \brief QML equivalent for \l QPointerEvent.
+
+ PointerEvent is the QML name of the QPointerEvent class.
+*/
+
+/*!
+ \qmltype PointerDevice
+ \instantiates QPointingDevice
+ \inqmlmodule QtQuick
+ \brief QML equivalent for \l QPointingDevice.
+
+ PointerDevice is the QML name of the QPointingDevice class.
+ It has the same properties and enums as \l QPointingDevice.
+*/
+
+/*!
+ \qmlproperty enumeration PointerDevice::deviceType
+
+ This property tells the type of device that generated a PointerEvent.
+
+ Valid values are:
+
+ \value PointerDevice.Unknown The device cannot be identified.
+ \value PointerDevice.Mouse A mouse.
+ \value PointerDevice.TouchScreen A touchscreen.
+ \value PointerDevice.TouchPad A touchpad or trackpad.
+ \value PointerDevice.Stylus A stylus on a graphics tablet.
+ \value PointerDevice.Airbrush An airbrush on a graphics tablet.
+ \value PointerDevice.Puck A digitizer with crosshairs, on a graphics tablet.
+
+ \sa QInputDevice::DeviceType, PointerDeviceHandler::acceptedDevices
+*/
+
+/*!
+ \qmlproperty enumeration PointerDevice::pointerType
+
+ This property tells what is interacting with the PointerDevice.
+
+ There is some redundancy between this property and \l deviceType.
+ For example, if a touchscreen is used, then \c deviceType is
+ \c TouchScreen and \c pointerType is \c Finger. But on a graphics
+ tablet, it's often possible for both ends of the stylus to be used,
+ and programs need to distinguish them.
+ \l PointerDeviceHandler::acceptedDevices and
+ \l PointerDeviceHandler::acceptedPointerTypes can be used in combination
+ to filter the subset of events that a particular handler should react to.
+
+ Valid values are:
+
+ \value PointerDevice.Unknown The device cannot be identified.
+ \value PointerDevice.Generic A mouse or a device that emulates a mouse.
+ \value PointerDevice.Finger A finger on a touchscreen.
+ \value PointerDevice.Pen A stylus on a graphics tablet.
+ \value PointerDevice.Eraser An eraser on a graphics tablet.
+ \value PointerDevice.Cursor A digitizer with crosshairs, on a graphics tablet.
+
+ \sa QPointingDevice::PointerType, PointerDeviceHandler::acceptedPointerTypes
+*/
+
+/*!
+ \qmlproperty int PointerDevice::maximumPoints
+
+ This property tells the maximum number of simultaneous touch points
+ (fingers) that can be detected.
+*/
+
+/*!
+ \qmlproperty int PointerDevice::buttonCount
+
+ This property tells the maximum number of on-device buttons that can be
+ detected.
+*/
+
+/*!
+ \qmltype pointingDeviceUniqueId
+ \instantiates QPointingDeviceUniqueId
+ \inqmlmodule QtQuick
+ \brief QML equivalent for \l QPointingDeviceUniqueId.
+
+ pointingDeviceUniqueId is the QML name of the QPointingDeviceUniqueId class.
+*/
+
+/*!
+ \qmlproperty qint64 pointingDeviceUniqueId::numericId
+
+ This property gives the numeric ID of the \l PointerDevice, if available;
+ otherwise it is \c -1.
+*/
+
+/*!
+ \qmlproperty pointingDeviceUniqueId PointerDevice::uniqueId
+
+ This property may provide a unique ID for the device, if available. For
+ example, a graphics tablet stylus device may have a unique serial number.
+
+ \sa eventPoint, QEventPoint::uniqueId()
+*/
+
+/*!
+ \qmlsignal PointerDevice::grabChanged(QtObject grabber, enumeration transition, PointerEvent event, eventPoint point)
+
+ This signal is emitted when the \a grabber object gains or loses an
+ exclusive or passive grab of \a point during delivery of \a event.
+ The \a transition tells what happened, from the perspective of the
+ \c grabber object, which may be either an \l Item or an
+ \l {Qt Quick Input Handlers}{Input Handler}.
+
+ Valid values for \a transition are:
+
+ \value PointerDevice.GrabExclusive
+ The \a grabber has taken primary responsibility for handling the \a point.
+ \value PointerDevice.UngrabExclusive
+ The \a grabber has given up its previous exclusive grab.
+ \value PointerDevice.CancelGrabExclusive
+ The exclusive grab of \a grabber has been taken over or cancelled.
+ \value PointerDevice.GrabPassive
+ The \a grabber has acquired a passive grab, to monitor the \a point.
+ \value PointerDevice.UngrabPassive
+ The \a grabber has given up its previous passive grab.
+ \value PointerDevice.CancelGrabPassive
+ The previous passive grab has terminated abnormally.
+
+ \note A grab transition from one object to another results in two signals,
+ to notify that one object has lost its grab, and to notify that there is
+ another grabber. In other cases, when transitioning to or from a non-grabbing
+ state, only one signal is emitted.
+
+ \sa QPointerEvent::setExclusiveGrabber(), QPointerEvent::addPassiveGrabber(),
+ QPointerEvent::removePassiveGrabber(), PointerHandler::grabChanged()
+*/
+
QT_END_NAMESPACE
diff --git a/src/quick/items/qquickitemview.cpp b/src/quick/items/qquickitemview.cpp
index a149f8d098..1518a484d3 100644
--- a/src/quick/items/qquickitemview.cpp
+++ b/src/quick/items/qquickitemview.cpp
@@ -160,8 +160,8 @@ void QQuickItemView::setModel(const QVariant &m)
disconnect(d->model, SIGNAL(createdItem(int,QObject*)), this, SLOT(createdItem(int,QObject*)));
disconnect(d->model, SIGNAL(destroyingItem(QObject*)), this, SLOT(destroyingItem(QObject*)));
if (QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel*>(d->model)) {
- disconnect(delegateModel, SIGNAL(itemPooled(int, QObject *)), this, SLOT(onItemPooled(int, QObject *)));
- disconnect(delegateModel, SIGNAL(itemReused(int, QObject *)), this, SLOT(onItemReused(int, QObject *)));
+ disconnect(delegateModel, SIGNAL(itemPooled(int,QObject*)), this, SLOT(onItemPooled(int,QObject*)));
+ disconnect(delegateModel, SIGNAL(itemReused(int,QObject*)), this, SLOT(onItemReused(int,QObject*)));
}
}
@@ -199,8 +199,8 @@ void QQuickItemView::setModel(const QVariant &m)
connect(d->model, SIGNAL(initItem(int,QObject*)), this, SLOT(initItem(int,QObject*)));
connect(d->model, SIGNAL(destroyingItem(QObject*)), this, SLOT(destroyingItem(QObject*)));
if (QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel*>(d->model)) {
- connect(delegateModel, SIGNAL(itemPooled(int, QObject *)), this, SLOT(onItemPooled(int, QObject *)));
- connect(delegateModel, SIGNAL(itemReused(int, QObject *)), this, SLOT(onItemReused(int, QObject *)));
+ connect(delegateModel, SIGNAL(itemPooled(int,QObject*)), this, SLOT(onItemPooled(int,QObject*)));
+ connect(delegateModel, SIGNAL(itemReused(int,QObject*)), this, SLOT(onItemReused(int,QObject*)));
}
if (isComponentComplete()) {
d->updateSectionCriteria();
@@ -211,10 +211,12 @@ void QQuickItemView::setModel(const QVariant &m)
setCurrentIndex(d->model->count() > 0 ? 0 : -1);
d->updateViewport();
+#if QT_CONFIG(quick_viewtransitions)
if (d->transitioner && d->transitioner->populateTransition) {
d->transitioner->setPopulateTransitionEnabled(true);
d->forceLayoutPolish();
}
+#endif
}
connect(d->model, SIGNAL(modelUpdated(QQmlChangeSet,bool)),
@@ -684,6 +686,7 @@ void QQuickItemView::setReuseItems(bool reuse)
emit reuseItemsChanged();
}
+#if QT_CONFIG(quick_viewtransitions)
QQuickTransition *QQuickItemView::populateTransition() const
{
Q_D(const QQuickItemView);
@@ -811,6 +814,7 @@ void QQuickItemView::setDisplacedTransition(QQuickTransition *transition)
emit displacedTransitionChanged();
}
}
+#endif
void QQuickItemViewPrivate::positionViewAtIndex(int index, int mode)
{
@@ -1082,8 +1086,7 @@ qreal QQuickItemViewPrivate::calculatedMaxExtent() const
void QQuickItemViewPrivate::applyDelegateChange()
{
releaseVisibleItems(QQmlDelegateModel::NotReusable);
- releaseItem(currentItem, QQmlDelegateModel::NotReusable);
- currentItem = nullptr;
+ releaseCurrentItem(QQmlDelegateModel::NotReusable);
updateSectionCriteria();
refill();
moveReason = QQuickItemViewPrivate::SetIndex;
@@ -1148,11 +1151,13 @@ void QQuickItemViewPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometry
// don't allow item movement transitions to trigger a re-layout and
// start new transitions
bool prevInLayout = inLayout;
+#if QT_CONFIG(quick_viewtransitions)
if (!inLayout) {
FxViewItem *actualItem = transitioner ? visibleItem(currentIndex) : nullptr;
if (actualItem && actualItem->transitionRunning())
inLayout = true;
}
+#endif
updateHighlight();
inLayout = prevInLayout;
}
@@ -1165,17 +1170,20 @@ void QQuickItemView::destroyRemoved()
{
Q_D(QQuickItemView);
+#if QT_CONFIG(quick_viewtransitions)
bool hasRemoveTransition = false;
bool hasRemoveTransitionAsTarget = false;
if (d->transitioner) {
hasRemoveTransition = d->transitioner->canTransition(QQuickItemViewTransitioner::RemoveTransition, false);
hasRemoveTransitionAsTarget = d->transitioner->canTransition(QQuickItemViewTransitioner::RemoveTransition, true);
}
+#endif
for (QList<FxViewItem*>::Iterator it = d->visibleItems.begin();
it != d->visibleItems.end();) {
FxViewItem *item = *it;
if (item->index == -1 && (!item->attached || item->attached->delayRemove() == false)) {
+#if QT_CONFIG(quick_viewtransitions)
if (hasRemoveTransitionAsTarget) {
// don't remove from visibleItems until next layout()
d->runDelayedRemoveTransition = true;
@@ -1184,9 +1192,12 @@ void QQuickItemView::destroyRemoved()
} else {
if (hasRemoveTransition)
d->runDelayedRemoveTransition = true;
+#endif
d->releaseItem(item, d->reusableFlag);
it = d->visibleItems.erase(it);
+#if QT_CONFIG(quick_viewtransitions)
}
+#endif
} else {
++it;
}
@@ -1201,8 +1212,10 @@ void QQuickItemView::modelUpdated(const QQmlChangeSet &changeSet, bool reset)
Q_D(QQuickItemView);
if (reset) {
cancelFlick();
+#if QT_CONFIG(quick_viewtransitions)
if (d->transitioner)
d->transitioner->setPopulateTransitionEnabled(true);
+#endif
d->moveReason = QQuickItemViewPrivate::SetIndex;
d->regenerate();
if (d->highlight && d->currentItem) {
@@ -1212,8 +1225,10 @@ void QQuickItemView::modelUpdated(const QQmlChangeSet &changeSet, bool reset)
}
d->moveReason = QQuickItemViewPrivate::Other;
emit countChanged();
+#if QT_CONFIG(quick_viewtransitions)
if (d->transitioner && d->transitioner->populateTransition)
d->forceLayoutPolish();
+#endif
} else {
if (d->inLayout) {
d->bufferedChanges.prepare(d->currentIndex, d->itemCount);
@@ -1251,7 +1266,9 @@ void QQuickItemView::trackedPositionChanged()
return;
}
- if (d->moveReason == QQuickItemViewPrivate::SetIndex) {
+ const bool needMoveToTrackHighlight = d->autoHighlight || d->highlightRange != NoHighlightRange;
+
+ if (d->moveReason == QQuickItemViewPrivate::SetIndex && needMoveToTrackHighlight) {
qreal trackedPos = d->trackedItem->position();
qreal trackedSize = d->trackedItem->size();
qreal viewPos = d->isContentFlowReversed() ? -d->position()-d->size() : d->position();
@@ -1449,8 +1466,10 @@ void QQuickItemView::componentComplete()
d->updateFooter();
d->updateViewport();
d->setPosition(d->contentStartOffset());
+#if QT_CONFIG(quick_viewtransitions)
if (d->transitioner)
d->transitioner->setPopulateTransitionEnabled(true);
+#endif
if (d->isValid()) {
d->refill();
@@ -1478,7 +1497,6 @@ QQuickItemViewPrivate::QQuickItemViewPrivate()
, buffer(QML_VIEW_DEFAULTCACHEBUFFER), bufferMode(BufferBefore | BufferAfter)
, displayMarginBeginning(0), displayMarginEnd(0)
, layoutDirection(Qt::LeftToRight), verticalLayoutDirection(QQuickItemView::TopToBottom)
- , moveReason(Other)
, visibleIndex(0)
, currentIndex(-1), currentItem(nullptr)
, trackedItem(nullptr), requestedIndex(-1)
@@ -1487,7 +1505,9 @@ QQuickItemViewPrivate::QQuickItemViewPrivate()
, highlightRangeStart(0), highlightRangeEnd(0)
, highlightMoveDuration(150)
, headerComponent(nullptr), header(nullptr), footerComponent(nullptr), footer(nullptr)
+#if QT_CONFIG(quick_viewtransitions)
, transitioner(nullptr)
+#endif
, minExtent(0), maxExtent(0)
, ownModel(false), wrap(false)
, keyNavigationEnabled(true)
@@ -1495,7 +1515,10 @@ QQuickItemViewPrivate::QQuickItemViewPrivate()
, inLayout(false), inViewportMoved(false), forceLayout(false), currentIndexCleared(false)
, haveHighlightRange(false), autoHighlight(true), highlightRangeStartValid(false), highlightRangeEndValid(false)
, fillCacheBuffer(false), inRequest(false)
- , runDelayedRemoveTransition(false), delegateValidated(false), isClearing(false)
+#if QT_CONFIG(quick_viewtransitions)
+ , runDelayedRemoveTransition(false)
+#endif
+ , delegateValidated(false), isClearing(false)
{
bufferPause.addAnimationChangeListener(this, QAbstractAnimationJob::Completion);
bufferPause.setLoopCount(1);
@@ -1504,9 +1527,11 @@ QQuickItemViewPrivate::QQuickItemViewPrivate()
QQuickItemViewPrivate::~QQuickItemViewPrivate()
{
+#if QT_CONFIG(quick_viewtransitions)
if (transitioner)
transitioner->setChangeListener(nullptr);
delete transitioner;
+#endif
}
bool QQuickItemViewPrivate::isValid() const
@@ -1628,8 +1653,7 @@ void QQuickItemViewPrivate::updateCurrent(int modelIndex)
if (currentItem) {
if (currentItem->attached)
currentItem->attached->setIsCurrentItem(false);
- releaseItem(currentItem, reusableFlag);
- currentItem = nullptr;
+ releaseCurrentItem(reusableFlag);
currentIndex = modelIndex;
emit q->currentIndexChanged();
emit q->currentItemChanged();
@@ -1682,16 +1706,17 @@ void QQuickItemViewPrivate::clear(bool onDestruction)
releaseVisibleItems(QQmlInstanceModel::NotReusable);
visibleIndex = 0;
+#if QT_CONFIG(quick_viewtransitions)
for (FxViewItem *item : std::as_const(releasePendingTransition)) {
item->releaseAfterTransition = false;
releaseItem(item, QQmlInstanceModel::NotReusable);
}
releasePendingTransition.clear();
+#endif
- auto oldCurrentItem = currentItem;
- releaseItem(currentItem, QQmlDelegateModel::NotReusable);
- currentItem = nullptr;
- if (oldCurrentItem)
+ const bool hadCurrentItem = currentItem != nullptr;
+ releaseCurrentItem(QQmlDelegateModel::NotReusable);
+ if (hadCurrentItem)
emit q->currentItemChanged();
createHighlight(onDestruction);
trackedItem = nullptr;
@@ -1736,6 +1761,8 @@ void QQuickItemViewPrivate::refill(qreal from, qreal to)
Q_Q(QQuickItemView);
if (!model || !model->isValid() || !q->isComponentComplete())
return;
+ if (q->size().isEmpty() && visibleItems.isEmpty())
+ return;
if (!model->count()) {
updateHeader();
updateFooter();
@@ -1759,7 +1786,6 @@ void QQuickItemViewPrivate::refill(qreal from, qreal to)
qreal fillTo = to;
bool added = addVisibleItems(fillFrom, fillTo, bufferFrom, bufferTo, false);
- bool removed = removeNonVisibleItems(bufferFrom, bufferTo);
if (requestedIndex == -1 && buffer && bufferMode != NoBuffer) {
if (added) {
@@ -1775,6 +1801,8 @@ void QQuickItemViewPrivate::refill(qreal from, qreal to)
}
}
+ bool removed = removeNonVisibleItems(bufferFrom, bufferTo);
+
if (added || removed) {
markExtentsDirty();
updateBeginningEnd();
@@ -1836,12 +1864,15 @@ void QQuickItemViewPrivate::layout()
clear();
setPosition(contentStartOffset());
updateViewport();
+#if QT_CONFIG(quick_viewtransitions)
if (transitioner)
transitioner->setPopulateTransitionEnabled(false);
+#endif
inLayout = false;
return;
}
+#if QT_CONFIG(quick_viewtransitions)
if (runDelayedRemoveTransition && transitioner
&& transitioner->canTransition(QQuickItemViewTransitioner::RemoveTransition, false)) {
// assume that any items moving now are moving due to the remove - if they schedule
@@ -1849,6 +1880,7 @@ void QQuickItemViewPrivate::layout()
for (int i=0; i<visibleItems.size(); i++)
visibleItems[i]->transitionNextReposition(transitioner, QQuickItemViewTransitioner::RemoveTransition, false);
}
+#endif
ChangeResult insertionPosChanges;
ChangeResult removalPosChanges;
@@ -1862,6 +1894,7 @@ void QQuickItemViewPrivate::layout()
}
forceLayout = false;
+#if QT_CONFIG(quick_viewtransitions)
if (transitioner && transitioner->canTransition(QQuickItemViewTransitioner::PopulateTransition, true)) {
// Give the view one more chance to refill itself,
// in case its size is changed such that more delegates become visible after component completed
@@ -1871,12 +1904,15 @@ void QQuickItemViewPrivate::layout()
item->transitionNextReposition(transitioner, QQuickItemViewTransitioner::PopulateTransition, true);
}
}
+#endif
updateSections();
layoutVisibleItems();
storeFirstVisibleItemPosition();
+#if QT_CONFIG(quick_viewtransitions)
int lastIndexInView = findLastIndexInView();
+#endif
refill();
markExtentsDirty();
updateHighlight();
@@ -1891,6 +1927,7 @@ void QQuickItemViewPrivate::layout()
updateViewport();
updateUnrequestedPositions();
+#if QT_CONFIG(quick_viewtransitions)
if (transitioner) {
// items added in the last refill() may need to be transitioned in - e.g. a remove
// causes items to slide up into view
@@ -1931,11 +1968,14 @@ void QQuickItemViewPrivate::layout()
transitioner->setPopulateTransitionEnabled(false);
transitioner->resetTargetLists();
}
+#endif
if (!currentItem)
updateCurrent(currentIndex);
+#if QT_CONFIG(quick_viewtransitions)
runDelayedRemoveTransition = false;
+#endif
inLayout = false;
}
@@ -1991,6 +2031,7 @@ bool QQuickItemViewPrivate::applyModelChanges(ChangeResult *totalInsertionResult
removalResult.countChangeBeforeVisible += (correctedFirstVisibleIndex - r.index);
}
}
+#if QT_CONFIG(quick_viewtransitions)
if (runDelayedRemoveTransition) {
QQmlChangeSet::Change removal;
for (QList<FxViewItem*>::Iterator it = visibleItems.begin(); it != visibleItems.end();) {
@@ -2004,6 +2045,7 @@ bool QQuickItemViewPrivate::applyModelChanges(ChangeResult *totalInsertionResult
}
}
}
+#endif
*totalRemovalResult += removalResult;
if (!removals.isEmpty()) {
updateVisibleIndex();
@@ -2042,6 +2084,7 @@ bool QQuickItemViewPrivate::applyModelChanges(ChangeResult *totalInsertionResult
item->attached->emitAdd();
}
+#if QT_CONFIG(quick_viewtransitions)
// for each item that was moved directly into the view as a result of a move(),
// find the index it was moved from in order to set its initial position, so that we
// can transition it from this "original" position to its new position in the view
@@ -2057,13 +2100,16 @@ bool QQuickItemViewPrivate::applyModelChanges(ChangeResult *totalInsertionResult
}
}
}
+#endif
// reposition visibleItems.first() correctly so that the content y doesn't jump
if (removedCount != prevVisibleItemsCount)
repositionFirstItem(prevVisibleItemsFirst, prevVisibleItemsFirstPos, prevFirstItemInView, &insertionResult, &removalResult);
// Whatever removed/moved items remain are no longer visible items.
+#if QT_CONFIG(quick_viewtransitions)
prepareRemoveTransitions(&currentChanges.removedItems);
+#endif
for (auto it = currentChanges.removedItems.begin();
it != currentChanges.removedItems.end(); ++it) {
releaseItem(it.value(), reusableFlag);
@@ -2074,10 +2120,9 @@ bool QQuickItemViewPrivate::applyModelChanges(ChangeResult *totalInsertionResult
if (currentChanges.currentRemoved && currentItem) {
if (currentItem->item && currentItem->attached)
currentItem->attached->setIsCurrentItem(false);
- auto oldCurrentItem = currentItem;
- releaseItem(currentItem, reusableFlag);
- currentItem = nullptr;
- if (oldCurrentItem)
+ const bool hadCurrentItem = currentItem != nullptr;
+ releaseCurrentItem(reusableFlag);
+ if (hadCurrentItem)
emit q->currentItemChanged();
}
if (!currentIndexCleared)
@@ -2120,10 +2165,12 @@ bool QQuickItemViewPrivate::applyRemovalChange(const QQmlChangeSet::Change &remo
} else if (item->index >= removal.index + removal.count) {
// after removed items
item->index -= removal.count;
+#if QT_CONFIG(quick_viewtransitions)
if (removal.isMove())
item->transitionNextReposition(transitioner, QQuickItemViewTransitioner::MoveTransition, false);
else
item->transitionNextReposition(transitioner, QQuickItemViewTransitioner::RemoveTransition, false);
+#endif
++it;
} else {
// removed item
@@ -2157,7 +2204,9 @@ void QQuickItemViewPrivate::removeItem(FxViewItem *item, const QQmlChangeSet::Ch
}
if (removal.isMove()) {
currentChanges.removedItems.replace(removal.moveKey(item->index), item);
+#if QT_CONFIG(quick_viewtransitions)
item->transitionNextReposition(transitioner, QQuickItemViewTransitioner::MoveTransition, true);
+#endif
} else {
// track item so it is released later
currentChanges.removedItems.insert(QQmlChangeSet::MoveKey(), item);
@@ -2211,6 +2260,7 @@ void QQuickItemViewPrivate::repositionFirstItem(FxViewItem *prevVisibleItemsFirs
}
}
+#if QT_CONFIG(quick_viewtransitions)
void QQuickItemViewPrivate::createTransitioner()
{
if (!transitioner) {
@@ -2286,6 +2336,7 @@ void QQuickItemViewPrivate::viewItemTransitionFinished(QQuickItemViewTransitiona
}
}
}
+#endif
/*
This may return 0 if the item is being created asynchronously.
@@ -2299,6 +2350,7 @@ FxViewItem *QQuickItemViewPrivate::createItem(int modelIndex, QQmlIncubator::Inc
if (requestedIndex == modelIndex && incubationMode == QQmlIncubator::Asynchronous)
return nullptr;
+#if QT_CONFIG(quick_viewtransitions)
for (int i=0; i<releasePendingTransition.size(); i++) {
if (releasePendingTransition.at(i)->index == modelIndex
&& !releasePendingTransition.at(i)->isPendingRemoval()) {
@@ -2306,6 +2358,7 @@ FxViewItem *QQuickItemViewPrivate::createItem(int modelIndex, QQmlIncubator::Inc
return releasePendingTransition.takeAt(i);
}
}
+#endif
inRequest = true;
diff --git a/src/quick/items/qquickitemview_p.h b/src/quick/items/qquickitemview_p.h
index f74ca5177b..cecbf094cb 100644
--- a/src/quick/items/qquickitemview_p.h
+++ b/src/quick/items/qquickitemview_p.h
@@ -31,7 +31,7 @@ class QQmlChangeSet;
class QQuickItemViewPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickItemView : public QQuickFlickable
+class Q_QUICK_EXPORT QQuickItemView : public QQuickFlickable
{
Q_OBJECT
@@ -57,6 +57,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickItemView : public QQuickFlickable
Q_PROPERTY(QQmlComponent *footer READ footer WRITE setFooter NOTIFY footerChanged)
Q_PROPERTY(QQuickItem *footerItem READ footerItem NOTIFY footerItemChanged)
+#if QT_CONFIG(quick_viewtransitions)
Q_PROPERTY(QQuickTransition *populate READ populateTransition WRITE setPopulateTransition NOTIFY populateTransitionChanged)
Q_PROPERTY(QQuickTransition *add READ addTransition WRITE setAddTransition NOTIFY addTransitionChanged)
Q_PROPERTY(QQuickTransition *addDisplaced READ addDisplacedTransition WRITE setAddDisplacedTransition NOTIFY addDisplacedTransitionChanged)
@@ -65,6 +66,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickItemView : public QQuickFlickable
Q_PROPERTY(QQuickTransition *remove READ removeTransition WRITE setRemoveTransition NOTIFY removeTransitionChanged)
Q_PROPERTY(QQuickTransition *removeDisplaced READ removeDisplacedTransition WRITE setRemoveDisplacedTransition NOTIFY removeDisplacedTransitionChanged)
Q_PROPERTY(QQuickTransition *displaced READ displacedTransition WRITE setDisplacedTransition NOTIFY displacedTransitionChanged)
+#endif
Q_PROPERTY(QQmlComponent *highlight READ highlight WRITE setHighlight NOTIFY highlightChanged)
Q_PROPERTY(QQuickItem *highlightItem READ highlightItem NOTIFY highlightItemChanged)
@@ -144,6 +146,7 @@ public:
void setHeader(QQmlComponent *);
QQuickItem *headerItem() const;
+#if QT_CONFIG(quick_viewtransitions)
QQuickTransition *populateTransition() const;
void setPopulateTransition(QQuickTransition *transition);
@@ -167,6 +170,7 @@ public:
QQuickTransition *displacedTransition() const;
void setDisplacedTransition(QQuickTransition *transition);
+#endif
QQmlComponent *highlight() const;
void setHighlight(QQmlComponent *);
@@ -233,6 +237,7 @@ Q_SIGNALS:
void headerItemChanged();
void footerItemChanged();
+#if QT_CONFIG(quick_viewtransitions)
void populateTransitionChanged();
void addTransitionChanged();
void addDisplacedTransitionChanged();
@@ -241,6 +246,7 @@ Q_SIGNALS:
void removeTransitionChanged();
void removeDisplacedTransitionChanged();
void displacedTransitionChanged();
+#endif
void highlightChanged();
void highlightItemChanged();
@@ -277,17 +283,17 @@ private:
};
-class Q_QUICK_PRIVATE_EXPORT QQuickItemViewAttached : public QObject
+class Q_QUICK_EXPORT QQuickItemViewAttached : public QObject
{
Q_OBJECT
- Q_PROPERTY(QQuickItemView *view READ view NOTIFY viewChanged)
- Q_PROPERTY(bool isCurrentItem READ isCurrentItem NOTIFY currentItemChanged)
- Q_PROPERTY(bool delayRemove READ delayRemove WRITE setDelayRemove NOTIFY delayRemoveChanged)
+ Q_PROPERTY(QQuickItemView *view READ view NOTIFY viewChanged FINAL)
+ Q_PROPERTY(bool isCurrentItem READ isCurrentItem NOTIFY currentItemChanged FINAL)
+ Q_PROPERTY(bool delayRemove READ delayRemove WRITE setDelayRemove NOTIFY delayRemoveChanged FINAL)
- Q_PROPERTY(QString section READ section NOTIFY sectionChanged)
- Q_PROPERTY(QString previousSection READ prevSection NOTIFY prevSectionChanged)
- Q_PROPERTY(QString nextSection READ nextSection NOTIFY nextSectionChanged)
+ Q_PROPERTY(QString section READ section NOTIFY sectionChanged FINAL)
+ Q_PROPERTY(QString previousSection READ prevSection NOTIFY prevSectionChanged FINAL)
+ Q_PROPERTY(QString nextSection READ nextSection NOTIFY nextSectionChanged FINAL)
public:
QQuickItemViewAttached(QObject *parent)
diff --git a/src/quick/items/qquickitemview_p_p.h b/src/quick/items/qquickitemview_p_p.h
index b978410bce..0179dc1fdd 100644
--- a/src/quick/items/qquickitemview_p_p.h
+++ b/src/quick/items/qquickitemview_p_p.h
@@ -21,12 +21,15 @@ QT_REQUIRE_CONFIG(quick_itemview);
#include "qquickitemview_p.h"
#include "qquickitemviewfxitem_p_p.h"
+#if QT_CONFIG(quick_viewtransitions)
#include "qquickitemviewtransition_p.h"
+#endif
#include "qquickflickable_p_p.h"
#include <QtQmlModels/private/qqmlobjectmodel_p.h>
#include <QtQmlModels/private/qqmldelegatemodel_p.h>
#include <QtQmlModels/private/qqmlchangeset_p.h>
+#include <QtCore/qpointer.h>
QT_BEGIN_NAMESPACE
@@ -64,7 +67,12 @@ public:
};
-class Q_QUICK_AUTOTEST_EXPORT QQuickItemViewPrivate : public QQuickFlickablePrivate, public QQuickItemViewTransitionChangeListener, public QAnimationJobChangeListener
+class Q_QUICK_AUTOTEST_EXPORT QQuickItemViewPrivate
+ : public QQuickFlickablePrivate
+#if QT_CONFIG(quick_viewtransitions)
+ , public QQuickItemViewTransitionChangeListener
+#endif
+ , public QAnimationJobChangeListener
{
public:
Q_DECLARE_PUBLIC(QQuickItemView)
@@ -112,7 +120,6 @@ public:
};
enum BufferMode { NoBuffer = 0x00, BufferBefore = 0x01, BufferAfter = 0x02 };
- enum MovementReason { Other, SetIndex, Mouse };
bool isValid() const;
qreal position() const;
@@ -138,6 +145,11 @@ public:
void mirrorChange() override;
FxViewItem *createItem(int modelIndex,QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested);
+ bool releaseCurrentItem(QQmlInstanceModel::ReusableFlag reusableFlag)
+ {
+ auto oldCurrentItem = std::exchange(currentItem, nullptr);
+ return releaseItem(oldCurrentItem, reusableFlag);
+ }
virtual bool releaseItem(FxViewItem *item, QQmlInstanceModel::ReusableFlag reusableFlag);
QQuickItem *createHighlightItem() const;
@@ -166,11 +178,13 @@ public:
void repositionFirstItem(FxViewItem *prevVisibleItemsFirst, qreal prevVisibleItemsFirstPos,
FxViewItem *prevFirstVisible, ChangeResult *insertionResult, ChangeResult *removalResult);
+#if QT_CONFIG(quick_viewtransitions)
void createTransitioner();
void prepareVisibleItemTransitions();
void prepareRemoveTransitions(QMultiHash<QQmlChangeSet::MoveKey, FxViewItem *> *removedItems);
bool prepareNonVisibleItemTransition(FxViewItem *item, const QRectF &viewBounds);
void viewItemTransitionFinished(QQuickItemViewTransitionableItem *item) override;
+#endif
int findMoveKeyIndex(QQmlChangeSet::MoveKey key, const QVector<QQmlChangeSet::Change> &changes) const;
@@ -187,7 +201,10 @@ public:
bool hasPendingChanges() const {
return currentChanges.hasPendingChanges()
|| bufferedChanges.hasPendingChanges()
- ||runDelayedRemoveTransition;
+#if QT_CONFIG(quick_viewtransitions)
+ ||runDelayedRemoveTransition
+#endif
+ ;
}
void refillOrLayout() {
@@ -224,8 +241,6 @@ public:
Qt::LayoutDirection layoutDirection;
QQuickItemView::VerticalLayoutDirection verticalLayoutDirection;
- MovementReason moveReason;
-
QList<FxViewItem *> visibleItems;
qreal firstVisibleItemPosition = 0;
void storeFirstVisibleItemPosition() {
@@ -266,8 +281,10 @@ public:
MovedItem(FxViewItem *i, QQmlChangeSet::MoveKey k)
: item(i), moveKey(k) {}
};
+#if QT_CONFIG(quick_viewtransitions)
QQuickItemViewTransitioner *transitioner;
QVector<FxViewItem *> releasePendingTransition;
+#endif
mutable qreal minExtent;
mutable qreal maxExtent;
@@ -286,7 +303,9 @@ public:
bool highlightRangeEndValid : 1;
bool fillCacheBuffer : 1;
bool inRequest : 1;
+#if QT_CONFIG(quick_viewtransitions)
bool runDelayedRemoveTransition : 1;
+#endif
bool delegateValidated : 1;
bool isClearing : 1;
@@ -333,7 +352,9 @@ protected:
QList<FxViewItem *> *newItems, QList<MovedItem> *movingIntoView) = 0;
virtual bool needsRefillForAddedOrRemovedIndex(int) const { return false; }
+#if QT_CONFIG(quick_viewtransitions)
virtual void translateAndTransitionItemsAfter(int afterIndex, const ChangeResult &insertionResult, const ChangeResult &removalResult) = 0;
+#endif
virtual void initializeViewItem(FxViewItem *) {}
virtual void initializeCurrentItem() {}
diff --git a/src/quick/items/qquickitemviewfxitem.cpp b/src/quick/items/qquickitemviewfxitem.cpp
index 70e122ca60..d372fde0c8 100644
--- a/src/quick/items/qquickitemviewfxitem.cpp
+++ b/src/quick/items/qquickitemviewfxitem.cpp
@@ -10,7 +10,9 @@ QT_BEGIN_NAMESPACE
QQuickItemViewFxItem::QQuickItemViewFxItem(QQuickItem *item, bool ownItem, QQuickItemChangeListener* changeListener)
: item(item)
, changeListener(changeListener)
+#if QT_CONFIG(quick_viewtransitions)
, transitionableItem(nullptr)
+#endif
, ownItem(ownItem)
, releaseAfterTransition(false)
, trackGeom(false)
@@ -19,8 +21,10 @@ QQuickItemViewFxItem::QQuickItemViewFxItem(QQuickItem *item, bool ownItem, QQuic
QQuickItemViewFxItem::~QQuickItemViewFxItem()
{
+#if QT_CONFIG(quick_viewtransitions)
delete transitionableItem;
transitionableItem = nullptr;
+#endif
if (ownItem && item) {
trackGeometry(false);
@@ -31,28 +35,47 @@ QQuickItemViewFxItem::~QQuickItemViewFxItem()
qreal QQuickItemViewFxItem::itemX() const
{
- return transitionableItem ? transitionableItem->itemX() : (item ? item->x() : 0);
+ return
+#if QT_CONFIG(quick_viewtransitions)
+ transitionableItem ? transitionableItem->itemX() :
+#endif
+ (item ? item->x() : 0);
}
qreal QQuickItemViewFxItem::itemY() const
{
- return transitionableItem ? transitionableItem->itemY() : (item ? item->y() : 0);
+ return
+#if QT_CONFIG(quick_viewtransitions)
+ transitionableItem ? transitionableItem->itemY() :
+#endif
+ (item ? item->y() : 0);
}
void QQuickItemViewFxItem::moveTo(const QPointF &pos, bool immediate)
{
+#if QT_CONFIG(quick_viewtransitions)
if (transitionableItem)
transitionableItem->moveTo(pos, immediate);
- else if (item)
+ else
+#else
+ Q_UNUSED(immediate)
+#endif
+ if (item)
item->setPosition(pos);
}
void QQuickItemViewFxItem::setVisible(bool visible)
{
- if (!visible && transitionableItem && transitionableItem->transitionScheduledOrRunning())
+ if (!visible
+#if QT_CONFIG(quick_viewtransitions)
+ && transitionableItem && transitionableItem->transitionScheduledOrRunning()
+#endif
+ )
return;
- if (item)
+ if (item) {
QQuickItemPrivate::get(item)->setCulled(!visible);
+ QQuickItemPrivate::get(item)->isAccessible = visible;
+ }
}
void QQuickItemViewFxItem::trackGeometry(bool track)
@@ -87,6 +110,7 @@ void QQuickItemViewFxItem::setGeometry(const QRectF &geometry)
item->setSize(geometry.size());
}
+#if QT_CONFIG(quick_viewtransitions)
QQuickItemViewTransitioner::TransitionType QQuickItemViewFxItem::scheduledTransitionType() const
{
return transitionableItem ? transitionableItem->nextTransitionType : QQuickItemViewTransitioner::NoTransition;
@@ -126,6 +150,7 @@ void QQuickItemViewFxItem::startTransition(QQuickItemViewTransitioner *transitio
if (transitionableItem)
transitionableItem->startTransition(transitioner, index);
}
+#endif
QT_END_NAMESPACE
diff --git a/src/quick/items/qquickitemviewfxitem_p_p.h b/src/quick/items/qquickitemviewfxitem_p_p.h
index b453054a89..e6e10c4233 100644
--- a/src/quick/items/qquickitemviewfxitem_p_p.h
+++ b/src/quick/items/qquickitemviewfxitem_p_p.h
@@ -17,14 +17,18 @@
#include <QtQuick/private/qtquickglobal_p.h>
#include <QtQuick/private/qquickitem_p.h>
+#if QT_CONFIG(quick_viewtransitions)
#include <QtQuick/private/qquickitemviewtransition_p.h>
+#endif
#include <private/qanimationjobutil_p.h>
+#include <QtCore/qpointer.h>
+
QT_REQUIRE_CONFIG(quick_itemview);
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QQuickItemViewFxItem
+class Q_QUICK_EXPORT QQuickItemViewFxItem
{
public:
QQuickItemViewFxItem(QQuickItem *item, bool ownItem, QQuickItemChangeListener *changeListener);
@@ -42,6 +46,7 @@ public:
QRectF geometry() const;
void setGeometry(const QRectF &geometry);
+#if QT_CONFIG(quick_viewtransitions)
QQuickItemViewTransitioner::TransitionType scheduledTransitionType() const;
bool transitionScheduledOrRunning() const;
bool transitionRunning() const;
@@ -50,6 +55,7 @@ public:
void transitionNextReposition(QQuickItemViewTransitioner *transitioner, QQuickItemViewTransitioner::TransitionType type, bool asTarget);
bool prepareTransition(QQuickItemViewTransitioner *transitioner, const QRectF &viewBounds);
void startTransition(QQuickItemViewTransitioner *transitioner);
+#endif
// these are positions and sizes along the current direction of scrolling/flicking
virtual qreal position() const = 0;
@@ -62,7 +68,9 @@ public:
SelfDeletable m_selfDeletable;
QPointer<QQuickItem> item;
QQuickItemChangeListener *changeListener;
+#if QT_CONFIG(quick_viewtransitions)
QQuickItemViewTransitionableItem *transitionableItem;
+#endif
int index = -1;
bool ownItem : 1;
bool releaseAfterTransition : 1;
diff --git a/src/quick/items/qquickitemviewtransition_p.h b/src/quick/items/qquickitemviewtransition_p.h
index 01e504a440..7a2d23c3cd 100644
--- a/src/quick/items/qquickitemviewtransition_p.h
+++ b/src/quick/items/qquickitemviewtransition_p.h
@@ -26,6 +26,8 @@ QT_REQUIRE_CONFIG(quick_viewtransitions);
#include <private/qquicktransition_p.h>
#include <private/qanimationjobutil_p.h>
+#include <QtCore/qpointer.h>
+
QT_BEGIN_NAMESPACE
class QQuickItem;
@@ -34,7 +36,7 @@ class QQuickItemViewTransitionableItem;
class QQuickItemViewTransitionJob;
-class Q_QUICK_PRIVATE_EXPORT QQuickItemViewTransitionChangeListener
+class Q_QUICK_EXPORT QQuickItemViewTransitionChangeListener
{
public:
QQuickItemViewTransitionChangeListener() {}
@@ -44,7 +46,7 @@ public:
};
-class Q_QUICK_PRIVATE_EXPORT QQuickItemViewTransitioner
+class Q_QUICK_EXPORT QQuickItemViewTransitioner
{
public:
enum TransitionType {
@@ -104,7 +106,7 @@ private:
/*
An item that can be transitioned using QQuickViewTransitionJob.
*/
-class Q_QUICK_PRIVATE_EXPORT QQuickItemViewTransitionableItem
+class Q_QUICK_EXPORT QQuickItemViewTransitionableItem
{
public:
QQuickItemViewTransitionableItem(QQuickItem *i);
@@ -152,12 +154,12 @@ class QQuickViewTransitionAttached : public QObject
{
Q_OBJECT
- Q_PROPERTY(int index READ index NOTIFY indexChanged)
- Q_PROPERTY(QQuickItem* item READ item NOTIFY itemChanged)
- Q_PROPERTY(QPointF destination READ destination NOTIFY destinationChanged)
+ Q_PROPERTY(int index READ index NOTIFY indexChanged FINAL)
+ Q_PROPERTY(QQuickItem* item READ item NOTIFY itemChanged FINAL)
+ Q_PROPERTY(QPointF destination READ destination NOTIFY destinationChanged FINAL)
- Q_PROPERTY(QList<int> targetIndexes READ targetIndexes NOTIFY targetIndexesChanged)
- Q_PROPERTY(QQmlListProperty<QObject> targetItems READ targetItems NOTIFY targetItemsChanged)
+ Q_PROPERTY(QList<int> targetIndexes READ targetIndexes NOTIFY targetIndexesChanged FINAL)
+ Q_PROPERTY(QQmlListProperty<QObject> targetItems READ targetItems NOTIFY targetItemsChanged FINAL)
QML_NAMED_ELEMENT(ViewTransition)
QML_ADDED_IN_VERSION(2, 0)
@@ -196,6 +198,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickViewTransitionAttached)
-
#endif // QQUICKITEMVIEWTRANSITION_P_P_H
diff --git a/src/quick/items/qquicklistview.cpp b/src/quick/items/qquicklistview.cpp
index 7693c9d288..6d7ddfb0a2 100644
--- a/src/quick/items/qquicklistview.cpp
+++ b/src/quick/items/qquicklistview.cpp
@@ -3,6 +3,7 @@
#include "qquicklistview_p.h"
#include "qquickitemview_p_p.h"
+#include "qquickflickablebehavior_p.h"
#include <private/qqmlobjectmodel_p.h>
#include <QtQml/qqmlexpression.h>
@@ -14,7 +15,6 @@
#include <private/qquicksmoothedanimation_p_p.h>
#include <private/qqmlcomponent_p.h>
-#include "qplatformdefs.h"
QT_BEGIN_NAMESPACE
@@ -74,7 +74,9 @@ public:
void layoutVisibleItems(int fromModelIndex = 0) override;
bool applyInsertionChange(const QQmlChangeSet::Change &insert, ChangeResult *changeResult, QList<FxViewItem *> *addedItems, QList<MovedItem> *movingIntoView) override;
+#if QT_CONFIG(quick_viewtransitions)
void translateAndTransitionItemsAfter(int afterIndex, const ChangeResult &insertionResult, const ChangeResult &removalResult) override;
+#endif
void updateSectionCriteria() override;
void updateSections() override;
@@ -105,7 +107,7 @@ public:
void fixupPosition() override;
void fixup(AxisData &data, qreal minExtent, qreal maxExtent) override;
bool flick(QQuickItemViewPrivate::AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize,
- QQuickTimeLineCallback::Callback fixupCallback, qreal velocity) override;
+ QQuickTimeLineCallback::Callback fixupCallback, QEvent::Type eventType, qreal velocity) override;
QQuickItemViewAttached *getAttachedObject(const QObject *object) const override;
@@ -287,7 +289,8 @@ public:
: itemX() + itemWidth());
}
}
- void setPosition(qreal pos, bool immediate = false) {
+
+ void setPosition(qreal pos, bool immediate = false, bool resetInactiveAxis = true) {
// position the section immediately even if there is a transition
if (section()) {
if (view->orientation() == QQuickListView::Vertical) {
@@ -302,8 +305,9 @@ public:
section()->setX(pos);
}
}
- moveTo(pointForPosition(pos), immediate);
+ moveTo(pointForPosition(pos, resetInactiveAxis), immediate);
}
+
void setSize(qreal size) {
if (view->orientation() == QQuickListView::Vertical)
item->setHeight(size);
@@ -318,26 +322,26 @@ public:
QQuickListView *view;
private:
- QPointF pointForPosition(qreal pos) const {
+ QPointF pointForPosition(qreal pos, bool resetInactiveAxis) const {
if (view->orientation() == QQuickListView::Vertical) {
if (view->verticalLayoutDirection() == QQuickItemView::BottomToTop) {
if (section())
pos += section()->height();
- return QPointF(itemX(), -itemHeight() - pos);
+ return QPointF(resetInactiveAxis ? 0 : itemX(), -itemHeight() - pos);
} else {
if (section())
pos += section()->height();
- return QPointF(itemX(), pos);
+ return QPointF(resetInactiveAxis ? 0 : itemX(), pos);
}
} else {
if (view->effectiveLayoutDirection() == Qt::RightToLeft) {
if (section())
pos += section()->width();
- return QPointF(-itemWidth() - pos, itemY());
+ return QPointF(-itemWidth() - pos, resetInactiveAxis ? 0 : itemY());
} else {
if (section())
pos += section()->width();
- return QPointF(pos, itemY());
+ return QPointF(pos, resetInactiveAxis ? 0 : itemY());
}
}
}
@@ -700,6 +704,8 @@ bool QQuickListViewPrivate::releaseItem(FxViewItem *item, QQmlInstanceModel::Reu
bool released = QQuickItemViewPrivate::releaseItem(item, reusableFlag);
if (released && it && att && att->m_sectionItem) {
+ QQuickItemPrivate::get(att->m_sectionItem)->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
+
// We hold no more references to this item
int i = 0;
do {
@@ -755,7 +761,9 @@ bool QQuickListViewPrivate::addVisibleItems(qreal fillFrom, qreal fillTo, qreal
if (!(item = static_cast<FxListItemSG*>(createItem(modelIndex, incubationMode))))
break;
qCDebug(lcItemViewDelegateLifecycle) << "refill: append item" << modelIndex << "pos" << pos << "buffer" << doBuffer << "item" << (QObject *)(item->item);
+#if QT_CONFIG(quick_viewtransitions)
if (!transitioner || !transitioner->canTransition(QQuickItemViewTransitioner::PopulateTransition, true)) // pos will be set by layoutVisibleItems()
+#endif
item->setPosition(pos, true);
if (item->item)
QQuickItemPrivate::get(item->item)->setCulled(doBuffer);
@@ -774,7 +782,9 @@ bool QQuickListViewPrivate::addVisibleItems(qreal fillFrom, qreal fillTo, qreal
qCDebug(lcItemViewDelegateLifecycle) << "refill: prepend item" << visibleIndex-1 << "current top pos" << visiblePos << "buffer" << doBuffer << "item" << (QObject *)(item->item);
--visibleIndex;
visiblePos -= item->size() + spacing;
+#if QT_CONFIG(quick_viewtransitions)
if (!transitioner || !transitioner->canTransition(QQuickItemViewTransitioner::PopulateTransition, true)) // pos will be set by layoutVisibleItems()
+#endif
item->setPosition(visiblePos, true);
if (item->item)
QQuickItemPrivate::get(item->item)->setCulled(doBuffer);
@@ -787,11 +797,14 @@ bool QQuickListViewPrivate::addVisibleItems(qreal fillFrom, qreal fillTo, qreal
void QQuickListViewPrivate::removeItem(FxViewItem *item)
{
+#if QT_CONFIG(quick_viewtransitions)
if (item->transitionScheduledOrRunning()) {
qCDebug(lcItemViewDelegateLifecycle) << "\tnot releasing animating item" << item->index << (QObject *)(item->item);
item->releaseAfterTransition = true;
releasePendingTransition.append(item);
- } else {
+ } else
+#endif
+ {
qCDebug(lcItemViewDelegateLifecycle) << "\treleasing stationary item" << item->index << (QObject *)(item->item);
releaseItem(item, reusableFlag);
}
@@ -864,6 +877,14 @@ void QQuickListViewPrivate::layoutVisibleItems(int fromModelIndex)
FxListItemSG *firstItem = static_cast<FxListItemSG *>(visibleItems.constFirst());
bool fixedCurrent = currentItem && firstItem->item == currentItem->item;
+
+#if QT_CONFIG(quick_viewtransitions)
+ /* Set position of first item in list view when populate transition is configured, as it doesn't set
+ while adding visible item (addVisibleItem()) to the view */
+ if (transitioner && transitioner->canTransition(QQuickItemViewTransitioner::PopulateTransition, true))
+ resetFirstItemPosition(isContentFlowReversed() ? -firstItem->position()-firstItem->size() : firstItem->position());
+#endif
+
firstVisibleItemPosition = firstItem->position();
qreal sum = firstItem->size();
qreal pos = firstItem->position() + firstItem->size() + spacing;
@@ -1061,19 +1082,24 @@ QQuickItem * QQuickListViewPrivate::getSectionItem(const QString &section)
QQmlContext *context = QQmlEngine::contextForObject(sectionItem)->parentContext();
setSectionHelper(context, sectionItem, section);
} else {
- QQmlContext *creationContext = sectionCriteria->delegate()->creationContext();
- QQmlContext *context = new QQmlContext(
- creationContext ? creationContext : qmlContext(q));
QQmlComponent* delegate = sectionCriteria->delegate();
- QQmlComponentPrivate* delegatePriv = QQmlComponentPrivate::get(delegate);
+ const bool reuseExistingContext = delegate->isBound();
+ auto delegatePriv = QQmlComponentPrivate::get(delegate);
+ QQmlPropertyCache::ConstPtr rootPropertyCache;
+
+ QQmlContext *creationContext = sectionCriteria->delegate()->creationContext();
+ auto baseContext = creationContext ? creationContext : qmlContext(q);
+ // if we need to insert a context property, we need a separate context
+ QQmlContext *context = reuseExistingContext ? baseContext : new QQmlContext(baseContext);
QObject *nobj = delegate->beginCreate(context);
if (nobj) {
if (delegatePriv->hadTopLevelRequiredProperties()) {
delegate->setInitialProperties(nobj, {{QLatin1String("section"), section}});
- } else {
+ } else if (!reuseExistingContext) {
context->setContextProperty(QLatin1String("section"), section);
}
- QQml_setParent_noEvent(context, nobj);
+ if (!reuseExistingContext)
+ QQml_setParent_noEvent(context, nobj);
sectionItem = qobject_cast<QQuickItem *>(nobj);
if (!sectionItem) {
delete nobj;
@@ -1086,12 +1112,15 @@ QQuickItem * QQuickListViewPrivate::getSectionItem(const QString &section)
// sections are not controlled by FxListItemSG, so apply attached properties here
QQuickItemViewAttached *attached = static_cast<QQuickItemViewAttached*>(qmlAttachedPropertiesObject<QQuickListView>(sectionItem));
attached->setView(q);
- } else {
+ } else if (!reuseExistingContext) {
delete context;
}
sectionCriteria->delegate()->completeCreate();
}
+ if (sectionItem)
+ QQuickItemPrivate::get(sectionItem)->addItemChangeListener(this, QQuickItemPrivate::Geometry);
+
return sectionItem;
}
@@ -1100,6 +1129,9 @@ void QQuickListViewPrivate::releaseSectionItem(QQuickItem *item)
if (!item)
return;
int i = 0;
+
+ QQuickItemPrivate::get(item)->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
+
do {
if (!sectionCache[i]) {
sectionCache[i] = item;
@@ -1439,26 +1471,26 @@ void QQuickListViewPrivate::updateFooter()
FxListItemSG *listItem = static_cast<FxListItemSG*>(footer);
if (footerPositioning == QQuickListView::OverlayFooter) {
- listItem->setPosition(isContentFlowReversed() ? -position() - footerSize() : position() + size() - footerSize());
+ listItem->setPosition(isContentFlowReversed() ? -position() - footerSize() : position() + size() - footerSize(), false, false);
} else if (visibleItems.size()) {
if (footerPositioning == QQuickListView::PullBackFooter) {
qreal viewPos = isContentFlowReversed() ? -position() : position() + size();
// using qBound() would throw an assert here, because max < min is a valid case
// here, if the list's delegates do not fill the whole view
qreal clampedPos = qMax(originPosition() - footerSize() + size(), qMin(listItem->position(), lastPosition()));
- listItem->setPosition(qBound(viewPos - footerSize(), clampedPos, viewPos));
+ listItem->setPosition(qBound(viewPos - footerSize(), clampedPos, viewPos), false, false);
} else {
qreal endPos = lastPosition();
if (findLastVisibleIndex() == model->count()-1) {
- listItem->setPosition(endPos);
+ listItem->setPosition(endPos, false, false);
} else {
qreal visiblePos = position() + q->height();
if (endPos <= visiblePos || listItem->position() < endPos)
- listItem->setPosition(endPos);
+ listItem->setPosition(endPos, false, false);
}
}
} else {
- listItem->setPosition(visiblePos);
+ listItem->setPosition(visiblePos, false, false);
}
if (created)
@@ -1505,7 +1537,7 @@ void QQuickListViewPrivate::updateHeader()
FxListItemSG *listItem = static_cast<FxListItemSG*>(header);
if (headerPositioning == QQuickListView::OverlayHeader) {
- listItem->setPosition(isContentFlowReversed() ? -position() - size() : position());
+ listItem->setPosition(isContentFlowReversed() ? -position() - size() : position(), false, false);
} else if (visibleItems.size()) {
const bool fixingUp = (orient == QQuickListView::Vertical ? vData : hData).fixingUp;
if (headerPositioning == QQuickListView::PullBackHeader) {
@@ -1517,18 +1549,18 @@ void QQuickListViewPrivate::updateHeader()
// using qBound() would throw an assert here, because max < min is a valid case
// here, if the list's delegates do not fill the whole view
qreal clampedPos = qMax(originPosition() - headerSize(), qMin(headerPosition, lastPosition() - size()));
- listItem->setPosition(qBound(viewPos - headerSize(), clampedPos, viewPos));
+ listItem->setPosition(qBound(viewPos - headerSize(), clampedPos, viewPos), false, false);
} else {
qreal startPos = originPosition();
if (visibleIndex == 0) {
- listItem->setPosition(startPos - headerSize());
+ listItem->setPosition(startPos - headerSize(), false, false);
} else {
if (position() <= startPos || listItem->position() > startPos - headerSize())
- listItem->setPosition(startPos - headerSize());
+ listItem->setPosition(startPos - headerSize(), false, false);
}
}
} else {
- listItem->setPosition(-headerSize());
+ listItem->setPosition(-headerSize(), false, false);
}
if (created)
@@ -1579,8 +1611,10 @@ void QQuickListViewPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometry
// position all subsequent items
if (visibleItems.size() && item == visibleItems.constFirst()->item) {
FxListItemSG *listItem = static_cast<FxListItemSG*>(visibleItems.constFirst());
+#if QT_CONFIG(quick_viewtransitions)
if (listItem->transitionScheduledOrRunning())
return;
+#endif
if (orient == QQuickListView::Vertical) {
const qreal oldItemEndPosition = verticalLayoutDirection == QQuickItemView::BottomToTop ? -oldGeometry.y() : oldGeometry.y() + oldGeometry.height();
const qreal heightDiff = item->height() - oldGeometry.height();
@@ -1819,13 +1853,13 @@ void QQuickListViewPrivate::fixup(AxisData &data, qreal minExtent, qreal maxExte
}
bool QQuickListViewPrivate::flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize,
- QQuickTimeLineCallback::Callback fixupCallback, qreal velocity)
+ QQuickTimeLineCallback::Callback fixupCallback, QEvent::Type eventType, qreal velocity)
{
data.fixingUp = false;
moveReason = Mouse;
if ((!haveHighlightRange || highlightRange != QQuickListView::StrictlyEnforceRange) && snapMode == QQuickListView::NoSnap) {
correctFlick = true;
- return QQuickItemViewPrivate::flick(data, minExtent, maxExtent, vSize, fixupCallback, velocity);
+ return QQuickItemViewPrivate::flick(data, minExtent, maxExtent, vSize, fixupCallback, eventType, velocity);
}
qreal maxDistance = 0;
const qreal dataValue =
@@ -1881,7 +1915,7 @@ bool QQuickListViewPrivate::flick(AxisData &data, qreal minExtent, qreal maxExte
}
if (!hData.flicking && !vData.flicking) {
// the initial flick - estimate boundary
- qreal accel = deceleration;
+ qreal accel = eventType == QEvent::Wheel ? wheelDeceleration : deceleration;
qreal v2 = v * v;
overshootDist = 0.0;
// + averageSize/4 to encourage moving at least one item in the flick direction
@@ -1946,7 +1980,7 @@ bool QQuickListViewPrivate::flick(AxisData &data, qreal minExtent, qreal maxExte
else if (velocity > 0 && newtarget >= minExtent)
newtarget = minExtent + overshootDist;
if (newtarget == data.flickTarget) { // boundary unchanged - nothing to do
- if (qAbs(velocity) < MinimumFlickVelocity)
+ if (qAbs(velocity) < _q_MinimumFlickVelocity)
correctFlick = false;
return false;
}
@@ -1973,7 +2007,7 @@ bool QQuickListViewPrivate::flick(AxisData &data, qreal minExtent, qreal maxExte
void QQuickListViewPrivate::setSectionHelper(QQmlContext *context, QQuickItem *sectionItem, const QString &section)
{
- if (context->contextProperty(QLatin1String("section")).isValid())
+ if (!QQmlContextData::get(context)->isInternal() && context->contextProperty(QLatin1String("section")).isValid())
context->setContextProperty(QLatin1String("section"), section);
else
sectionItem->setProperty("section", section);
@@ -2004,6 +2038,11 @@ QQuickItemViewAttached *QQuickListViewPrivate::getAttachedObject(const QObject *
ListView are laid out horizontally or vertically. List views are inherently
flickable because ListView inherits from \l Flickable.
+ \note ListView will only load as many delegate items as needed to fill up the view.
+ Items outside of the view will not be loaded unless a sufficient \l cacheBuffer has
+ been set. Hence, a ListView with zero width or height might not load any delegate
+ items at all.
+
\section1 Example Usage
The following example shows the definition of a simple list model defined
@@ -2155,7 +2194,7 @@ QQuickItemViewAttached *QQuickListViewPrivate::getAttachedObject(const QObject *
of type \l [QML] {real}, so it is possible to set fractional
values like \c 0.1.
- \section1 Reusing items
+ \section1 Reusing Items
Since 5.15, ListView can be configured to recycle items instead of instantiating
from the \l delegate whenever new rows are flicked into view. This approach improves
@@ -2183,12 +2222,28 @@ QQuickItemViewAttached *QQuickListViewPrivate::getAttachedObject(const QObject *
\note While an item is in the pool, it might still be alive and respond
to connected signals and bindings.
+ \note For an item to be pooled, it needs to be completely flicked out of the bounds
+ of the view, \e including the extra margins set with \l {ListView::}{cacheBuffer}.
+ Some items will also never be pooled or reused, such as \l currentItem.
+
The following example shows a delegate that animates a spinning rectangle. When
it is pooled, the animation is temporarily paused:
\snippet qml/listview/ReusableDelegate.qml 0
\sa {QML Data Models}, GridView, PathView, {Qt Quick Examples - Views}
+
+ \section1 Variable Delegate Size and Section Labels
+
+ Variable delegate sizes might lead to resizing and skipping of any attached
+ \l {ScrollBar}. This is because ListView estimates its content size from
+ allocated items (usually only the visible items, the rest are assumed to be of
+ similar size), and variable delegate sizes prevent an accurate estimation. To
+ reduce this effect, \l {ListView::}{cacheBuffer} can be set to higher values,
+ effectively creating more items and improving the size estimate of unallocated
+ items, at the expense of additional memory usage. \l{ListView::section}{Sections}
+ have the same effect because they attach and elongate the section label to the
+ first item within the section.
*/
QQuickListView::QQuickListView(QQuickItem *parent)
: QQuickItemView(*(new QQuickListViewPrivate), parent)
@@ -2201,6 +2256,8 @@ QQuickListView::~QQuickListView()
/*!
\qmlattachedproperty bool QtQuick::ListView::isCurrentItem
+ \readonly
+
This attached property is true if this delegate is the current item; otherwise false.
It is attached to each instance of the delegate.
@@ -2212,6 +2269,8 @@ QQuickListView::~QQuickListView()
/*!
\qmlattachedproperty ListView QtQuick::ListView::view
+ \readonly
+
This attached property holds the view that manages this delegate instance.
It is attached to each instance of the delegate and also to the header, the footer,
@@ -2220,6 +2279,8 @@ QQuickListView::~QQuickListView()
/*!
\qmlattachedproperty string QtQuick::ListView::previousSection
+ \readonly
+
This attached property holds the section of the previous element.
It is attached to each instance of the delegate.
@@ -2229,6 +2290,8 @@ QQuickListView::~QQuickListView()
/*!
\qmlattachedproperty string QtQuick::ListView::nextSection
+ \readonly
+
This attached property holds the section of the next element.
It is attached to each instance of the delegate.
@@ -2238,6 +2301,8 @@ QQuickListView::~QQuickListView()
/*!
\qmlattachedproperty string QtQuick::ListView::section
+ \readonly
+
This attached property holds the section of this element.
It is attached to each instance of the delegate.
@@ -2346,7 +2411,7 @@ QQuickListView::~QQuickListView()
/*!
\qmlproperty int QtQuick::ListView::count
- This property holds the number of items in the view.
+ This property holds the number of items in the model.
*/
/*!
@@ -2449,15 +2514,13 @@ QQuickListView::~QQuickListView()
Valid values for \c highlightRangeMode are:
- \list
- \li ListView.ApplyRange - the view attempts to maintain the highlight within the range.
- However, the highlight can move outside of the range at the ends of the list or due
- to mouse interaction.
- \li ListView.StrictlyEnforceRange - the highlight never moves outside of the range.
- The current item changes if a keyboard or mouse action would cause the highlight to move
- outside of the range.
- \li ListView.NoHighlightRange - this is the default value.
- \endlist
+ \value ListView.ApplyRange the view attempts to maintain the highlight within the range.
+ However, the highlight can move outside of the range at the
+ ends of the list or due to mouse interaction.
+ \value ListView.StrictlyEnforceRange the highlight never moves outside of the range.
+ The current item changes if a keyboard or mouse action would
+ cause the highlight to move outside of the range.
+ \value ListView.NoHighlightRange this is the default value.
*/
void QQuickListView::setHighlightFollowsCurrentItem(bool autoHighlight)
{
@@ -2504,20 +2567,12 @@ void QQuickListView::setSpacing(qreal spacing)
Possible values:
- \list
- \li ListView.Horizontal - Items are laid out horizontally
- \li ListView.Vertical (default) - Items are laid out vertically
- \endlist
-
- \table
- \row
- \li Horizontal orientation:
- \image ListViewHorizontal.png
-
- \row
- \li Vertical orientation:
- \image listview-highlight.png
- \endtable
+ \value ListView.Horizontal Items are laid out horizontally
+ \br
+ \inlineimage ListViewHorizontal.png
+ \value ListView.Vertical (default) Items are laid out vertically
+ \br
+ \inlineimage listview-highlight.png
\sa {Flickable Direction}
*/
@@ -2558,10 +2613,8 @@ void QQuickListView::setOrientation(QQuickListView::Orientation orientation)
Possible values:
- \list
- \li Qt.LeftToRight (default) - Items will be laid out from left to right.
- \li Qt.RightToLeft - Items will be laid out from right to left.
- \endlist
+ \value Qt.LeftToRight (default) Items will be laid out from left to right.
+ \value Qt.RightToLeft Items will be laid out from right to left.
Setting this property has no effect if the \l orientation is Qt.Vertical.
@@ -2587,10 +2640,8 @@ void QQuickListView::setOrientation(QQuickListView::Orientation orientation)
Possible values:
- \list
- \li ListView.TopToBottom (default) - Items are laid out from the top of the view down to the bottom of the view.
- \li ListView.BottomToTop - Items are laid out from the bottom of the view up to the top of the view.
- \endlist
+ \value ListView.TopToBottom (default) Items are laid out from the top of the view down to the bottom of the view.
+ \value ListView.BottomToTop Items are laid out from the bottom of the view up to the top of the view.
Setting this property has no effect if the \l orientation is Qt.Horizontal.
@@ -2698,35 +2749,35 @@ void QQuickListView::setOrientation(QQuickListView::Orientation orientation)
\c section.criteria holds the criteria for forming each section based on
\c section.property. This value can be one of:
- \list
- \li ViewSection.FullString (default) - sections are created based on the
- \c section.property value.
- \li ViewSection.FirstCharacter - sections are created based on the first
- character of the \c section.property value (for example, 'A', 'B', 'C'
- sections, etc. for an address book)
- \endlist
+ \value ViewSection.FullString (default) sections are created based on the
+ \c section.property value.
+ \value ViewSection.FirstCharacter sections are created based on the first character of
+ the \c section.property value (for example,
+ 'A', 'B', 'C' ... sections for an address book.)
A case insensitive comparison is used when determining section
boundaries.
\c section.delegate holds the delegate component for each section. The
default \l {QQuickItem::z}{stacking order} of section delegate instances
- is \c 2.
+ is \c 2. If you declare a \c required property named "section" in it,
+ that property will contain the section's title.
\c section.labelPositioning determines whether the current and/or
next section labels stick to the start/end of the view, and whether
the labels are shown inline. This value can be a combination of:
- \list
- \li ViewSection.InlineLabels - section labels are shown inline between
- the item delegates separating sections (default).
- \li ViewSection.CurrentLabelAtStart - the current section label sticks to the
- start of the view as it is moved.
- \li ViewSection.NextLabelAtEnd - the next section label (beyond all visible
- sections) sticks to the end of the view as it is moved. \note Enabling
- \c ViewSection.NextLabelAtEnd requires the view to scan ahead for the next
- section, which has performance implications, especially for slower models.
- \endlist
+ \value ViewSection.InlineLabels
+ (default) section labels are shown inline between the item delegates
+ separating sections.
+ \value ViewSection.CurrentLabelAtStart
+ the current section label sticks to the start of the view as it is moved.
+ \value ViewSection.NextLabelAtEnd
+ the next section label (beyond all visible sections) sticks to the end
+ of the view as it is moved.
+ \note Enabling \c ViewSection.NextLabelAtEnd requires the view to scan
+ ahead for the next section, which has performance implications,
+ especially for slower models.
Each item in the list has attached properties named \c ListView.section,
\c ListView.previousSection and \c ListView.nextSection.
@@ -2877,18 +2928,14 @@ void QQuickListView::setHighlightResizeDuration(int duration)
This property determines how the view scrolling will settle following a drag or flick.
The possible values are:
- \list
- \li ListView.NoSnap (default) - the view stops anywhere within the visible area.
- \li ListView.SnapToItem - the view settles with an item aligned with the start of
- the view.
- \li ListView.SnapOneItem - the view settles no more than one item away from the first
- visible item at the time the mouse button is released. This mode is particularly
- useful for moving one page at a time. When SnapOneItem is enabled, the ListView will
- show a stronger affinity to neighboring items when movement occurs. For example, a
- short drag that snaps back to the current item with SnapToItem might snap to a
- neighboring item with SnapOneItem.
-
- \endlist
+ \value ListView.NoSnap (default) the view stops anywhere within the visible area.
+ \value ListView.SnapToItem the view settles with an item aligned with the start of the view.
+ \value ListView.SnapOneItem the view settles no more than one item away from the first
+ visible item at the time the mouse button is released. This mode is particularly
+ useful for moving one page at a time. When SnapOneItem is enabled, the ListView will
+ show a stronger affinity to neighboring items when movement occurs. For example, a
+ short drag that snaps back to the current item with SnapToItem might snap to a
+ neighboring item with SnapOneItem.
\c snapMode does not affect the \l currentIndex. To update the
\l currentIndex as the list is moved, set \l highlightRangeMode
@@ -3431,12 +3478,12 @@ void QQuickListView::viewportMoved(Qt::Orientations orient)
const qreal minY = minYExtent();
if ((minY - d->vData.move.value() < height()/2 || d->vData.flickTarget - d->vData.move.value() < height()/2)
&& minY != d->vData.flickTarget)
- d->flickY(-d->vData.smoothVelocity.value());
+ d->flickY(QEvent::TouchUpdate, -d->vData.smoothVelocity.value());
} else if (d->vData.velocity < 0) {
const qreal maxY = maxYExtent();
if ((d->vData.move.value() - maxY < height()/2 || d->vData.move.value() - d->vData.flickTarget < height()/2)
&& maxY != d->vData.flickTarget)
- d->flickY(-d->vData.smoothVelocity.value());
+ d->flickY(QEvent::TouchUpdate, -d->vData.smoothVelocity.value());
}
}
@@ -3445,12 +3492,12 @@ void QQuickListView::viewportMoved(Qt::Orientations orient)
const qreal minX = minXExtent();
if ((minX - d->hData.move.value() < width()/2 || d->hData.flickTarget - d->hData.move.value() < width()/2)
&& minX != d->hData.flickTarget)
- d->flickX(-d->hData.smoothVelocity.value());
+ d->flickX(QEvent::TouchUpdate, -d->hData.smoothVelocity.value());
} else if (d->hData.velocity < 0) {
const qreal maxX = maxXExtent();
if ((d->hData.move.value() - maxX < width()/2 || d->hData.move.value() - d->hData.flickTarget < width()/2)
&& maxX != d->hData.flickTarget)
- d->flickX(-d->hData.smoothVelocity.value());
+ d->flickX(QEvent::TouchUpdate, -d->hData.smoothVelocity.value());
}
}
d->inFlickCorrection = false;
@@ -3610,9 +3657,16 @@ void QQuickListViewPrivate::updateSectionCriteria()
bool QQuickListViewPrivate::applyInsertionChange(const QQmlChangeSet::Change &change, ChangeResult *insertResult, QList<FxViewItem *> *addedItems, QList<MovedItem> *movingIntoView)
{
+ Q_Q(QQuickListView);
+#if QT_CONFIG(quick_viewtransitions)
+ Q_UNUSED(movingIntoView)
+#endif
int modelIndex = change.index;
int count = change.count;
+ if (q->size().isEmpty() && visibleItems.isEmpty())
+ return false;
+
qreal tempPos = isContentFlowReversed() ? -position()-size() : position();
int index = visibleItems.size() ? mapFromModel(modelIndex) : 0;
qreal lastVisiblePos = buffer + displayMarginEnd + tempPos + size();
@@ -3652,10 +3706,12 @@ bool QQuickListViewPrivate::applyInsertionChange(const QQmlChangeSet::Change &ch
for (FxViewItem *item : std::as_const(visibleItems)) {
if (item->index != -1 && item->index >= modelIndex) {
item->index += count;
+#if QT_CONFIG(quick_viewtransitions)
if (change.isMove())
item->transitionNextReposition(transitioner, QQuickItemViewTransitioner::MoveTransition, false);
else
item->transitionNextReposition(transitioner, QQuickItemViewTransitioner::AddTransition, false);
+#endif
}
}
@@ -3690,9 +3746,11 @@ bool QQuickListViewPrivate::applyInsertionChange(const QQmlChangeSet::Change &ch
insertResult->changedFirstItem = true;
if (!change.isMove()) {
addedItems->append(item);
+#if QT_CONFIG(quick_viewtransitions)
if (transitioner)
item->transitionNextReposition(transitioner, QQuickItemViewTransitioner::AddTransition, true);
else
+#endif
static_cast<FxListItemSG *>(item)->setPosition(pos, true);
}
insertResult->sizeChangesBeforeVisiblePos += item->size() + spacing;
@@ -3721,7 +3779,9 @@ bool QQuickListViewPrivate::applyInsertionChange(const QQmlChangeSet::Change &ch
FxViewItem *item = nullptr;
if (change.isMove() && (item = currentChanges.removedItems.take(change.moveKey(it.index))))
item->index = it.index;
+#if QT_CONFIG(quick_viewtransitions)
bool newItem = !item;
+#endif
it.removedAtIndex = false;
if (!item)
item = createItem(it.index, QQmlIncubator::Synchronous);
@@ -3732,19 +3792,26 @@ bool QQuickListViewPrivate::applyInsertionChange(const QQmlChangeSet::Change &ch
continue;
}
- visibleItems.insert(index, item);
+ if (index < visibleItems.size())
+ visibleItems.insert(index, item);
+ else // special case of appending an item to the model - as above
+ visibleItems.append(item);
if (index == 0)
insertResult->changedFirstItem = true;
if (change.isMove()) {
// we know this is a move target, since move displaced items that are
// shuffled into view due to a move would be added in refill()
+#if QT_CONFIG(quick_viewtransitions)
if (newItem && transitioner && transitioner->canTransition(QQuickItemViewTransitioner::MoveTransition, true))
movingIntoView->append(MovedItem(item, change.moveKey(item->index)));
+#endif
} else {
addedItems->append(item);
+#if QT_CONFIG(quick_viewtransitions)
if (transitioner)
item->transitionNextReposition(transitioner, QQuickItemViewTransitioner::AddTransition, true);
else
+#endif
static_cast<FxListItemSG *>(item)->setPosition(pos, true);
}
insertResult->sizeChangesAfterVisiblePos += item->size() + spacing;
@@ -3758,13 +3825,17 @@ bool QQuickListViewPrivate::applyInsertionChange(const QQmlChangeSet::Change &ch
FxViewItem *item = visibleItems.at(index);
if (prevItem->index != item->index - 1) {
int i = index;
+#if QT_CONFIG(quick_viewtransitions)
qreal prevPos = prevItem->position();
+#endif
while (i < visibleItems.size()) {
FxListItemSG *nvItem = static_cast<FxListItemSG *>(visibleItems.takeLast());
insertResult->sizeChangesAfterVisiblePos -= nvItem->size() + spacing;
addedItems->removeOne(nvItem);
+#if QT_CONFIG(quick_viewtransitions)
if (nvItem->transitionScheduledOrRunning())
nvItem->setPosition(prevPos + (nvItem->index - prevItem->index) * averageSize);
+#endif
removeItem(nvItem);
}
}
@@ -3776,6 +3847,7 @@ bool QQuickListViewPrivate::applyInsertionChange(const QQmlChangeSet::Change &ch
return visibleAffected;
}
+#if QT_CONFIG(quick_viewtransitions)
void QQuickListViewPrivate::translateAndTransitionItemsAfter(int afterModelIndex, const ChangeResult &insertionResult, const ChangeResult &removalResult)
{
Q_UNUSED(insertionResult);
@@ -3809,25 +3881,22 @@ void QQuickListViewPrivate::translateAndTransitionItemsAfter(int afterModelIndex
}
}
}
+#endif
/*!
\qmlmethod QtQuick::ListView::positionViewAtIndex(int index, PositionMode mode)
- Positions the view such that the \a index is at the position specified by
- \a mode:
-
- \list
- \li ListView.Beginning - position item at the top (or left for horizontal orientation) of the view.
- \li ListView.Center - position item in the center of the view.
- \li ListView.End - position item at bottom (or right for horizontal orientation) of the view.
- \li ListView.Visible - if any part of the item is visible then take no action, otherwise
- bring the item into view.
- \li ListView.Contain - ensure the entire item is visible. If the item is larger than
- the view the item is positioned at the top (or left for horizontal orientation) of the view.
- \li ListView.SnapPosition - position the item at \l preferredHighlightBegin. This mode
- is only valid if \l highlightRangeMode is StrictlyEnforceRange or snapping is enabled
- via \l snapMode.
- \endlist
+ Positions the view such that the \a index is at the position specified by \a mode:
+
+ \value ListView.Beginning position item at the top (or left for horizontal orientation) of the view.
+ \value ListView.Center position item in the center of the view.
+ \value ListView.End position item at bottom (or right for horizontal orientation) of the view.
+ \value ListView.Visible if any part of the item is visible then take no action, otherwise
+ bring the item into view.
+ \value ListView.Contain ensure the entire item is visible. If the item is larger than the view,
+ the item is positioned at the top (or left for horizontal orientation) of the view.
+ \value ListView.SnapPosition position the item at \l preferredHighlightBegin. This mode is only valid
+ if \l highlightRangeMode is StrictlyEnforceRange or snapping is enabled via \l snapMode.
If positioning the view at \a index would cause empty space to be displayed at
the beginning or end of the view, the view will be positioned at the boundary.
diff --git a/src/quick/items/qquicklistview_p.h b/src/quick/items/qquicklistview_p.h
index e4719b5d36..4651bc689f 100644
--- a/src/quick/items/qquicklistview_p.h
+++ b/src/quick/items/qquicklistview_p.h
@@ -23,11 +23,13 @@ QT_REQUIRE_CONFIG(quick_listview);
#include <private/qtquickglobal_p.h>
+#include <QtCore/qpointer.h>
+
QT_BEGIN_NAMESPACE
class QQuickListView;
class QQuickListViewPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickViewSection : public QObject
+class Q_QUICK_EXPORT QQuickViewSection : public QObject
{
Q_OBJECT
Q_PROPERTY(QString property READ property WRITE setProperty NOTIFY propertyChanged)
@@ -75,7 +77,7 @@ private:
class QQmlInstanceModel;
class QQuickListViewAttached;
-class Q_QUICK_PRIVATE_EXPORT QQuickListView : public QQuickItemView
+class Q_QUICK_EXPORT QQuickListView : public QQuickItemView
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickListView)
@@ -169,7 +171,7 @@ protected:
qreal maxXExtent() const override;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickListViewAttached : public QQuickItemViewAttached
+class Q_QUICK_EXPORT QQuickListViewAttached : public QQuickItemViewAttached
{
Q_OBJECT
@@ -185,7 +187,4 @@ public:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickListView)
-QML_DECLARE_TYPE(QQuickViewSection)
-
#endif // QQUICKLISTVIEW_P_H
diff --git a/src/quick/items/qquickloader.cpp b/src/quick/items/qquickloader.cpp
index da04a2956c..496948fcb7 100644
--- a/src/quick/items/qquickloader.cpp
+++ b/src/quick/items/qquickloader.cpp
@@ -13,8 +13,6 @@
QT_BEGIN_NAMESPACE
-Q_DECLARE_LOGGING_CATEGORY(lcTransient)
-
static const QQuickItemPrivate::ChangeTypes watchedChanges
= QQuickItemPrivate::Geometry | QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight;
@@ -161,9 +159,7 @@ qreal QQuickLoaderPrivate::getImplicitHeight() const
\section2 Loader Sizing Behavior
- If the source component is not an Item type, Loader does not
- apply any special sizing rules. When used to load visual types,
- Loader applies the following sizing rules:
+ When used to load visual types, Loader applies the following sizing rules:
\list
\li If an explicit size is not specified for the Loader, the Loader
@@ -190,6 +186,8 @@ qreal QQuickLoaderPrivate::getImplicitHeight() const
\li The red rectangle will be 50x50, centered in the root item.
\endtable
+ If the source component is not an Item type, Loader does not apply any
+ special sizing rules.
\section2 Receiving Signals from Loaded Objects
@@ -673,13 +671,6 @@ void QQuickLoaderPrivate::incubatorStateChanged(QQmlIncubator::Status status)
if (status == QQmlIncubator::Ready) {
object = incubator->object();
item = qmlobject_cast<QQuickItem*>(object);
- if (!item) {
- QQuickWindow *window = qmlobject_cast<QQuickWindow*>(object);
- if (window) {
- qCDebug(lcTransient) << window << "is transient for" << q->window();
- window->setTransientParent(q->window());
- }
- }
emit q->itemChanged();
initResize();
incubator->clear();
@@ -795,7 +786,7 @@ void QQuickLoader::componentComplete()
{
Q_D(QQuickLoader);
QQuickItem::componentComplete();
- if (active()) {
+ if (active() && (status() != Ready)) {
if (d->loadingFromSource)
d->createComponent();
d->load();
@@ -804,12 +795,15 @@ void QQuickLoader::componentComplete()
void QQuickLoader::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
{
- if (change == ItemSceneChange) {
- QQuickWindow *loadedWindow = qmlobject_cast<QQuickWindow *>(item());
- if (loadedWindow) {
- qCDebug(lcTransient) << loadedWindow << "is transient for" << value.window;
- loadedWindow->setTransientParent(value.window);
- }
+ switch (change) {
+ case ItemChildAddedChange:
+ Q_ASSERT(value.item);
+ if (value.item->flags().testFlag(QQuickItem::ItemObservesViewport))
+ // Re-trigger the parent traversal to get subtreeTransformChangedEnabled turned on
+ value.item->setFlag(QQuickItem::ItemObservesViewport);
+ break;
+ default:
+ break;
}
QQuickItem::itemChange(change, value);
}
@@ -915,12 +909,24 @@ void QQuickLoaderPrivate::_q_updateSize(bool loaderGeometryChanged)
const bool needToUpdateWidth = loaderGeometryChanged && q->widthValid();
const bool needToUpdateHeight = loaderGeometryChanged && q->heightValid();
- if (needToUpdateWidth && needToUpdateHeight)
+ if (needToUpdateWidth && needToUpdateHeight) {
+ /* setSize keeps bindings intact (for backwards compatibility reasons),
+ but here we actually want the loader to control the size, so any
+ prexisting bindings ought to be removed
+ */
+ auto *itemPriv = QQuickItemPrivate::get(item);
+ // takeBinding would work without the check, but this is more efficient
+ // for the common case where we don't have a binding
+ if (itemPriv->width.hasBinding())
+ itemPriv->width.takeBinding();
+ if (itemPriv->height.hasBinding())
+ itemPriv->height.takeBinding();
item->setSize(QSizeF(q->width(), q->height()));
- else if (needToUpdateWidth)
+ } else if (needToUpdateWidth) {
item->setWidth(q->width());
- else if (needToUpdateHeight)
+ } else if (needToUpdateHeight) {
item->setHeight(q->height());
+ }
if (updatingSize)
return;
diff --git a/src/quick/items/qquickloader_p.h b/src/quick/items/qquickloader_p.h
index f8c65513d5..c3d2015aed 100644
--- a/src/quick/items/qquickloader_p.h
+++ b/src/quick/items/qquickloader_p.h
@@ -20,8 +20,7 @@
QT_BEGIN_NAMESPACE
class QQuickLoaderPrivate;
-class QQmlV4Function;
-class Q_QUICK_PRIVATE_EXPORT QQuickLoader : public QQuickImplicitSizeItem
+class Q_QUICK_EXPORT QQuickLoader : public QQuickImplicitSizeItem
{
Q_OBJECT
@@ -90,6 +89,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickLoader)
-
#endif // QQUICKLOADER_P_H
diff --git a/src/quick/items/qquickloader_p_p.h b/src/quick/items/qquickloader_p_p.h
index 43257e0c84..39011f1162 100644
--- a/src/quick/items/qquickloader_p_p.h
+++ b/src/quick/items/qquickloader_p_p.h
@@ -27,7 +27,6 @@ QT_BEGIN_NAMESPACE
class QQuickLoaderPrivate;
-class QQmlV4Function;
class QQuickLoaderIncubator : public QQmlIncubator
{
public:
diff --git a/src/quick/items/qquickmousearea.cpp b/src/quick/items/qquickmousearea.cpp
index dc44deca38..7e68dd8be3 100644
--- a/src/quick/items/qquickmousearea.cpp
+++ b/src/quick/items/qquickmousearea.cpp
@@ -19,9 +19,7 @@
QT_BEGIN_NAMESPACE
-DEFINE_BOOL_CONFIG_OPTION(qmlVisualTouchDebugging, QML_VISUAL_TOUCH_DEBUGGING)
-
-Q_DECLARE_LOGGING_CATEGORY(lcHoverTrace)
+DEFINE_BOOL_CONFIG_OPTION(qmlMaVisualTouchDebugging, QML_VISUAL_TOUCH_DEBUGGING)
QQuickMouseAreaPrivate::QQuickMouseAreaPrivate()
: enabled(true), hoverEnabled(false), scrollGestureEnabled(true), hovered(false), longPress(false),
@@ -53,7 +51,7 @@ void QQuickMouseAreaPrivate::init()
q->setAcceptedMouseButtons(Qt::LeftButton);
q->setAcceptTouchEvents(false); // rely on mouse events synthesized from touch
q->setFiltersChildMouseEvents(true);
- if (qmlVisualTouchDebugging()) {
+ if (qmlMaVisualTouchDebugging()) {
q->setFlag(QQuickItem::ItemHasContents);
}
}
@@ -743,7 +741,9 @@ void QQuickMouseArea::mouseMoveEvent(QMouseEvent *event)
QQuickMouseEvent &me = d->quickMouseEvent;
me.reset(d->lastPos.x(), d->lastPos.y(), d->lastButton, d->lastButtons, d->lastModifiers, false, d->longPress, event->flags());
+#if QT_DEPRECATED_SINCE(6, 6)
me.setSource(event->source());
+#endif
emit mouseXChanged(&me);
me.setPosition(d->lastPos);
emit mouseYChanged(&me);
@@ -788,13 +788,18 @@ void QQuickMouseArea::mouseDoubleClickEvent(QMouseEvent *event)
QQuickMouseEvent &me = d->quickMouseEvent;
me.reset(d->lastPos.x(), d->lastPos.y(), d->lastButton, d->lastButtons, d->lastModifiers, true,
false, event->flags());
+#if QT_DEPRECATED_SINCE(6, 6)
me.setSource(event->source());
+#endif
me.setAccepted(d->isDoubleClickConnected());
emit this->doubleClicked(&me);
if (!me.isAccepted())
d->propagate(&me, QQuickMouseAreaPrivate::DoubleClick);
if (d->pressed)
d->doubleClick = d->isDoubleClickConnected() || me.isAccepted();
+
+ // Do not call the base implementation: we don't want to call event->ignore().
+ return;
}
QQuickItem::mouseDoubleClickEvent(event);
}
@@ -902,6 +907,7 @@ void QQuickMouseArea::ungrabMouse()
emit pressedButtonsChanged();
if (d->hovered && !isUnderMouse()) {
+ qCDebug(lcHoverTrace) << "losing hover: not under the mouse";
d->hovered = false;
emit hoveredChanged();
}
@@ -966,6 +972,7 @@ bool QQuickMouseArea::sendMouseEvent(QMouseEvent *event)
emit pressedChanged();
emit containsPressChanged();
if (d->hovered) {
+ qCDebug(lcHoverTrace) << "losing hover: button released";
d->hovered = false;
emit hoveredChanged();
}
@@ -1012,7 +1019,9 @@ void QQuickMouseArea::timerEvent(QTimerEvent *event)
d->longPress = true;
QQuickMouseEvent &me = d->quickMouseEvent;
me.reset(d->lastPos.x(), d->lastPos.y(), d->lastButton, d->lastButtons, d->lastModifiers, false, d->longPress, d->lastFlags);
+#if QT_DEPRECATED_SINCE(6, 6)
me.setSource(Qt::MouseEventSynthesizedByQt);
+#endif
me.setAccepted(d->isPressAndHoldConnected());
emit pressAndHold(&me);
if (!me.isAccepted())
@@ -1028,7 +1037,7 @@ void QQuickMouseArea::geometryChange(const QRectF &newGeometry, const QRectF &ol
Q_D(QQuickMouseArea);
QQuickItem::geometryChange(newGeometry, oldGeometry);
- if (d->lastScenePos.isNull)
+ if (!d->lastScenePos.isValid())
d->lastScenePos = mapToScene(d->lastPos);
else if (newGeometry.x() != oldGeometry.x() || newGeometry.y() != oldGeometry.y())
d->lastPos = mapFromScene(d->lastScenePos);
@@ -1130,8 +1139,10 @@ void QQuickMouseArea::setHoverEnabled(bool h)
\qmlproperty bool QtQuick::MouseArea::containsMouse
This property holds whether the mouse is currently inside the mouse area.
- \warning If hoverEnabled is false, containsMouse will only be valid
+ \warning If hoverEnabled is \c false, \c containsMouse will be \c true
when the mouse is pressed while the mouse cursor is inside the MouseArea.
+ But if you set \c {mouse.accepted = false} in an \c onPressed handler,
+ \c containsMouse will remain \c false because the press was rejected.
*/
bool QQuickMouseArea::hovered() const
{
@@ -1229,7 +1240,9 @@ bool QQuickMouseArea::setPressed(Qt::MouseButton button, bool p, Qt::MouseEventS
if (wasPressed != p) {
QQuickMouseEvent &me = d->quickMouseEvent;
me.reset(d->lastPos.x(), d->lastPos.y(), d->lastButton, d->lastButtons, d->lastModifiers, isclick, d->longPress, d->lastFlags);
+#if QT_DEPRECATED_SINCE(6, 6)
me.setSource(source);
+#endif
if (p) {
d->pressed |= button;
if (!d->doubleClick)
@@ -1241,6 +1254,8 @@ bool QQuickMouseArea::setPressed(Qt::MouseButton button, bool p, Qt::MouseEventS
if (!me.isAccepted()) {
d->pressed = Qt::NoButton;
+ if (!hoverEnabled())
+ setHovered(false);
}
if (!oldPressed) {
@@ -1267,6 +1282,7 @@ bool QQuickMouseArea::setPressed(Qt::MouseButton button, bool p, Qt::MouseEventS
return me.isAccepted();
}
+ Q_UNUSED(source)
return false;
}
@@ -1438,7 +1454,7 @@ QSGNode *QQuickMouseArea::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData
Q_UNUSED(data);
Q_D(QQuickMouseArea);
- if (!qmlVisualTouchDebugging())
+ if (!qmlMaVisualTouchDebugging())
return nullptr;
QSGInternalRectangleNode *rectangle = static_cast<QSGInternalRectangleNode *>(oldNode);
diff --git a/src/quick/items/qquickmousearea_p.h b/src/quick/items/qquickmousearea_p.h
index 9ce57bcf60..96e60002b5 100644
--- a/src/quick/items/qquickmousearea_p.h
+++ b/src/quick/items/qquickmousearea_p.h
@@ -26,7 +26,7 @@ class QQuickDrag;
class QQuickMouseAreaPrivate;
class QQuickWheelEvent;
// used in Qt Location
-class Q_QUICK_PRIVATE_EXPORT QQuickMouseArea : public QQuickItem
+class Q_QUICK_EXPORT QQuickMouseArea : public QQuickItem
{
Q_OBJECT
@@ -163,6 +163,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickMouseArea)
-
#endif // QQUICKMOUSEAREA_P_H
diff --git a/src/quick/items/qquickmultipointtoucharea.cpp b/src/quick/items/qquickmultipointtoucharea.cpp
index fc994fd18c..b0b99334a2 100644
--- a/src/quick/items/qquickmultipointtoucharea.cpp
+++ b/src/quick/items/qquickmultipointtoucharea.cpp
@@ -17,7 +17,7 @@
QT_BEGIN_NAMESPACE
-DEFINE_BOOL_CONFIG_OPTION(qmlVisualTouchDebugging, QML_VISUAL_TOUCH_DEBUGGING)
+DEFINE_BOOL_CONFIG_OPTION(qmlMptaVisualTouchDebugging, QML_VISUAL_TOUCH_DEBUGGING)
/*!
\qmltype TouchPoint
@@ -329,18 +329,39 @@ void QQuickTouchPoint::setUniqueId(const QPointingDeviceUniqueId &id)
If minimumTouchPoints is set to a value greater than one, this signal will not be emitted until the minimum number
of required touch points has been reached.
+
+ \note If you use the \c touchPoints argument in your signal handler code,
+ it's best to rename it in your formal parameter to avoid confusion with the
+ \c touchPoints property (see \l{QML Coding Conventions}):
+ \qml
+ onPressed: (points) => console.log("pressed", points.length)
+ \endqml
*/
/*!
\qmlsignal QtQuick::MultiPointTouchArea::updated(list<TouchPoint> touchPoints)
This signal is emitted when existing touch points are updated. \a touchPoints is a list of these updated points.
+
+ \note If you use the \c touchPoints argument in your signal handler code,
+ it's best to rename it in your formal parameter to avoid confusion with the
+ \c touchPoints property (see \l{QML Coding Conventions}):
+ \qml
+ onUpdated: (points) => console.log("updated", points.length)
+ \endqml
*/
/*!
\qmlsignal QtQuick::MultiPointTouchArea::released(list<TouchPoint> touchPoints)
This signal is emitted when existing touch points are removed. \a touchPoints is a list of these removed points.
+
+ \note If you use the \c touchPoints argument in your signal handler code,
+ it's best to rename it in your formal parameter to avoid confusion with the
+ \c touchPoints property (see \l{QML Coding Conventions}):
+ \qml
+ onReleased: (points) => console.log("released", points.length)
+ \endqml
*/
/*!
@@ -356,8 +377,17 @@ void QQuickTouchPoint::setUniqueId(const QPointingDeviceUniqueId &id)
\c canceled should be handled in addition to \l released.
\a touchPoints is the list of canceled points.
+
+ \note If you use the \c touchPoints argument in your signal handler code,
+ it's best to rename it in your formal parameter to avoid confusion with the
+ \c touchPoints property (see \l{QML Coding Conventions}):
+ \qml
+ onCanceled: (points) => console.log("canceled", points.length)
+ \endqml
*/
+// TODO Qt 7: remove the notes above about the signal touchPoints arguments
+
/*!
\qmlsignal QtQuick::MultiPointTouchArea::gestureStarted(GestureEvent gesture)
@@ -407,11 +437,11 @@ QQuickMultiPointTouchArea::QQuickMultiPointTouchArea(QQuickItem *parent)
{
setAcceptedMouseButtons(Qt::LeftButton);
setFiltersChildMouseEvents(true);
- if (qmlVisualTouchDebugging()) {
+ if (qmlMptaVisualTouchDebugging()) {
setFlag(QQuickItem::ItemHasContents);
}
setAcceptTouchEvents(true);
-#ifdef Q_OS_OSX
+#ifdef Q_OS_MACOS
setAcceptHoverEvents(true); // needed to enable touch events on mouse hover.
#endif
}
@@ -596,6 +626,8 @@ void QQuickMultiPointTouchArea::updateTouchData(QEvent *event, RemapEventPoints
}
if (numTouchPoints >= _minimumTouchPoints && numTouchPoints <= _maximumTouchPoints) {
for (QEventPoint &p : touchPoints) {
+ QPointF oldPos = p.position();
+ auto transformBack = qScopeGuard([&] { QMutableEventPoint::setPosition(p, oldPos); });
if (touchPointsFromEvent && remap == RemapEventPoints::ToLocal)
QMutableEventPoint::setPosition(p, mapFromScene(p.scenePosition()));
QEventPoint::State touchPointState = p.state();
@@ -715,17 +747,17 @@ void QQuickMultiPointTouchArea::addTouchPoint(const QMouseEvent *e)
_mouseTouchPoint = dtp;
}
-#ifdef Q_OS_OSX
+#ifdef Q_OS_MACOS
void QQuickMultiPointTouchArea::hoverEnterEvent(QHoverEvent *event)
{
- Q_UNUSED(event);
- setTouchEventsEnabled(true);
+ setTouchEventsEnabled(isEnabled());
+ QQuickItem::hoverEnterEvent(event);
}
void QQuickMultiPointTouchArea::hoverLeaveEvent(QHoverEvent *event)
{
- Q_UNUSED(event);
setTouchEventsEnabled(false);
+ QQuickItem::hoverLeaveEvent(event);
}
void QQuickMultiPointTouchArea::setTouchEventsEnabled(bool enable)
@@ -739,7 +771,14 @@ void QQuickMultiPointTouchArea::setTouchEventsEnabled(bool enable)
registerTouchWindow(window(), enable);
}
-#endif // Q_OS_OSX
+
+void QQuickMultiPointTouchArea::itemChange(ItemChange change, const ItemChangeData &data)
+{
+ if (change == ItemEnabledHasChanged)
+ setAcceptHoverEvents(data.boolValue);
+ QQuickItem::itemChange(change, data);
+}
+#endif // Q_OS_MACOS
void QQuickMultiPointTouchArea::addTouchPrototype(QQuickTouchPoint *prototype)
{
@@ -1000,7 +1039,7 @@ QSGNode *QQuickMultiPointTouchArea::updatePaintNode(QSGNode *oldNode, UpdatePain
{
Q_UNUSED(data);
- if (!qmlVisualTouchDebugging())
+ if (!qmlMptaVisualTouchDebugging())
return nullptr;
QSGInternalRectangleNode *rectangle = static_cast<QSGInternalRectangleNode *>(oldNode);
diff --git a/src/quick/items/qquickmultipointtoucharea_p.h b/src/quick/items/qquickmultipointtoucharea_p.h
index f92705f28e..51ddb1645c 100644
--- a/src/quick/items/qquickmultipointtoucharea_p.h
+++ b/src/quick/items/qquickmultipointtoucharea_p.h
@@ -30,7 +30,7 @@
QT_BEGIN_NAMESPACE
class QQuickMultiPointTouchArea;
-class Q_QUICK_PRIVATE_EXPORT QQuickTouchPoint : public QObject
+class Q_QUICK_EXPORT QQuickTouchPoint : public QObject
{
Q_OBJECT
Q_PROPERTY(int pointId READ pointId NOTIFY pointIdChanged)
@@ -152,8 +152,8 @@ private:
class QQuickGrabGestureEvent : public QObject
{
Q_OBJECT
- Q_PROPERTY(QQmlListProperty<QObject> touchPoints READ touchPoints CONSTANT)
- Q_PROPERTY(qreal dragThreshold READ dragThreshold CONSTANT)
+ Q_PROPERTY(QQmlListProperty<QObject> touchPoints READ touchPoints CONSTANT FINAL)
+ Q_PROPERTY(qreal dragThreshold READ dragThreshold CONSTANT FINAL)
QML_NAMED_ELEMENT(GestureEvent)
QML_ADDED_IN_VERSION(2, 0)
QML_UNCREATABLE("GestureEvent is only available in the context of handling the gestureStarted signal from MultiPointTouchArea.")
@@ -176,12 +176,12 @@ private:
QList<QObject*> _touchPoints;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickMultiPointTouchArea : public QQuickItem
+class Q_QUICK_EXPORT QQuickMultiPointTouchArea : public QQuickItem
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(QQuickMultiPointTouchArea)
- Q_PROPERTY(QQmlListProperty<QQuickTouchPoint> touchPoints READ touchPoints)
+ Q_PROPERTY(QQmlListProperty<QQuickTouchPoint> touchPoints READ touchPoints CONSTANT)
Q_PROPERTY(int minimumTouchPoints READ minimumTouchPoints WRITE setMinimumTouchPoints NOTIFY minimumTouchPointsChanged)
Q_PROPERTY(int maximumTouchPoints READ maximumTouchPoints WRITE setMaximumTouchPoints NOTIFY maximumTouchPointsChanged)
Q_PROPERTY(bool mouseEnabled READ mouseEnabled WRITE setMouseEnabled NOTIFY mouseEnabledChanged)
@@ -219,12 +219,23 @@ public:
}
Q_SIGNALS:
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
void pressed(const QList<QObject*> &touchPoints);
void updated(const QList<QObject*> &touchPoints);
void released(const QList<QObject*> &touchPoints);
void canceled(const QList<QObject*> &touchPoints);
+#else
+ void pressed(const QList<QObject*> &points);
+ void updated(const QList<QObject*> &points);
+ void released(const QList<QObject*> &points);
+ void canceled(const QList<QObject*> &points);
+#endif
void gestureStarted(QQuickGrabGestureEvent *gesture);
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
void touchUpdated(const QList<QObject*> &touchPoints);
+#else
+ void touchUpdated(const QList<QObject*> &points);
+#endif
void minimumTouchPointsChanged();
void maximumTouchPointsChanged();
void mouseEnabledChanged();
@@ -252,10 +263,11 @@ protected:
bool shouldFilter(QEvent *event);
void grabGesture(QPointingDevice *dev);
QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *) override;
-#ifdef Q_OS_OSX
+#ifdef Q_OS_MACOS
void hoverEnterEvent(QHoverEvent *event) override;
void hoverLeaveEvent(QHoverEvent *event) override;
void setTouchEventsEnabled(bool enable);
+ void itemChange(ItemChange change, const ItemChangeData &data) override;
#endif
private:
@@ -278,8 +290,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickTouchPoint)
-QML_DECLARE_TYPE(QQuickGrabGestureEvent)
-QML_DECLARE_TYPE(QQuickMultiPointTouchArea)
-
#endif // QQUICKMULTIPOINTTOUCHAREA_H
diff --git a/src/quick/items/qquickpainteditem.cpp b/src/quick/items/qquickpainteditem.cpp
index d363fbcd31..df4ee6014d 100644
--- a/src/quick/items/qquickpainteditem.cpp
+++ b/src/quick/items/qquickpainteditem.cpp
@@ -8,7 +8,7 @@
#include <QtQuick/private/qsgcontext_p.h>
#include <private/qsgadaptationlayer_p.h>
#include <qsgtextureprovider.h>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
#include <qmath.h>
@@ -87,7 +87,6 @@ QQuickPaintedItemPrivate::QQuickPaintedItemPrivate()
, fillColor(Qt::transparent)
, renderTarget(QQuickPaintedItem::Image)
, opaquePainting(false)
- , antialiasing(false)
, mipmap(false)
, textureProvider(nullptr)
, node(nullptr)
@@ -177,6 +176,7 @@ void QQuickPaintedItem::setOpaquePainting(bool opaque)
QQuickItem::update();
}
+//### Qt7: remove the aa functions; they shadow the QQuickItem property
/*!
Returns true if antialiased painting is enabled; otherwise, false is returned.
@@ -186,8 +186,7 @@ void QQuickPaintedItem::setOpaquePainting(bool opaque)
*/
bool QQuickPaintedItem::antialiasing() const
{
- Q_D(const QQuickPaintedItem);
- return d->antialiasing;
+ return QQuickItem::antialiasing();
}
/*!
@@ -199,13 +198,7 @@ bool QQuickPaintedItem::antialiasing() const
*/
void QQuickPaintedItem::setAntialiasing(bool enable)
{
- Q_D(QQuickPaintedItem);
-
- if (d->antialiasing == enable)
- return;
-
- d->antialiasing = enable;
- update();
+ QQuickItem::setAntialiasing(enable);
}
/*!
@@ -416,6 +409,10 @@ void QQuickPaintedItem::setContentsScale(qreal scale)
\brief The item's background fill color.
By default, the fill color is set to Qt::transparent.
+
+ Set the fill color to an invalid color (e.g. QColor()) to disable background
+ filling. This may improve performance, and is safe to do if the paint() function
+ draws to all pixels on each frame.
*/
QColor QQuickPaintedItem::fillColor() const
{
@@ -546,7 +543,7 @@ QSGNode *QQuickPaintedItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeDat
node->setPreferredRenderTarget(d->renderTarget);
node->setFastFBOResizing(d->performanceHints & FastFBOResizing);
- node->setSmoothPainting(d->antialiasing);
+ node->setSmoothPainting(antialiasing());
node->setLinearFiltering(d->smooth);
node->setMipmapping(d->mipmap);
node->setOpaquePainting(d->opaquePainting);
diff --git a/src/quick/items/qquickpainteditem_p.h b/src/quick/items/qquickpainteditem_p.h
index fd677f5519..2695d13da4 100644
--- a/src/quick/items/qquickpainteditem_p.h
+++ b/src/quick/items/qquickpainteditem_p.h
@@ -25,7 +25,7 @@ QT_BEGIN_NAMESPACE
class QQuickPaintedItemTextureProvider;
class QSGPainterNode;
-class Q_QUICK_PRIVATE_EXPORT QQuickPaintedItemPrivate : public QQuickItemPrivate
+class Q_QUICK_EXPORT QQuickPaintedItemPrivate : public QQuickItemPrivate
{
public:
QQuickPaintedItemPrivate();
@@ -40,7 +40,6 @@ public:
QRect dirtyRect;
bool opaquePainting: 1;
- bool antialiasing: 1;
bool mipmap: 1;
mutable QQuickPaintedItemTextureProvider *textureProvider;
diff --git a/src/quick/items/qquickpalette.cpp b/src/quick/items/qquickpalette.cpp
index 07d1e61b59..79b22bd8a0 100644
--- a/src/quick/items/qquickpalette.cpp
+++ b/src/quick/items/qquickpalette.cpp
@@ -33,7 +33,7 @@ static constexpr bool is_valid(QPalette::ColorGroup cg) noexcept
\internal
\class QQuickPalette
- \brief The QQuickPalette class contains color groups for each QML item state.
+ \brief Contains color groups for each QML item state.
\inmodule QtQuick
\since 6.0
@@ -48,10 +48,10 @@ static constexpr bool is_valid(QPalette::ColorGroup cg) noexcept
\inherits QQuickColorGroup
\inqmlmodule QtQuick
\ingroup qtquick-visual
- \brief The QQuickPalette class contains color groups for each QML item state.
+ \brief Contains color groups for each QML item state.
- A palette consists of three color groups: Active, Disabled, and Inactive.
- Active color group is the default group, its colors are used for other groups
+ A palette consists of three color groups: \c active, \c disabled, and \c inactive.
+ The \c active color group is the default group: its colors are used for other groups
if colors of these groups aren't explicitly specified.
In the following example, color is applied for all color groups:
@@ -93,22 +93,23 @@ static constexpr bool is_valid(QPalette::ColorGroup cg) noexcept
\endcode
It is also possible to specify colors like this:
- \code
- palette {
- buttonText: "azure"
- button: "khaki"
- disabled {
- buttonText: "lavender"
- button: "coral"
- }
- }
- \endcode
- This approach is convenient when you need to specify a whole palette with all color groups.
+ \snippet qtquickcontrols-custom-palette-buttons.qml palette
+
+ This approach is especially convenient when you need to specify a whole
+ palette with all color groups; but as with the other cases above, the
+ colors that are not specified are initialized from SystemPalette, or
+ potentially the \l {Styling Qt Quick Controls}{Qt Quick Controls style},
+ if one is in use.
+
+ \note Some Controls styles use some palette colors, but many styles use
+ independent colors.
+
+ \sa Window::palette, Item::palette, Popup::palette, SystemPalette
*/
/*!
- \qmlproperty QQuickColorGroup QtQuick::Palette::active
+ \qmlproperty ColorGroup QtQuick::Palette::active
The Active group is used for windows that are in focus.
@@ -116,7 +117,7 @@ static constexpr bool is_valid(QPalette::ColorGroup cg) noexcept
*/
/*!
- \qmlproperty QQuickColorGroup QtQuick::Palette::inactive
+ \qmlproperty ColorGroup QtQuick::Palette::inactive
The Inactive group is used for windows that have no keyboard focus.
@@ -124,7 +125,7 @@ static constexpr bool is_valid(QPalette::ColorGroup cg) noexcept
*/
/*!
- \qmlproperty QQuickColorGroup QtQuick::Palette::disabled
+ \qmlproperty ColorGroup QtQuick::Palette::disabled
The Disabled group is used for elements that are disabled for some reason.
@@ -152,6 +153,24 @@ QQuickColorGroup *QQuickPalette::disabled() const
return colorGroup(QPalette::Disabled);
}
+void QQuickPalette::resetActive()
+{
+ if (colorProvider().resetColor(QPalette::Active))
+ Q_EMIT changed();
+}
+
+void QQuickPalette::resetInactive()
+{
+ if (colorProvider().resetColor(QPalette::Inactive))
+ Q_EMIT changed();
+}
+
+void QQuickPalette::resetDisabled()
+{
+ if (colorProvider().resetColor(QPalette::Disabled))
+ Q_EMIT changed();
+}
+
/*!
\internal
diff --git a/src/quick/items/qquickpalette_p.h b/src/quick/items/qquickpalette_p.h
index ba93b867cc..3e40cf9036 100644
--- a/src/quick/items/qquickpalette_p.h
+++ b/src/quick/items/qquickpalette_p.h
@@ -16,20 +16,21 @@
#include <QtQuick/private/qquickcolorgroup_p.h>
+#include <QtCore/qpointer.h>
+
#include <array>
QT_BEGIN_NAMESPACE
class QQuickAbstractPaletteProvider;
-class Q_QUICK_PRIVATE_EXPORT QQuickPalette : public QQuickColorGroup
+class Q_QUICK_EXPORT QQuickPalette : public QQuickColorGroup
{
Q_OBJECT
- Q_PROPERTY(QQuickColorGroup *active READ active WRITE setActive NOTIFY activeChanged)
- Q_PROPERTY(QQuickColorGroup *inactive READ inactive WRITE setInactive NOTIFY inactiveChanged)
- Q_PROPERTY(QQuickColorGroup *disabled READ disabled WRITE setDisabled NOTIFY disabledChanged)
-
+ Q_PROPERTY(QQuickColorGroup *active READ active WRITE setActive RESET resetActive NOTIFY activeChanged FINAL)
+ Q_PROPERTY(QQuickColorGroup *inactive READ inactive WRITE setInactive RESET resetInactive NOTIFY inactiveChanged FINAL)
+ Q_PROPERTY(QQuickColorGroup *disabled READ disabled WRITE setDisabled RESET resetDisabled NOTIFY disabledChanged FINAL)
QML_NAMED_ELEMENT(Palette)
QML_ADDED_IN_VERSION(6, 0)
@@ -43,6 +44,9 @@ public:
QQuickColorGroup *active() const;
QQuickColorGroup *inactive() const;
QQuickColorGroup *disabled() const;
+ void resetActive();
+ void resetInactive();
+ void resetDisabled();
QPalette::ColorGroup currentColorGroup() const override;
void setCurrentGroup(QPalette::ColorGroup currentGroup);
@@ -71,6 +75,7 @@ private:
void setColorGroup(QPalette::ColorGroup groupTag,
const QQuickColorGroup::GroupPtr &group,
void (QQuickPalette::*notifier)());
+
QQuickColorGroup::GroupPtr colorGroup(QPalette::ColorGroup groupTag) const;
QQuickColorGroup::GroupPtr findColorGroup(QPalette::ColorGroup groupTag) const;
@@ -88,6 +93,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickPalette)
-
#endif // QQUICKPALETTE_H
diff --git a/src/quick/items/qquickpalettecolorprovider.cpp b/src/quick/items/qquickpalettecolorprovider.cpp
index 2d36ff01c5..f01fa22bdd 100644
--- a/src/quick/items/qquickpalettecolorprovider.cpp
+++ b/src/quick/items/qquickpalettecolorprovider.cpp
@@ -3,6 +3,7 @@
#include "qquickpalettecolorprovider_p.h"
#include <QtQuick/private/qquickabstractpaletteprovider_p.h>
+#include <QtGui/private/qpalette_p.h>
QT_BEGIN_NAMESPACE
@@ -44,10 +45,55 @@ bool QQuickPaletteColorProvider::setColor(QPalette::ColorGroup g, QPalette::Colo
bool QQuickPaletteColorProvider::resetColor(QPalette::ColorGroup group, QPalette::ColorRole role)
{
- const auto &defaultPalette = paletteProvider()->defaultPalette() ;
- const auto &defaultColor = defaultPalette.color(adjustCg(group), role);
+ if (!m_requestedPalette.isAllocated())
+ return false;
- return setColor(group, role, defaultColor);
+ QPalette::ResolveMask unsetResolveMask = 0;
+
+ if (group == QPalette::Current)
+ group = m_requestedPalette->currentColorGroup();
+
+ if (group == QPalette::All) {
+ for (int g = QPalette::Active; g < QPalette::NColorGroups; ++g)
+ unsetResolveMask |= (QPalette::ResolveMask(1) << QPalettePrivate::bitPosition(QPalette::ColorGroup(g), role));
+ } else {
+ unsetResolveMask = (QPalette::ResolveMask(1) << QPalettePrivate::bitPosition(group, role));
+ }
+
+ m_requestedPalette->setResolveMask(m_requestedPalette->resolveMask() & ~unsetResolveMask);
+
+ return updateInheritedPalette();
+}
+
+bool QQuickPaletteColorProvider::resetColor(QPalette::ColorGroup group)
+{
+ if (!m_requestedPalette.isAllocated())
+ return false;
+
+ QPalette::ResolveMask unsetResolveMask = 0;
+
+ auto getResolveMask = [] (QPalette::ColorGroup group) {
+ QPalette::ResolveMask mask = 0;
+ for (int roleIndex = QPalette::WindowText; roleIndex < QPalette::NColorRoles; ++roleIndex) {
+ const auto cr = QPalette::ColorRole(roleIndex);
+ mask |= (QPalette::ResolveMask(1) << QPalettePrivate::bitPosition(group, cr));
+ }
+ return mask;
+ };
+
+ if (group == QPalette::Current)
+ group = m_requestedPalette->currentColorGroup();
+
+ if (group == QPalette::All) {
+ for (int g = QPalette::Active; g < QPalette::NColorGroups; ++g)
+ unsetResolveMask |= getResolveMask(QPalette::ColorGroup(g));
+ } else {
+ unsetResolveMask = getResolveMask(group);
+ }
+
+ m_requestedPalette->setResolveMask(m_requestedPalette->resolveMask() & ~unsetResolveMask);
+
+ return updateInheritedPalette();
}
bool QQuickPaletteColorProvider::fromQPalette(QPalette p)
@@ -116,6 +162,7 @@ bool QQuickPaletteColorProvider::doInheritPalette(const QPalette &palette)
{
auto inheritedMask = m_requestedPalette.isAllocated() ? m_requestedPalette->resolveMask() | palette.resolveMask()
: palette.resolveMask();
+ // If a palette was set on this item, it should always win over the palette to be inherited from.
QPalette parentPalette = m_requestedPalette.isAllocated() ? m_requestedPalette->resolve(palette) : palette;
parentPalette.setResolveMask(inheritedMask);
diff --git a/src/quick/items/qquickpalettecolorprovider_p.h b/src/quick/items/qquickpalettecolorprovider_p.h
index ff715219dc..2a779f20d3 100644
--- a/src/quick/items/qquickpalettecolorprovider_p.h
+++ b/src/quick/items/qquickpalettecolorprovider_p.h
@@ -25,7 +25,7 @@ QT_BEGIN_NAMESPACE
class QQuickAbstractPaletteProvider;
-class Q_QUICK_PRIVATE_EXPORT QQuickPaletteColorProvider
+class Q_QUICK_EXPORT QQuickPaletteColorProvider
: public std::enable_shared_from_this<QQuickPaletteColorProvider>
{
public:
@@ -34,6 +34,7 @@ public:
const QColor &color(QPalette::ColorGroup group, QPalette::ColorRole role) const;
bool setColor(QPalette::ColorGroup group, QPalette::ColorRole role, QColor color);
bool resetColor(QPalette::ColorGroup group, QPalette::ColorRole role);
+ bool resetColor(QPalette::ColorGroup group);
bool fromQPalette(QPalette p);
QPalette palette() const;
diff --git a/src/quick/items/qquickpaletteproviderprivatebase_p.h b/src/quick/items/qquickpaletteproviderprivatebase_p.h
index a0e0302ea9..869e86c954 100644
--- a/src/quick/items/qquickpaletteproviderprivatebase_p.h
+++ b/src/quick/items/qquickpaletteproviderprivatebase_p.h
@@ -221,7 +221,7 @@ void QQuickPaletteProviderPrivateBase<I, Impl>::registerPalette(PalettePtr palet
// In order to avoid extra noise, we should connect
// the following signals only after everything is already setup
I::connect(paletteData(), &QQuickPalette::changed, itemWithPalette(), &I::paletteChanged);
- I::connect(paletteData(), &QQuickPalette::changed, [this]{ updateChildrenPalettes(toQPalette()); });
+ I::connect(paletteData(), &QQuickPalette::changed, itemWithPalette(), [this]{ updateChildrenPalettes(toQPalette()); });
}
template<class T> struct dependent_false : std::false_type {};
@@ -230,9 +230,9 @@ template<class Impl, class I> decltype(auto) getPrivateImpl(I &t) { return Impl:
template <class T>
decltype(auto) getPrivate(T &item)
{
- if constexpr (std::is_same_v<T, QQuickWindow>) {
+ if constexpr (std::is_base_of_v<T, QQuickWindow>) {
return getPrivateImpl<QQuickWindowPrivate>(item);
- } else if constexpr (std::is_same_v<T, QQuickItem>) {
+ } else if constexpr (std::is_base_of_v<T, QQuickItem>) {
return getPrivateImpl<QQuickItemPrivate>(item);
} else {
static_assert (dependent_false<T>::value, "Extend please.");
@@ -257,12 +257,15 @@ template<class I, class Impl>
QPalette QQuickPaletteProviderPrivateBase<I, Impl>::parentPalette(const QPalette &fallbackPalette) const
{
if constexpr (!isRootWindow<I>()) {
- for (auto parentItem = itemWithPalette()->parentItem(); parentItem;
- parentItem = parentItem->parentItem()) {
-
- // Don't allocate a new palette here. Use only if it's already pre allocated
- if (parentItem && getPrivate(*parentItem)->providesPalette()) {
- return getPrivate(*parentItem)->palette()->toQPalette();
+ // Popups should always inherit from their window, even child popups: QTBUG-115707.
+ if (!std::is_base_of_v<QQuickPopup, I>) {
+ for (auto parentItem = itemWithPalette()->parentItem(); parentItem;
+ parentItem = parentItem->parentItem()) {
+
+ // Don't allocate a new palette here. Use only if it's already pre allocated
+ if (parentItem && getPrivate(*parentItem)->providesPalette()) {
+ return getPrivate(*parentItem)->palette()->toQPalette();
+ }
}
}
@@ -279,7 +282,7 @@ const QQuickItem* rootItem(const I &item)
{
if constexpr (isRootWindow<I>()) {
return item.contentItem();
- } else if constexpr (std::is_same_v<QQuickPopup, I>) {
+ } else if constexpr (std::is_base_of_v<QQuickPopup, I>) {
return nullptr;
} else {
return &item;
@@ -342,9 +345,12 @@ void QQuickPaletteProviderPrivateBase<I, Impl>::connectItem()
if constexpr (!isRootWindow<I>()) {
// Item with palette has the same lifetime as its implementation that inherits this class
- I::connect(itemWithPalette(), &I::parentChanged , [this]() { inheritPalette(parentPalette(defaultPalette())); });
- I::connect(itemWithPalette(), &I::windowChanged , [this]() { inheritPalette(parentPalette(defaultPalette())); });
- I::connect(itemWithPalette(), &I::enabledChanged, [this]() { setCurrentColorGroup(); });
+ I::connect(itemWithPalette(), &I::parentChanged,
+ itemWithPalette(), [this]() { inheritPalette(parentPalette(defaultPalette())); });
+ I::connect(itemWithPalette(), &I::windowChanged,
+ itemWithPalette(), [this]() { inheritPalette(parentPalette(defaultPalette())); });
+ I::connect(itemWithPalette(), &I::enabledChanged,
+ itemWithPalette(), [this]() { setCurrentColorGroup(); });
}
}
diff --git a/src/quick/items/qquickpathview.cpp b/src/quick/items/qquickpathview.cpp
index 6de81470c4..3fa4125a1d 100644
--- a/src/quick/items/qquickpathview.cpp
+++ b/src/quick/items/qquickpathview.cpp
@@ -10,7 +10,7 @@
#include <private/qqmlglobal_p.h>
#include <private/qqmlopenmetaobject_p.h>
#include <private/qqmlchangeset_p.h>
-#include <qpa/qplatformintegration.h>
+#include <qpa/qplatformtheme.h>
#include <QtQml/qqmlinfo.h>
@@ -26,10 +26,11 @@
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(lcItemViewDelegateLifecycle)
+#if !QT_CONFIG(quick_itemview)
+Q_LOGGING_CATEGORY(lcItemViewDelegateLifecycle, "qt.quick.itemview.lifecycle")
+#endif
Q_LOGGING_CATEGORY(lcPathView, "qt.quick.pathview")
-static const qreal MinimumFlickVelocity = 75;
-
static QQmlOpenMetaObjectType *qPathViewAttachedType = nullptr;
QQuickPathViewAttached::QQuickPathViewAttached(QObject *parent)
@@ -64,9 +65,9 @@ QQuickPathViewPrivate::QQuickPathViewPrivate()
, moving(false), flicking(false), dragging(false), inRequest(false), delegateValidated(false)
, inRefill(false)
, dragMargin(0), deceleration(100)
- , maximumFlickVelocity(QGuiApplicationPrivate::platformIntegration()->styleHint(QPlatformIntegration::FlickMaximumVelocity).toReal())
+ , maximumFlickVelocity(QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::FlickMaximumVelocity).toReal())
, moveOffset(this, &QQuickPathViewPrivate::setAdjustedOffset), flickDuration(0)
- , pathItems(-1), requestedIndex(-1), cacheSize(0), requestedZ(0)
+ , pathItems(-1), requestedIndex(-1), cacheSize(0), requestedCacheSize(0), requestedZ(0)
, moveReason(Other), movementDirection(QQuickPathView::Shortest), moveDirection(QQuickPathView::Shortest)
, attType(nullptr), highlightComponent(nullptr), highlightItem(nullptr)
, moveHighlight(this, &QQuickPathViewPrivate::setHighlightPosition)
@@ -75,6 +76,7 @@ QQuickPathViewPrivate::QQuickPathViewPrivate()
, highlightRangeMode(QQuickPathView::StrictlyEnforceRange)
, highlightMoveDuration(300), modelCount(0), snapMode(QQuickPathView::NoSnap)
{
+ setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Preferred);
}
void QQuickPathViewPrivate::init()
@@ -207,10 +209,7 @@ QQmlOpenMetaObjectType *QQuickPathViewPrivate::attachedType()
void QQuickPathViewPrivate::clear()
{
- if (currentItem) {
- releaseItem(currentItem);
- currentItem = nullptr;
- }
+ releaseCurrentItem();
for (QQuickItem *p : std::as_const(items))
releaseItem(p);
@@ -231,6 +230,10 @@ void QQuickPathViewPrivate::clear()
void QQuickPathViewPrivate::updateMappedRange()
{
+ // Update the actual cache size to be at max
+ // the available non-visible items.
+ cacheSize = qMax(0, qMin(requestedCacheSize, modelCount - pathItems));
+
if (model && pathItems != -1 && pathItems < modelCount) {
mappedRange = qreal(modelCount)/pathItems;
mappedCache = qreal(cacheSize)/pathItems/2; // Half of cache at each end
@@ -265,9 +268,10 @@ qreal QQuickPathViewPrivate::positionOfIndex(qreal index) const
// returns true if position is between lower and upper, taking into
// account the circular space.
-bool QQuickPathViewPrivate::isInBound(qreal position, qreal lower, qreal upper) const
+bool QQuickPathViewPrivate::isInBound(qreal position, qreal lower,
+ qreal upper, bool emptyRangeCheck) const
{
- if (qFuzzyCompare(lower, upper))
+ if (emptyRangeCheck && qFuzzyCompare(lower, upper))
return true;
if (lower > upper) {
if (position > upper && position > lower)
@@ -530,6 +534,8 @@ QQuickPathView::~QQuickPathView()
/*!
\qmlattachedproperty PathView QtQuick::PathView::view
+ \readonly
+
This attached property holds the view that manages this delegate instance.
It is attached to each instance of the delegate.
@@ -537,6 +543,8 @@ QQuickPathView::~QQuickPathView()
/*!
\qmlattachedproperty bool QtQuick::PathView::onPath
+ \readonly
+
This attached property holds whether the item is currently on the path.
If a pathItemCount has been set, it is possible that some items may
@@ -557,6 +565,8 @@ QQuickPathView::~QQuickPathView()
/*!
\qmlattachedproperty bool QtQuick::PathView::isCurrentItem
+ \readonly
+
This attached property is true if this delegate is the current item; otherwise false.
It is attached to each instance of the delegate.
@@ -725,14 +735,13 @@ void QQuickPathView::setCurrentIndex(int idx)
? ((idx % d->modelCount) + d->modelCount) % d->modelCount
: 0;
if (d->model && (idx != d->currentIndex || !d->currentItem)) {
- if (d->currentItem) {
+ const bool hadCurrentItem = d->currentItem != nullptr;
+ const int oldCurrentIdx = d->currentIndex;
+ if (hadCurrentItem) {
if (QQuickPathViewAttached *att = d->attached(d->currentItem))
att->setIsCurrentItem(false);
- d->releaseItem(d->currentItem);
+ d->releaseCurrentItem();
}
- int oldCurrentIdx = d->currentIndex;
- QQuickItem *oldCurrentItem = d->currentItem;
- d->currentItem = nullptr;
d->moveReason = QQuickPathViewPrivate::SetIndex;
d->currentIndex = idx;
if (d->modelCount) {
@@ -744,7 +753,7 @@ void QQuickPathView::setCurrentIndex(int idx)
}
if (oldCurrentIdx != d->currentIndex)
emit currentIndexChanged();
- if (oldCurrentItem != d->currentItem)
+ if (hadCurrentItem)
emit currentItemChanged();
}
}
@@ -894,18 +903,16 @@ QQuickItem *QQuickPathView::highlightItem() const
Valid values for \c highlightRangeMode are:
- \list
- \li \e PathView.NoHighlightRange - no range is applied and the
- highlight will move freely within the view.
- \li \e PathView.ApplyRange - the view will attempt to maintain
- the highlight within the range, however the highlight can
- move outside of the range at the ends of the path or due to
- a mouse interaction.
- \li \e PathView.StrictlyEnforceRange - the highlight will never
- move outside of the range. This means that the current item
- will change if a keyboard or mouse action would cause the
- highlight to move outside of the range.
- \endlist
+ \value PathView.NoHighlightRange no range is applied: the highlight
+ will move freely within the view.
+ \value PathView.ApplyRange the view will attempt to maintain the highlight
+ within the range, however the highlight can move
+ outside of the range at the ends of the path or
+ due to a mouse interaction.
+ \value PathView.StrictlyEnforceRange the highlight will never move outside of the range.
+ This means that the current item will change if a
+ keyboard or mouse action would cause the highlight
+ to move outside of the range.
The default value is \e PathView.StrictlyEnforceRange.
@@ -1291,16 +1298,16 @@ void QQuickPathView::resetPathItemCount()
int QQuickPathView::cacheItemCount() const
{
Q_D(const QQuickPathView);
- return d->cacheSize;
+ return d->requestedCacheSize;
}
void QQuickPathView::setCacheItemCount(int i)
{
Q_D(QQuickPathView);
- if (i == d->cacheSize || i < 0)
+ if (i == d->requestedCacheSize || i < 0)
return;
- d->cacheSize = i;
+ d->requestedCacheSize = i;
d->updateMappedRange();
refill();
emit cacheItemCountChanged();
@@ -1312,13 +1319,11 @@ void QQuickPathView::setCacheItemCount(int i)
This property determines how the items will settle following a drag or flick.
The possible values are:
- \list
- \li PathView.NoSnap (default) - the items stop anywhere along the path.
- \li PathView.SnapToItem - the items settle with an item aligned with the \l preferredHighlightBegin.
- \li PathView.SnapOneItem - the items settle no more than one item away from the item nearest
+ \value PathView.NoSnap (default) the items stop anywhere along the path.
+ \value PathView.SnapToItem the items settle with an item aligned with the \l preferredHighlightBegin.
+ \value PathView.SnapOneItem the items settle no more than one item away from the item nearest
\l preferredHighlightBegin at the time the press is released. This mode is particularly
useful for moving one page at a time.
- \endlist
\c snapMode does not affect the \l currentIndex. To update the
\l currentIndex as the view is moved, set \l highlightRangeMode
@@ -1348,12 +1353,10 @@ void QQuickPathView::setSnapMode(SnapMode mode)
This property determines the direction in which items move when setting the current index.
The possible values are:
- \list
- \li PathView.Shortest (default) - the items move in the direction that requires the least
- movement, which could be either \c Negative or \c Positive.
- \li PathView.Negative - the items move backwards towards their destination.
- \li PathView.Positive - the items move forwards towards their destination.
- \endlist
+ \value PathView.Shortest (default) the items move in the direction that requires the least
+ movement, which could be either \c Negative or \c Positive.
+ \value PathView.Negative the items move backwards towards their destination.
+ \value PathView.Positive the items move forwards towards their destination.
For example, suppose that there are 5 items in the model, and \l currentIndex is \c 0.
If currentIndex is set to \c 2,
@@ -1389,15 +1392,13 @@ void QQuickPathView::setMovementDirection(QQuickPathView::MovementDirection dir)
Positions the view such that the \a index is at the position specified by
\a mode:
- \list
- \li PathView.Beginning - position item at the beginning of the path.
- \li PathView.Center - position item in the center of the path.
- \li PathView.End - position item at the end of the path.
- \li PathView.Contain - ensure the item is positioned on the path.
- \li PathView.SnapPosition - position the item at \l preferredHighlightBegin. This mode
- is only valid if \l highlightRangeMode is StrictlyEnforceRange or snapping is enabled
- via \l snapMode.
- \endlist
+ \value PathView.Beginning position item at the beginning of the path.
+ \value PathView.Center position item in the center of the path.
+ \value PathView.End position item at the end of the path.
+ \value PathView.Contain ensure the item is positioned on the path.
+ \value PathView.SnapPosition position the item at \l preferredHighlightBegin. This mode
+ is only valid if \l highlightRangeMode is StrictlyEnforceRange or snapping is enabled
+ via \l snapMode.
\b Note: methods should only be called after the Component has completed. To position
the view at startup, this method should be called by Component.onCompleted. For
@@ -1510,7 +1511,7 @@ QQuickItem *QQuickPathView::itemAt(qreal x, qreal y) const
}
/*!
- \qmlmethod Item QtQuick::QQuickPathView::itemAtIndex(int index)
+ \qmlmethod Item QtQuick::PathView::itemAtIndex(int index)
Returns the item for \a index. If there is no item for that index, for example
because it has not been created yet, or because it has been panned out of
@@ -1536,6 +1537,14 @@ QQuickItem *QQuickPathView::itemAtIndex(int index) const
return nullptr;
}
+/*!
+ \internal
+
+ Returns a point in the path, that has the closest distance from \a point.
+ A value in the range 0-1 will be written to \a nearPercent if given, which
+ represents where on the path the \a point is closest to. \c 0 means the very
+ beginning of the path, and \c 1 means the very end.
+*/
QPointF QQuickPathViewPrivate::pointNear(const QPointF &point, qreal *nearPercent) const
{
const auto pathLength = path->path().length();
@@ -1751,7 +1760,7 @@ void QQuickPathViewPrivate::handleMouseReleaseEvent(QMouseEvent *event)
qreal count = pathItems == -1 ? modelCount : qMin(pathItems, modelCount);
const auto averageItemLength = path->path().length() / count;
qreal pixelVelocity = averageItemLength * velocity;
- if (qAbs(pixelVelocity) > MinimumFlickVelocity) {
+ if (qAbs(pixelVelocity) > _q_MinimumFlickVelocity) {
if (qAbs(pixelVelocity) > maximumFlickVelocity || snapMode == QQuickPathView::SnapOneItem) {
// limit velocity
qreal maxVel = velocity < 0 ? -maximumFlickVelocity : maximumFlickVelocity;
@@ -1855,7 +1864,8 @@ bool QQuickPathView::childMouseEventFilter(QQuickItem *i, QEvent *e)
const bool filtered = stealThisEvent || grabberDisabled;
if (filtered)
- pe->setAccepted(false);
+ pe->setAccepted(stealThisEvent && grabber == this && grabber->isEnabled());
+
return filtered;
} else if (d->timer.isValid()) {
d->timer.invalidate();
@@ -2011,6 +2021,7 @@ void QQuickPathView::refill()
startPos = d->highlightRangeStart;
// With no items, then "end" is just off the top so we populate via append
endIdx = (qRound(d->modelCount - d->offset) - 1) % d->modelCount;
+ endIdx = qMax(-1, endIdx); // endIdx shouldn't be smaller than -1
endPos = d->positionOfIndex(endIdx);
}
//Append
@@ -2018,8 +2029,8 @@ void QQuickPathView::refill()
if (idx >= d->modelCount)
idx = 0;
qreal nextPos = d->positionOfIndex(idx);
- while ((d->isInBound(nextPos, endPos, 1 + d->mappedCache) || !d->items.size())
- && d->items.size() < count+d->cacheSize) {
+ while ((d->isInBound(nextPos, endPos, 1 + d->mappedCache, false) || !d->items.size())
+ && d->items.size() < count + d->cacheSize) {
qCDebug(lcItemViewDelegateLifecycle) << "append" << idx << "@" << nextPos << (d->currentIndex == idx ? "current" : "") << "items count was" << d->items.size();
QQuickItem *item = d->getItem(idx, idx+1, nextPos >= 1);
if (!item) {
@@ -2193,8 +2204,7 @@ void QQuickPathView::modelUpdated(const QQmlChangeSet &changeSet, bool reset)
} else if (d->currentItem) {
if (QQuickPathViewAttached *att = d->attached(d->currentItem))
att->setIsCurrentItem(true);
- d->releaseItem(d->currentItem);
- d->currentItem = nullptr;
+ d->releaseCurrentItem();
}
d->currentIndex = qMin(r.index, d->modelCount - r.count - 1);
currentChanged = true;
@@ -2342,7 +2352,7 @@ void QQuickPathViewPrivate::updateCurrent()
if (currentItem) {
if (QQuickPathViewAttached *att = attached(currentItem))
att->setIsCurrentItem(false);
- releaseItem(currentItem);
+ releaseCurrentItem();
}
int oldCurrentIndex = currentIndex;
currentIndex = idx;
diff --git a/src/quick/items/qquickpathview_p.h b/src/quick/items/qquickpathview_p.h
index da207bbf29..187ebad65a 100644
--- a/src/quick/items/qquickpathview_p.h
+++ b/src/quick/items/qquickpathview_p.h
@@ -30,7 +30,7 @@ class QQmlChangeSet;
class QQuickPathViewPrivate;
class QQuickPathViewAttached;
-class Q_QUICK_PRIVATE_EXPORT QQuickPathView : public QQuickItem
+class Q_QUICK_EXPORT QQuickPathView : public QQuickItem
{
Q_OBJECT
@@ -218,9 +218,9 @@ class QQuickPathViewAttached : public QObject
{
Q_OBJECT
- Q_PROPERTY(QQuickPathView *view READ view CONSTANT)
- Q_PROPERTY(bool isCurrentItem READ isCurrentItem NOTIFY currentItemChanged)
- Q_PROPERTY(bool onPath READ isOnPath NOTIFY pathChanged)
+ Q_PROPERTY(QQuickPathView *view READ view CONSTANT FINAL)
+ Q_PROPERTY(bool isCurrentItem READ isCurrentItem NOTIFY currentItemChanged FINAL)
+ Q_PROPERTY(bool onPath READ isOnPath NOTIFY pathChanged FINAL)
public:
QQuickPathViewAttached(QObject *parent);
@@ -264,6 +264,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickPathView)
-
#endif // QQUICKPATHVIEW_P_H
diff --git a/src/quick/items/qquickpathview_p_p.h b/src/quick/items/qquickpathview_p_p.h
index 61c0b2ce62..f23b03f6c2 100644
--- a/src/quick/items/qquickpathview_p_p.h
+++ b/src/quick/items/qquickpathview_p_p.h
@@ -31,6 +31,8 @@ QT_REQUIRE_CONFIG(quick_pathview);
#include <private/qquicktimeline_p_p.h>
#include <private/qpodvector_p.h>
+#include <QtCore/qpointer.h>
+
QT_BEGIN_NAMESPACE
class QQmlOpenMetaObjectType;
@@ -67,13 +69,18 @@ public:
}
QQuickItem *getItem(int modelIndex, qreal z = 0, bool async=false);
+ void releaseCurrentItem()
+ {
+ auto oldCurrentItem = std::exchange(currentItem, nullptr);
+ releaseItem(oldCurrentItem);
+ }
void releaseItem(QQuickItem *item);
QQuickPathViewAttached *attached(QQuickItem *item);
QQmlOpenMetaObjectType *attachedType();
void clear();
void updateMappedRange();
qreal positionOfIndex(qreal index) const;
- bool isInBound(qreal position, qreal lower, qreal upper) const;
+ bool isInBound(qreal position, qreal lower, qreal upper, bool emptyRangeCheck = true) const;
void createHighlight();
void updateHighlight();
void setHighlightPosition(qreal pos);
@@ -138,6 +145,7 @@ public:
int pathItems;
int requestedIndex;
int cacheSize;
+ int requestedCacheSize;
qreal requestedZ;
QList<QQuickItem *> items;
QList<QQuickItem *> itemCache;
diff --git a/src/quick/items/qquickpincharea.cpp b/src/quick/items/qquickpincharea.cpp
index d547fae6c9..482941b9ba 100644
--- a/src/quick/items/qquickpincharea.cpp
+++ b/src/quick/items/qquickpincharea.cpp
@@ -247,7 +247,7 @@ QQuickPinchArea::QQuickPinchArea(QQuickItem *parent)
Q_D(QQuickPinchArea);
d->init();
setAcceptTouchEvents(true);
-#ifdef Q_OS_OSX
+#ifdef Q_OS_MACOS
setAcceptHoverEvents(true); // needed to enable touch events on mouse hover.
#endif
}
diff --git a/src/quick/items/qquickpincharea_p.h b/src/quick/items/qquickpincharea_p.h
index 5e08aa3eeb..9ea16f3ea4 100644
--- a/src/quick/items/qquickpincharea_p.h
+++ b/src/quick/items/qquickpincharea_p.h
@@ -21,7 +21,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QQuickPinch : public QObject
+class Q_QUICK_EXPORT QQuickPinch : public QObject
{
Q_OBJECT
@@ -160,24 +160,24 @@ private:
bool m_active;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickPinchEvent : public QObject
+class Q_QUICK_EXPORT QQuickPinchEvent : public QObject
{
Q_OBJECT
- Q_PROPERTY(QPointF center READ center)
- Q_PROPERTY(QPointF startCenter READ startCenter)
- Q_PROPERTY(QPointF previousCenter READ previousCenter)
- Q_PROPERTY(qreal scale READ scale)
- Q_PROPERTY(qreal previousScale READ previousScale)
- Q_PROPERTY(qreal angle READ angle)
- Q_PROPERTY(qreal previousAngle READ previousAngle)
- Q_PROPERTY(qreal rotation READ rotation)
- Q_PROPERTY(QPointF point1 READ point1)
- Q_PROPERTY(QPointF startPoint1 READ startPoint1)
- Q_PROPERTY(QPointF point2 READ point2)
- Q_PROPERTY(QPointF startPoint2 READ startPoint2)
- Q_PROPERTY(int pointCount READ pointCount)
- Q_PROPERTY(bool accepted READ accepted WRITE setAccepted)
+ Q_PROPERTY(QPointF center READ center FINAL)
+ Q_PROPERTY(QPointF startCenter READ startCenter FINAL)
+ Q_PROPERTY(QPointF previousCenter READ previousCenter FINAL)
+ Q_PROPERTY(qreal scale READ scale FINAL)
+ Q_PROPERTY(qreal previousScale READ previousScale FINAL)
+ Q_PROPERTY(qreal angle READ angle FINAL)
+ Q_PROPERTY(qreal previousAngle READ previousAngle FINAL)
+ Q_PROPERTY(qreal rotation READ rotation FINAL)
+ Q_PROPERTY(QPointF point1 READ point1 FINAL)
+ Q_PROPERTY(QPointF startPoint1 READ startPoint1 FINAL)
+ Q_PROPERTY(QPointF point2 READ point2 FINAL)
+ Q_PROPERTY(QPointF startPoint2 READ startPoint2 FINAL)
+ Q_PROPERTY(int pointCount READ pointCount FINAL)
+ Q_PROPERTY(bool accepted READ accepted WRITE setAccepted FINAL)
QML_ANONYMOUS
QML_ADDED_IN_VERSION(2, 0)
@@ -230,9 +230,8 @@ private:
};
-class QQuickMouseEvent;
class QQuickPinchAreaPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickPinchArea : public QQuickItem
+class Q_QUICK_EXPORT QQuickPinchArea : public QQuickItem
{
Q_OBJECT
@@ -278,9 +277,5 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickPinch)
-QML_DECLARE_TYPE(QQuickPinchEvent)
-QML_DECLARE_TYPE(QQuickPinchArea)
-
#endif // QQUICKPINCHAREA_H
diff --git a/src/quick/items/qquickpositioners.cpp b/src/quick/items/qquickpositioners.cpp
index 5bb1f4f6d9..ad5447df6f 100644
--- a/src/quick/items/qquickpositioners.cpp
+++ b/src/quick/items/qquickpositioners.cpp
@@ -12,7 +12,7 @@
QT_BEGIN_NAMESPACE
-static const QQuickItemPrivate::ChangeTypes watchedChanges
+static const QQuickItemPrivate::ChangeTypes positionerWatchedChanges
= QQuickItemPrivate::Geometry
| QQuickItemPrivate::SiblingOrder
| QQuickItemPrivate::Visibility
@@ -21,19 +21,21 @@ static const QQuickItemPrivate::ChangeTypes watchedChanges
void QQuickBasePositionerPrivate::watchChanges(QQuickItem *other)
{
QQuickItemPrivate *otherPrivate = QQuickItemPrivate::get(other);
- otherPrivate->addItemChangeListener(this, watchedChanges);
+ otherPrivate->addItemChangeListener(this, positionerWatchedChanges);
}
void QQuickBasePositionerPrivate::unwatchChanges(QQuickItem* other)
{
QQuickItemPrivate *otherPrivate = QQuickItemPrivate::get(other);
- otherPrivate->removeItemChangeListener(this, watchedChanges);
+ otherPrivate->removeItemChangeListener(this, positionerWatchedChanges);
}
QQuickBasePositioner::PositionedItem::PositionedItem(QQuickItem *i)
: item(i)
+#if QT_CONFIG(quick_viewtransitions)
, transitionableItem(nullptr)
+#endif
, index(-1)
, isNew(false)
, isVisible(true)
@@ -46,27 +48,40 @@ QQuickBasePositioner::PositionedItem::PositionedItem(QQuickItem *i)
QQuickBasePositioner::PositionedItem::~PositionedItem()
{
+#if QT_CONFIG(quick_viewtransitions)
delete transitionableItem;
+#endif
}
qreal QQuickBasePositioner::PositionedItem::itemX() const
{
- return transitionableItem ? transitionableItem->itemX() : item->x();
+ return
+#if QT_CONFIG(quick_viewtransitions)
+ transitionableItem ? transitionableItem->itemX() :
+#endif
+ item->x();
}
qreal QQuickBasePositioner::PositionedItem::itemY() const
{
- return transitionableItem ? transitionableItem->itemY() : item->y();
+ return
+#if QT_CONFIG(quick_viewtransitions)
+ transitionableItem ? transitionableItem->itemY() :
+#endif
+ item->y();
}
void QQuickBasePositioner::PositionedItem::moveTo(const QPointF &pos)
{
+#if QT_CONFIG(quick_viewtransitions)
if (transitionableItem)
transitionableItem->moveTo(pos);
else
+#endif
item->setPosition(pos);
}
+#if QT_CONFIG(quick_viewtransitions)
void QQuickBasePositioner::PositionedItem::transitionNextReposition(QQuickItemViewTransitioner *transitioner, QQuickItemViewTransitioner::TransitionType type, bool asTarget)
{
if (!transitioner)
@@ -86,6 +101,7 @@ void QQuickBasePositioner::PositionedItem::startTransition(QQuickItemViewTransit
if (transitionableItem)
transitionableItem->startTransition(transitioner, index);
}
+#endif
void QQuickBasePositioner::PositionedItem::updatePadding(qreal lp, qreal tp, qreal rp, qreal bp)
{
@@ -132,7 +148,9 @@ QQuickBasePositioner::QQuickBasePositioner(QQuickBasePositionerPrivate &dd, Posi
QQuickBasePositioner::~QQuickBasePositioner()
{
Q_D(QQuickBasePositioner);
+#if QT_CONFIG(quick_viewtransitions)
delete d->transitioner;
+#endif
for (int i = 0; i < positionedItems.count(); ++i)
d->unwatchChanges(positionedItems.at(i).item);
for (int i = 0; i < unpositionedItems.count(); ++i)
@@ -164,6 +182,7 @@ void QQuickBasePositioner::setSpacing(qreal s)
emit spacingChanged();
}
+#if QT_CONFIG(quick_viewtransitions)
QQuickTransition *QQuickBasePositioner::populate() const
{
Q_D(const QQuickBasePositioner);
@@ -216,17 +235,24 @@ void QQuickBasePositioner::setAdd(QQuickTransition *add)
d->transitioner->addTransition = add;
emit addChanged();
}
+#endif
void QQuickBasePositioner::componentComplete()
{
+#if QT_CONFIG(quick_viewtransitions)
Q_D(QQuickBasePositioner);
+#endif
QQuickItem::componentComplete();
+#if QT_CONFIG(quick_viewtransitions)
if (d->transitioner)
d->transitioner->setPopulateTransitionEnabled(true);
+#endif
positionedItems.reserve(childItems().size());
prePositioning();
+#if QT_CONFIG(quick_viewtransitions)
if (d->transitioner)
d->transitioner->setPopulateTransitionEnabled(false);
+#endif
}
void QQuickBasePositioner::itemChange(ItemChange change, const ItemChangeData &value)
@@ -275,7 +301,9 @@ void QQuickBasePositioner::prePositioning()
for (int ii = 0; ii < unpositionedItems.count(); ii++)
oldItems.append(unpositionedItems[ii]);
unpositionedItems.clear();
+#if QT_CONFIG(quick_viewtransitions)
int addedIndex = -1;
+#endif
for (int ii = 0; ii < children.size(); ++ii) {
QQuickItem *child = children.at(ii);
@@ -295,6 +323,7 @@ void QQuickBasePositioner::prePositioning()
posItem.index = positionedItems.count();
positionedItems.append(posItem);
+#if QT_CONFIG(quick_viewtransitions)
if (d->transitioner) {
if (addedIndex < 0)
addedIndex = posItem.index;
@@ -304,6 +333,7 @@ void QQuickBasePositioner::prePositioning()
else if (!d->transitioner->populateTransitionEnabled())
theItem->transitionNextReposition(d->transitioner, QQuickItemViewTransitioner::AddTransition, true);
}
+#endif
}
} else {
PositionedItem *item = &oldItems[wIdx];
@@ -320,11 +350,13 @@ void QQuickBasePositioner::prePositioning()
item->index = positionedItems.count();
positionedItems.append(*item);
+#if QT_CONFIG(quick_viewtransitions)
if (d->transitioner) {
if (addedIndex < 0)
addedIndex = item->index;
positionedItems[positionedItems.count()-1].transitionNextReposition(d->transitioner, QQuickItemViewTransitioner::AddTransition, true);
}
+#endif
} else {
item->isNew = false;
item->index = positionedItems.count();
@@ -333,6 +365,7 @@ void QQuickBasePositioner::prePositioning()
}
}
+#if QT_CONFIG(quick_viewtransitions)
if (d->transitioner) {
for (int i=0; i<positionedItems.count(); i++) {
if (!positionedItems[i].isNew) {
@@ -346,6 +379,7 @@ void QQuickBasePositioner::prePositioning()
}
}
}
+#endif
QSizeF contentSize(0,0);
reportConflictingAnchors();
@@ -354,6 +388,7 @@ void QQuickBasePositioner::prePositioning()
updateAttachedProperties();
}
+#if QT_CONFIG(quick_viewtransitions)
if (d->transitioner) {
QRectF viewBounds(QPointF(), contentSize);
for (int i=0; i<positionedItems.count(); i++)
@@ -362,6 +397,7 @@ void QQuickBasePositioner::prePositioning()
positionedItems[i].startTransition(d->transitioner);
d->transitioner->resetTargetLists();
}
+#endif
d->doingPositioning = false;
@@ -403,13 +439,17 @@ void QQuickBasePositioner::positionItemY(qreal y, PositionedItem *target)
void QQuickBasePositioner::removePositionedItem(QPODVector<PositionedItem,8> *items, int index)
{
Q_ASSERT(index >= 0 && index < items->count());
+#if QT_CONFIG(quick_viewtransitions)
delete items->at(index).transitionableItem;
+#endif
items->remove(index);
}
void QQuickBasePositioner::clearPositionedItems(QPODVector<PositionedItem,8> *items)
{
+#if QT_CONFIG(quick_viewtransitions)
for (int i=0; i<items->count(); i++)
delete items->at(i).transitionableItem;
+#endif
items->clear();
}
@@ -1111,14 +1151,12 @@ QQuickRow::QQuickRow(QQuickItem *parent)
Possible values:
- \list
- \li Qt.LeftToRight (default) - Items are laid out from left to right. If the width of the row is explicitly set,
- the left anchor remains to the left of the row.
- \li Qt.RightToLeft - Items are laid out from right to left. If the width of the row is explicitly set,
- the right anchor remains to the right of the row.
- \endlist
+ \value Qt.LeftToRight (default) Items are laid out from left to right. If the width of the row is
+ explicitly set, the left anchor remains to the left of the row.
+ \value Qt.RightToLeft Items are laid out from right to left. If the width of the row is
+ explicitly set, the right anchor remains to the right of the row.
- \sa Grid::layoutDirection, Flow::layoutDirection, {Qt Quick Examples - Right to Left}
+ \sa Grid::layoutDirection, Flow::layoutDirection
*/
Qt::LayoutDirection QQuickRow::layoutDirection() const
@@ -1531,7 +1569,7 @@ void QQuickGrid::setColumnSpacing(const qreal columnSpacing)
\l Grid::flow property.
\endlist
- \sa Flow::layoutDirection, Row::layoutDirection, {Qt Quick Examples - Right to Left}
+ \sa Flow::layoutDirection, Row::layoutDirection
*/
Qt::LayoutDirection QQuickGrid::layoutDirection() const
{
@@ -2031,7 +2069,7 @@ void QQuickFlow::setFlow(Flow flow)
\l Flow::flow property.
\endlist
- \sa Grid::layoutDirection, Row::layoutDirection, {Qt Quick Examples - Right to Left}
+ \sa Grid::layoutDirection, Row::layoutDirection
*/
Qt::LayoutDirection QQuickFlow::layoutDirection() const
diff --git a/src/quick/items/qquickpositioners_p.h b/src/quick/items/qquickpositioners_p.h
index 42ab3bc6a7..02a368882f 100644
--- a/src/quick/items/qquickpositioners_p.h
+++ b/src/quick/items/qquickpositioners_p.h
@@ -20,7 +20,9 @@
QT_REQUIRE_CONFIG(quick_positioners);
#include "qquickimplicitsizeitem_p.h"
+#if QT_CONFIG(quick_viewtransitions)
#include "qquickitemviewtransition_p.h"
+#endif
#include <private/qpodvector_p.h>
@@ -38,9 +40,9 @@ class QQuickPositionerAttached : public QObject
public:
QQuickPositionerAttached(QObject *parent);
- Q_PROPERTY(int index READ index NOTIFY indexChanged)
- Q_PROPERTY(bool isFirstItem READ isFirstItem NOTIFY isFirstItemChanged)
- Q_PROPERTY(bool isLastItem READ isLastItem NOTIFY isLastItemChanged)
+ Q_PROPERTY(int index READ index NOTIFY indexChanged FINAL)
+ Q_PROPERTY(bool isFirstItem READ isFirstItem NOTIFY isFirstItemChanged FINAL)
+ Q_PROPERTY(bool isLastItem READ isLastItem NOTIFY isLastItemChanged FINAL)
int index() const { return m_index; }
void setIndex(int index);
@@ -62,14 +64,16 @@ private:
bool m_isLastItem;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickBasePositioner : public QQuickImplicitSizeItem
+class Q_QUICK_EXPORT QQuickBasePositioner : public QQuickImplicitSizeItem
{
Q_OBJECT
Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged)
+#if QT_CONFIG(quick_viewtransitions)
Q_PROPERTY(QQuickTransition *populate READ populate WRITE setPopulate NOTIFY populateChanged)
Q_PROPERTY(QQuickTransition *move READ move WRITE setMove NOTIFY moveChanged)
Q_PROPERTY(QQuickTransition *add READ add WRITE setAdd NOTIFY addChanged)
+#endif
Q_PROPERTY(qreal padding READ padding WRITE setPadding RESET resetPadding NOTIFY paddingChanged REVISION(2, 6))
Q_PROPERTY(qreal topPadding READ topPadding WRITE setTopPadding RESET resetTopPadding NOTIFY topPaddingChanged REVISION(2, 6))
@@ -91,6 +95,7 @@ public:
qreal spacing() const;
void setSpacing(qreal);
+#if QT_CONFIG(quick_viewtransitions)
QQuickTransition *populate() const;
void setPopulate(QQuickTransition *);
@@ -99,6 +104,7 @@ public:
QQuickTransition *add() const;
void setAdd(QQuickTransition *);
+#endif
static QQuickPositionerAttached *qmlAttachedProperties(QObject *obj);
@@ -164,14 +170,18 @@ protected:
void moveTo(const QPointF &pos);
+#if QT_CONFIG(quick_viewtransitions)
void transitionNextReposition(QQuickItemViewTransitioner *transitioner, QQuickItemViewTransitioner::TransitionType type, bool asTarget);
bool prepareTransition(QQuickItemViewTransitioner *transitioner, const QRectF &viewBounds);
void startTransition(QQuickItemViewTransitioner *transitioner);
+#endif
void updatePadding(qreal lp, qreal tp, qreal rp, qreal bp);
QQuickItem *item;
+#if QT_CONFIG(quick_viewtransitions)
QQuickItemViewTransitionableItem *transitionableItem;
+#endif
int index;
bool isNew;
bool isVisible;
@@ -197,7 +207,7 @@ private:
Q_DECLARE_PRIVATE(QQuickBasePositioner)
};
-class Q_QUICK_PRIVATE_EXPORT QQuickColumn : public QQuickBasePositioner
+class Q_QUICK_EXPORT QQuickColumn : public QQuickBasePositioner
{
Q_OBJECT
QML_NAMED_ELEMENT(Column)
@@ -211,7 +221,7 @@ protected:
};
class QQuickRowPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickRow: public QQuickBasePositioner
+class Q_QUICK_EXPORT QQuickRow: public QQuickBasePositioner
{
Q_OBJECT
Q_PROPERTY(Qt::LayoutDirection layoutDirection READ layoutDirection WRITE setLayoutDirection NOTIFY layoutDirectionChanged)
@@ -238,7 +248,7 @@ private:
};
class QQuickGridPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickGrid : public QQuickBasePositioner
+class Q_QUICK_EXPORT QQuickGrid : public QQuickBasePositioner
{
Q_OBJECT
Q_PROPERTY(int rows READ rows WRITE setRows NOTIFY rowsChanged)
@@ -326,7 +336,7 @@ private:
};
class QQuickFlowPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickFlow: public QQuickBasePositioner
+class Q_QUICK_EXPORT QQuickFlow: public QQuickBasePositioner
{
Q_OBJECT
Q_PROPERTY(Flow flow READ flow WRITE setFlow NOTIFY flowChanged)
@@ -360,14 +370,6 @@ private:
Q_DECLARE_PRIVATE(QQuickFlow)
};
-
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickColumn)
-QML_DECLARE_TYPE(QQuickRow)
-QML_DECLARE_TYPE(QQuickGrid)
-QML_DECLARE_TYPE(QQuickFlow)
-
-QML_DECLARE_TYPE(QQuickBasePositioner)
-
#endif // QQUICKPOSITIONERS_P_H
diff --git a/src/quick/items/qquickpositioners_p_p.h b/src/quick/items/qquickpositioners_p_p.h
index f91b093684..004a0f5ed1 100644
--- a/src/quick/items/qquickpositioners_p_p.h
+++ b/src/quick/items/qquickpositioners_p_p.h
@@ -54,7 +54,10 @@ public:
QQuickBasePositionerPrivate()
: spacing(0), type(QQuickBasePositioner::None)
- , transitioner(0), positioningDirty(false)
+#if QT_CONFIG(quick_viewtransitions)
+ , transitioner(0)
+#endif
+ , positioningDirty(false)
, doingPositioning(false), anchorConflict(false), layoutDirection(Qt::LeftToRight)
{
@@ -68,7 +71,9 @@ public:
qreal spacing;
QQuickBasePositioner::PositionerType type;
+#if QT_CONFIG(quick_viewtransitions)
QQuickItemViewTransitioner *transitioner;
+#endif
void watchChanges(QQuickItem *other);
void unwatchChanges(QQuickItem* other);
diff --git a/src/quick/items/qquickrectangle.cpp b/src/quick/items/qquickrectangle.cpp
index 7037526264..403d0c1034 100644
--- a/src/quick/items/qquickrectangle.cpp
+++ b/src/quick/items/qquickrectangle.cpp
@@ -229,10 +229,9 @@ QQmlListProperty<QQuickGradientStop> QQuickGradient::stops()
\since 5.12
Set this property to define the direction of the gradient.
- \list
- \li Gradient.Vertical - a vertical gradient
- \li Gradient.Horizontal - a horizontal gradient
- \endlist
+
+ \value Gradient.Vertical a vertical gradient
+ \value Gradient.Horizontal a horizontal gradient
The default is Gradient.Vertical.
*/
@@ -265,6 +264,17 @@ void QQuickGradient::doUpdate()
int QQuickRectanglePrivate::doUpdateSlotIdx = -1;
+void QQuickRectanglePrivate::maybeSetImplicitAntialiasing()
+{
+ bool implicitAA = (radius != 0);
+ if (extraRectangle.isAllocated() && !implicitAA) {
+ implicitAA = extraRectangle.value().topLeftRadius > 0.0
+ || extraRectangle.value().topRightRadius > 0.0
+ || extraRectangle.value().bottomLeftRadius > 0.0
+ || extraRectangle.value().bottomRightRadius > 0.0;
+ }
+ setImplicitAntialiasing(implicitAA);
+}
/*!
\qmltype Rectangle
\instantiates QQuickRectangle
@@ -289,7 +299,10 @@ int QQuickRectanglePrivate::doUpdateSlotIdx = -1;
You can also create rounded rectangles using the \l radius property. Since this
introduces curved edges to the corners of a rectangle, it may be appropriate to
- set the \l Item::antialiasing property to improve its appearance.
+ set the \l Item::antialiasing property to improve its appearance. To set the
+ radii individually for different corners, you can use the properties
+ \l topLeftRadius, \l topRightRadius, \l bottomLeftRadius and
+ \l bottomRightRadius.
\section1 Example Usage
@@ -340,6 +353,7 @@ void QQuickRectangle::doUpdate()
\qmlpropertygroup QtQuick::Rectangle::border
\qmlproperty int QtQuick::Rectangle::border.width
\qmlproperty color QtQuick::Rectangle::border.color
+ \qmlproperty bool QtQuick::Rectangle::border.pixelAligned
The width and color used to draw the border of the rectangle.
@@ -349,6 +363,10 @@ void QQuickRectangle::doUpdate()
rectangle itself or its position relative to other items if anchors are used.
The border is rendered within the rectangle's boundaries.
+
+ If \c pixelAligned is \c true (the default), the rendered border width is rounded to a whole
+ number of pixels, after device pixel ratio scaling. Setting \c pixelAligned to \c false will
+ allow fractional border widths, which may be desirable when \c antialiasing is enabled.
*/
QQuickPen *QQuickRectangle::border()
{
@@ -463,9 +481,12 @@ void QQuickRectangle::resetGradient()
\qmlproperty real QtQuick::Rectangle::radius
This property holds the corner radius used to draw a rounded rectangle.
- If radius is non-zero, the rectangle will be painted as a rounded rectangle, otherwise it will be
- painted as a normal rectangle. The same radius is used by all 4 corners; there is currently
- no way to specify different radii for different corners.
+ If radius is non-zero, the rectangle will be painted as a rounded rectangle,
+ otherwise it will be painted as a normal rectangle. Individual corner radii
+ can be set as well (see below). These values will override \l radius. If
+ they are unset (by setting them to \c undefined), \l radius will be used instead.
+
+ \sa topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius
*/
qreal QQuickRectangle::radius() const
{
@@ -480,10 +501,240 @@ void QQuickRectangle::setRadius(qreal radius)
return;
d->radius = radius;
- d->setImplicitAntialiasing(radius != 0.0);
+ d->maybeSetImplicitAntialiasing();
update();
emit radiusChanged();
+
+ if (d->extraRectangle.isAllocated()) {
+ if (d->extraRectangle->topLeftRadius < 0.)
+ emit topLeftRadiusChanged();
+ if (d->extraRectangle->topRightRadius < 0.)
+ emit topRightRadiusChanged();
+ if (d->extraRectangle->bottomLeftRadius < 0.)
+ emit bottomLeftRadiusChanged();
+ if (d->extraRectangle->bottomRightRadius < 0.)
+ emit bottomRightRadiusChanged();
+ } else {
+ emit topLeftRadiusChanged();
+ emit topRightRadiusChanged();
+ emit bottomLeftRadiusChanged();
+ emit bottomRightRadiusChanged();
+ }
+}
+
+/*!
+ \since 6.7
+ \qmlproperty real QtQuick::Rectangle::topLeftRadius
+ This property holds the radius used to draw the top left corner.
+
+ If \l topLeftRadius is not set, \l radius will be used instead.
+ If \l topLeftRadius is zero, the corner will be sharp.
+
+ \note This API is considered tech preview and may change or be removed in
+ future versions of Qt.
+
+ \sa radius, topRightRadius, bottomLeftRadius, bottomRightRadius
+*/
+qreal QQuickRectangle::topLeftRadius() const
+{
+ Q_D(const QQuickRectangle);
+ if (d->extraRectangle.isAllocated() && d->extraRectangle->topLeftRadius >= 0.)
+ return d->extraRectangle.value().topLeftRadius;
+ return d->radius;
+}
+
+void QQuickRectangle::setTopLeftRadius(qreal radius)
+{
+ Q_D(QQuickRectangle);
+ if (d->extraRectangle.value().topLeftRadius == radius)
+ return;
+
+ if (radius < 0) { // use the fact that radius < 0 resets the radius.
+ qmlWarning(this) << "topLeftRadius (" << radius << ") cannot be less than 0.";
+ return;
+ }
+ d->extraRectangle.value().topLeftRadius = radius;
+ d->maybeSetImplicitAntialiasing();
+
+ update();
+ emit topLeftRadiusChanged();
+}
+
+void QQuickRectangle::resetTopLeftRadius()
+{
+ Q_D(QQuickRectangle);
+ if (!d->extraRectangle.isAllocated())
+ return;
+ if (d->extraRectangle.value().topLeftRadius < 0)
+ return;
+
+ d->extraRectangle.value().topLeftRadius = -1.;
+ d->maybeSetImplicitAntialiasing();
+
+ update();
+ emit topLeftRadiusChanged();
+}
+
+/*!
+ \since 6.7
+ \qmlproperty real QtQuick::Rectangle::topRightRadius
+ This property holds the radius used to draw the top right corner.
+
+ If \l topRightRadius is not set, \l radius will be used instead.
+ If \l topRightRadius is zero, the corner will be sharp.
+
+ \note This API is considered tech preview and may change or be removed in
+ future versions of Qt.
+
+ \sa radius, topLeftRadius, bottomLeftRadius, bottomRightRadius
+*/
+qreal QQuickRectangle::topRightRadius() const
+{
+ Q_D(const QQuickRectangle);
+ if (d->extraRectangle.isAllocated() && d->extraRectangle->topRightRadius >= 0.)
+ return d->extraRectangle.value().topRightRadius;
+ return d->radius;
+}
+
+void QQuickRectangle::setTopRightRadius(qreal radius)
+{
+ Q_D(QQuickRectangle);
+ if (d->extraRectangle.value().topRightRadius == radius)
+ return;
+
+ if (radius < 0) { // use the fact that radius < 0 resets the radius.
+ qmlWarning(this) << "topRightRadius (" << radius << ") cannot be less than 0.";
+ return;
+ }
+ d->extraRectangle.value().topRightRadius = radius;
+ d->maybeSetImplicitAntialiasing();
+
+ update();
+ emit topRightRadiusChanged();
+}
+
+void QQuickRectangle::resetTopRightRadius()
+{
+ Q_D(QQuickRectangle);
+ if (!d->extraRectangle.isAllocated())
+ return;
+ if (d->extraRectangle.value().topRightRadius < 0)
+ return;
+
+ d->extraRectangle.value().topRightRadius = -1.;
+ d->maybeSetImplicitAntialiasing();
+
+ update();
+ emit topRightRadiusChanged();
+}
+
+/*!
+ \since 6.7
+ \qmlproperty real QtQuick::Rectangle::bottomLeftRadius
+ This property holds the radius used to draw the bottom left corner.
+
+ If \l bottomLeftRadius is not set, \l radius will be used instead.
+ If \l bottomLeftRadius is zero, the corner will be sharp.
+
+ \note This API is considered tech preview and may change or be removed in
+ future versions of Qt.
+
+ \sa radius, topLeftRadius, topRightRadius, bottomRightRadius
+*/
+qreal QQuickRectangle::bottomLeftRadius() const
+{
+ Q_D(const QQuickRectangle);
+ if (d->extraRectangle.isAllocated() && d->extraRectangle->bottomLeftRadius >= 0.)
+ return d->extraRectangle.value().bottomLeftRadius;
+ return d->radius;
+}
+
+void QQuickRectangle::setBottomLeftRadius(qreal radius)
+{
+ Q_D(QQuickRectangle);
+ if (d->extraRectangle.value().bottomLeftRadius == radius)
+ return;
+
+ if (radius < 0) { // use the fact that radius < 0 resets the radius.
+ qmlWarning(this) << "bottomLeftRadius (" << radius << ") cannot be less than 0.";
+ return;
+ }
+
+ d->extraRectangle.value().bottomLeftRadius = radius;
+ d->maybeSetImplicitAntialiasing();
+
+ update();
+ emit bottomLeftRadiusChanged();
+}
+
+void QQuickRectangle::resetBottomLeftRadius()
+{
+ Q_D(QQuickRectangle);
+ if (!d->extraRectangle.isAllocated())
+ return;
+ if (d->extraRectangle.value().bottomLeftRadius < 0)
+ return;
+
+ d->extraRectangle.value().bottomLeftRadius = -1.;
+ d->maybeSetImplicitAntialiasing();
+
+ update();
+ emit bottomLeftRadiusChanged();
+}
+
+/*!
+ \since 6.7
+ \qmlproperty real QtQuick::Rectangle::bottomRightRadius
+ This property holds the radius used to draw the bottom right corner.
+
+ If \l bottomRightRadius is not set, \l radius will be used instead.
+ If \l bottomRightRadius is zero, the corner will be sharp.
+
+ \note This API is considered tech preview and may change or be removed in
+ future versions of Qt.
+
+ \sa radius, topLeftRadius, topRightRadius, bottomLeftRadius
+*/
+qreal QQuickRectangle::bottomRightRadius() const
+{
+ Q_D(const QQuickRectangle);
+ if (d->extraRectangle.isAllocated() && d->extraRectangle->bottomRightRadius >= 0.)
+ return d->extraRectangle.value().bottomRightRadius;
+ return d->radius;
+}
+
+void QQuickRectangle::setBottomRightRadius(qreal radius)
+{
+ Q_D(QQuickRectangle);
+ if (d->extraRectangle.value().bottomRightRadius == radius)
+ return;
+
+ if (radius < 0) { // use the fact that radius < 0 resets the radius.
+ qmlWarning(this) << "bottomRightRadius (" << radius << ") cannot be less than 0.";
+ return;
+ }
+
+ d->extraRectangle.value().bottomRightRadius = radius;
+ d->maybeSetImplicitAntialiasing();
+
+ update();
+ emit bottomRightRadiusChanged();
+}
+
+void QQuickRectangle::resetBottomRightRadius()
+{
+ Q_D(QQuickRectangle);
+ if (!d->extraRectangle.isAllocated())
+ return;
+ if (d->extraRectangle.value().bottomRightRadius < 0)
+ return;
+
+ d->extraRectangle.value().bottomRightRadius = -1.;
+ d->maybeSetImplicitAntialiasing();
+
+ update();
+ emit bottomRightRadiusChanged();
}
/*!
@@ -554,6 +805,17 @@ QSGNode *QQuickRectangle::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData
}
rectangle->setRadius(d->radius);
+ if (d->extraRectangle.isAllocated()) {
+ rectangle->setTopLeftRadius(d->extraRectangle.value().topLeftRadius);
+ rectangle->setTopRightRadius(d->extraRectangle.value().topRightRadius);
+ rectangle->setBottomLeftRadius(d->extraRectangle.value().bottomLeftRadius);
+ rectangle->setBottomRightRadius(d->extraRectangle.value().bottomRightRadius);
+ } else {
+ rectangle->setTopLeftRadius(-1.);
+ rectangle->setTopRightRadius(-1.);
+ rectangle->setBottomLeftRadius(-1.);
+ rectangle->setBottomRightRadius(-1.);
+ }
rectangle->setAntialiasing(antialiasing());
QGradientStops stops;
diff --git a/src/quick/items/qquickrectangle_p.h b/src/quick/items/qquickrectangle_p.h
index 9e5b530382..690154cd65 100644
--- a/src/quick/items/qquickrectangle_p.h
+++ b/src/quick/items/qquickrectangle_p.h
@@ -23,13 +23,13 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QQuickPen : public QObject
+class Q_QUICK_EXPORT QQuickPen : public QObject
{
Q_OBJECT
- Q_PROPERTY(qreal width READ width WRITE setWidth NOTIFY widthChanged)
- Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
- Q_PROPERTY(bool pixelAligned READ pixelAligned WRITE setPixelAligned NOTIFY pixelAlignedChanged)
+ Q_PROPERTY(qreal width READ width WRITE setWidth NOTIFY widthChanged FINAL)
+ Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged FINAL)
+ Q_PROPERTY(bool pixelAligned READ pixelAligned WRITE setPixelAligned NOTIFY pixelAlignedChanged FINAL)
QML_ANONYMOUS
QML_ADDED_IN_VERSION(2, 0)
public:
@@ -58,7 +58,7 @@ private:
bool m_valid : 1;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickGradientStop : public QObject
+class Q_QUICK_EXPORT QQuickGradientStop : public QObject
{
Q_OBJECT
@@ -84,7 +84,7 @@ private:
QColor m_color;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickGradient : public QObject
+class Q_QUICK_EXPORT QQuickGradient : public QObject
{
Q_OBJECT
@@ -125,7 +125,7 @@ private:
};
class QQuickRectanglePrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickRectangle : public QQuickItem
+class Q_QUICK_EXPORT QQuickRectangle : public QQuickItem
{
Q_OBJECT
@@ -133,6 +133,10 @@ class Q_QUICK_PRIVATE_EXPORT QQuickRectangle : public QQuickItem
Q_PROPERTY(QJSValue gradient READ gradient WRITE setGradient RESET resetGradient)
Q_PROPERTY(QQuickPen * border READ border CONSTANT)
Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged)
+ Q_PROPERTY(qreal topLeftRadius READ topLeftRadius WRITE setTopLeftRadius NOTIFY topLeftRadiusChanged RESET resetTopLeftRadius REVISION(6, 7) FINAL)
+ Q_PROPERTY(qreal topRightRadius READ topRightRadius WRITE setTopRightRadius NOTIFY topRightRadiusChanged RESET resetTopRightRadius REVISION(6, 7) FINAL)
+ Q_PROPERTY(qreal bottomLeftRadius READ bottomLeftRadius WRITE setBottomLeftRadius NOTIFY bottomLeftRadiusChanged RESET resetBottomLeftRadius REVISION(6, 7) FINAL)
+ Q_PROPERTY(qreal bottomRightRadius READ bottomRightRadius WRITE setBottomRightRadius NOTIFY bottomRightRadiusChanged RESET resetBottomRightRadius REVISION(6, 7) FINAL)
QML_NAMED_ELEMENT(Rectangle)
QML_ADDED_IN_VERSION(2, 0)
public:
@@ -150,9 +154,26 @@ public:
qreal radius() const;
void setRadius(qreal radius);
+ qreal topLeftRadius() const;
+ void setTopLeftRadius(qreal radius);
+ void resetTopLeftRadius();
+ qreal topRightRadius() const;
+ void setTopRightRadius(qreal radius);
+ void resetTopRightRadius();
+ qreal bottomLeftRadius() const;
+ void setBottomLeftRadius(qreal radius);
+ void resetBottomLeftRadius();
+ qreal bottomRightRadius() const;
+ void setBottomRightRadius(qreal radius);
+ void resetBottomRightRadius();
+
Q_SIGNALS:
void colorChanged();
void radiusChanged();
+ Q_REVISION(6, 7) void topLeftRadiusChanged();
+ Q_REVISION(6, 7) void topRightRadiusChanged();
+ Q_REVISION(6, 7) void bottomLeftRadiusChanged();
+ Q_REVISION(6, 7) void bottomRightRadiusChanged();
protected:
QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *) override;
@@ -167,9 +188,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickPen)
-QML_DECLARE_TYPE(QQuickGradientStop)
-QML_DECLARE_TYPE(QQuickGradient)
-QML_DECLARE_TYPE(QQuickRectangle)
-
#endif // QQUICKRECTANGLE_P_H
diff --git a/src/quick/items/qquickrectangle_p_p.h b/src/quick/items/qquickrectangle_p_p.h
index 64bf215bc7..435cea1e52 100644
--- a/src/quick/items/qquickrectangle_p_p.h
+++ b/src/quick/items/qquickrectangle_p_p.h
@@ -17,6 +17,7 @@
#include "qquickitem_p.h"
#include <QtCore/qmetaobject.h>
+#include <private/qlazilyallocated_p.h>
QT_BEGIN_NAMESPACE
@@ -40,7 +41,26 @@ public:
QJSValue gradient;
QQuickPen *pen;
qreal radius;
+
+ struct ExtraData {
+ ExtraData()
+ : topLeftRadius(-1.),
+ topRightRadius(-1.),
+ bottomLeftRadius(-1.),
+ bottomRightRadius(-1.)
+ {
+ }
+
+ qreal topLeftRadius;
+ qreal topRightRadius;
+ qreal bottomLeftRadius;
+ qreal bottomRightRadius;
+ };
+ QLazilyAllocated<ExtraData> extraRectangle;
+
static int doUpdateSlotIdx;
+
+ void maybeSetImplicitAntialiasing();
};
QT_END_NAMESPACE
diff --git a/src/quick/items/qquickrendercontrol.cpp b/src/quick/items/qquickrendercontrol.cpp
index 0575c5e5eb..3a92507141 100644
--- a/src/quick/items/qquickrendercontrol.cpp
+++ b/src/quick/items/qquickrendercontrol.cpp
@@ -26,7 +26,7 @@
#include <QtCore/private/qobject_p.h>
#include <QtQuick/private/qquickwindow_p.h>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
QT_BEGIN_NAMESPACE
@@ -60,7 +60,7 @@ QT_BEGIN_NAMESPACE
Management of the graphics devices, contexts, image and texture objects is up
to the application. The device or context that will be used by Qt Quick must
- be created before calling initialize(). The creation of the the texture object
+ be created before calling initialize(). The creation of the texture object
can be deferred, see below. Qt 5.4 introduces the ability for QOpenGLContext
to adopt existing native contexts. Together with QQuickRenderControl this
makes it possible to create a QOpenGLContext that shares with an external
@@ -281,6 +281,17 @@ int QQuickRenderControl::samples() const
\note This function does not need to be, and must not be, called when using
the \c software adaptation of Qt Quick.
+ With the default Qt Quick adaptation this function creates a new \l QRhi
+ object, similarly to what would happen with an on-screen QQuickWindow when
+ QQuickRenderControl was not used. To make this new QRhi object adopt some
+ existing device or context resource (e.g. use an existing QOpenGLContext
+ instead of creating a new one), use QQuickWindow::setGraphicsDevice() as
+ mentioned above. When the application wants to make the Qt Quick rendering
+ use an already existing \l QRhi object, that is possible as well via
+ \l QQuickGraphicsDevice::fromRhi(). When such a QQuickGraphicsDevice,
+ referencing an already existing QRhi, is set, there will be no new,
+ dedicated \l QRhi object created in initialize().
+
\since 6.0
\sa QQuickRenderTarget, QQuickGraphicsDevice, QQuickGraphicsConfiguration::preferredInstanceExtensions()
@@ -357,7 +368,8 @@ bool QQuickRenderControl::sync()
return false;
}
if (!d->cb) {
- qWarning("QQuickRenderControl cannot be used with QRhi when no QRhiCommandBuffer is provided");
+ qWarning("QQuickRenderControl cannot be used with QRhi when no QRhiCommandBuffer is provided "
+ "(perhaps beginFrame() was not called or it was unsuccessful?)");
return false;
}
cd->setCustomCommandBuffer(d->cb);
@@ -549,6 +561,16 @@ bool QQuickRenderControlPrivate::isRenderWindowFor(QQuickWindow *quickWin, const
return false;
}
+bool QQuickRenderControlPrivate::isRenderWindow(const QWindow *w)
+{
+ Q_Q(QQuickRenderControl);
+
+ if (window && w)
+ return q->renderWindowFor(window, nullptr) == w;
+
+ return false;
+}
+
/*!
\return the QQuickWindow this QQuickRenderControl is associated with.
@@ -565,6 +587,53 @@ QQuickWindow *QQuickRenderControl::window() const
}
/*!
+ \return the QRhi this QQuickRenderControl is associated with.
+
+ \note The QRhi exists only when initialize() has successfully completed.
+ Before that the return value is null.
+
+ \note This function is not applicable and returns null when using the
+ \c software adaptation of Qt Quick.
+
+ \since 6.6
+
+ \sa commandBuffer(), beginFrame(), endFrame()
+ */
+QRhi *QQuickRenderControl::rhi() const
+{
+ Q_D(const QQuickRenderControl);
+ return d->rhi;
+}
+
+/*!
+ \return the current command buffer.
+
+ Once beginFrame() is called, a QRhiCommandBuffer is set up automatically.
+ That is the command buffer Qt Quick scenegraph uses, but in some cases
+ applications may also want to query it, for example to issue resource
+ updates (for example, a texture readback).
+
+ The returned command buffer reference should only be used between
+ beginFrame() and endFrame(). There are specific exceptions, for example
+ calling
+ \l{QRhiCommandBuffer::lastCompletedGpuTime()}{lastCompletedGpuTime()} on
+ the command buffer right after endFrame(), but before the next
+ beginFrame(), is valid.
+
+ \note This function is not applicable and returns null when using the
+ \c software adaptation of Qt Quick.
+
+ \since 6.6
+
+ \sa rhi(), beginFrame(), endFrame()
+ */
+QRhiCommandBuffer *QQuickRenderControl::commandBuffer() const
+{
+ Q_D(const QQuickRenderControl);
+ return d->cb;
+}
+
+/*!
Specifies the start of a graphics frame. Calls to sync() or render() must
be enclosed by calls to beginFrame() and endFrame().
@@ -574,11 +643,11 @@ QQuickWindow *QQuickRenderControl::window() const
to the user of QQuickRenderControl to specify these points.
A typical update step, including initialization of rendering into an
- existing texture, could like like the following. The example snippet
+ existing texture, could look like the following. The example snippet
assumes Direct3D 11 but the same concepts apply other graphics APIs as
well.
- \badcode
+ \code
if (!m_quickInitialized) {
m_quickWindow->setGraphicsDevice(QQuickGraphicsDevice::fromDeviceAndContext(m_engine->device(), m_engine->context()));
@@ -600,6 +669,15 @@ QQuickWindow *QQuickRenderControl::window() const
m_renderControl->endFrame(); // Qt Quick's rendering commands are submitted to the device context here
\endcode
+ \note This function does not need to be, and must not be, called when using
+ the \c software adaptation of Qt Quick.
+
+ \note Internally beginFrame() and endFrame() invoke
+ \l{QRhi::}{beginOffscreenFrame()} and \l{QRhi::}{endOffscreenFrame()},
+ respectively. This implies that there must not be a frame (neither
+ offscreen, nor swapchain-based) being recorded on the QRhi when
+ this function is called.
+
\since 6.0
\sa endFrame(), initialize(), sync(), render(), QQuickGraphicsDevice, QQuickRenderTarget
@@ -607,8 +685,18 @@ QQuickWindow *QQuickRenderControl::window() const
void QQuickRenderControl::beginFrame()
{
Q_D(QQuickRenderControl);
- if (!d->rhi || d->rhi->isRecordingFrame())
+ if (!d->rhi) {
+ qWarning("QQuickRenderControl: No QRhi in beginFrame()");
+ return;
+ }
+ if (d->frameStatus == QQuickRenderControlPrivate::RecordingFrame) {
+ qWarning("QQuickRenderControl: beginFrame() must be followed by a call to endFrame() before calling beginFrame() again");
return;
+ }
+ if (d->rhi->isRecordingFrame()) {
+ qWarning("QQuickRenderControl: Attempted to beginFrame() while the QRhi is already recording a frame");
+ return;
+ }
emit d->window->beforeFrameBegin();
@@ -639,6 +727,9 @@ void QQuickRenderControl::beginFrame()
scenegraph are submitted to the context or command queue, whichever is
applicable.
+ \note This function does not need to be, and must not be, called when using
+ the \c software adaptation of Qt Quick.
+
\since 6.0
\sa beginFrame(), initialize(), sync(), render(), QQuickGraphicsDevice, QQuickRenderTarget
@@ -646,11 +737,22 @@ void QQuickRenderControl::beginFrame()
void QQuickRenderControl::endFrame()
{
Q_D(QQuickRenderControl);
- if (!d->rhi || !d->rhi->isRecordingFrame())
+ if (!d->rhi) {
+ qWarning("QQuickRenderControl: No QRhi in endFrame()");
return;
+ }
+ if (d->frameStatus != QQuickRenderControlPrivate::RecordingFrame) {
+ qWarning("QQuickRenderControl: endFrame() must only be called after a successful beginFrame()");
+ return;
+ }
+ if (!d->rhi->isRecordingFrame()) {
+ qWarning("QQuickRenderControl: Attempted to endFrame() while the QRhi is not recording a frame");
+ return;
+ }
d->rhi->endOffscreenFrame();
- d->cb = nullptr;
+ // do not null out d->cb; this allows calling lastCompletedGpuTime() for example
+
d->frameStatus = QQuickRenderControlPrivate::NotRecordingFrame;
emit d->window->afterFrameEnd();
diff --git a/src/quick/items/qquickrendercontrol.h b/src/quick/items/qquickrendercontrol.h
index 195b518dcc..1d8da84e57 100644
--- a/src/quick/items/qquickrendercontrol.h
+++ b/src/quick/items/qquickrendercontrol.h
@@ -14,6 +14,8 @@ class QQuickWindow;
class QOpenGLContext;
class QQuickRenderControlPrivate;
class QThread;
+class QRhi;
+class QRhiCommandBuffer;
class Q_QUICK_EXPORT QQuickRenderControl : public QObject
{
@@ -44,6 +46,9 @@ public:
QQuickWindow *window() const;
+ QRhi *rhi() const;
+ QRhiCommandBuffer *commandBuffer() const;
+
protected:
explicit QQuickRenderControl(QQuickRenderControlPrivate &dd, QObject *parent = nullptr);
diff --git a/src/quick/items/qquickrendercontrol_p.h b/src/quick/items/qquickrendercontrol_p.h
index 29b83827d6..5b1d7e860e 100644
--- a/src/quick/items/qquickrendercontrol_p.h
+++ b/src/quick/items/qquickrendercontrol_p.h
@@ -25,7 +25,7 @@ class QRhiCommandBuffer;
class QOffscreenSurface;
class QQuickGraphicsConfiguration;
-class Q_QUICK_PRIVATE_EXPORT QQuickRenderControlPrivate : public QObjectPrivate
+class Q_QUICK_EXPORT QQuickRenderControlPrivate : public QObjectPrivate
{
public:
Q_DECLARE_PUBLIC(QQuickRenderControl)
@@ -44,7 +44,7 @@ public:
}
static bool isRenderWindowFor(QQuickWindow *quickWin, const QWindow *renderWin);
- virtual bool isRenderWindow(const QWindow *w) { Q_UNUSED(w); return false; }
+ virtual bool isRenderWindow(const QWindow *w);
static void cleanup();
diff --git a/src/quick/items/qquickrendertarget.cpp b/src/quick/items/qquickrendertarget.cpp
index 7242a55d88..55bbf22eee 100644
--- a/src/quick/items/qquickrendertarget.cpp
+++ b/src/quick/items/qquickrendertarget.cpp
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qquickrendertarget_p.h"
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
#include <QtQuick/private/qquickitem_p.h>
#include <QtQuick/private/qquickwindow_p.h>
#include <QtQuick/private/qsgrhisupport_p.h>
@@ -25,14 +25,16 @@ QQuickRenderTargetPrivate::QQuickRenderTargetPrivate()
{
}
-QQuickRenderTargetPrivate::QQuickRenderTargetPrivate(const QQuickRenderTargetPrivate *other)
+QQuickRenderTargetPrivate::QQuickRenderTargetPrivate(const QQuickRenderTargetPrivate &other)
: ref(1),
- type(other->type),
- pixelSize(other->pixelSize),
- devicePixelRatio(other->devicePixelRatio),
- sampleCount(other->sampleCount),
- u(other->u),
- mirrorVertically(other->mirrorVertically)
+ type(other.type),
+ pixelSize(other.pixelSize),
+ devicePixelRatio(other.devicePixelRatio),
+ sampleCount(other.sampleCount),
+ u(other.u),
+ customDepthTexture(other.customDepthTexture),
+ mirrorVertically(other.mirrorVertically),
+ multisampleResolve(other.multisampleResolve)
{
}
@@ -161,6 +163,84 @@ void QQuickRenderTarget::setMirrorVertically(bool enable)
}
/*!
+ \return the currently set depth texture or, in most cases, \nullptr.
+
+ The value is only non-null when setDepthTexture() was called.
+
+ \since 6.8
+ */
+QRhiTexture *QQuickRenderTarget::depthTexture() const
+{
+ return d->customDepthTexture;
+}
+
+/*!
+ Requests using the given \a texture as the depth or depth-stencil buffer.
+ Ownership of \a texture is not taken.
+
+ The request is only taken into account when relevant. For example, calling
+ this function has no effect with fromRhiRenderTarget(), fromPaintDevice(),
+ or fromOpenGLRenderBuffer().
+
+ Normally a depth-stencil buffer is created automatically, transparently to
+ the user of QQuickRenderTarget. Therefore, there is no need to call this
+ function in most cases when working with QQuickRenderTarget. In special
+ circumstances, it can however become essential to be able to provide a
+ texture to render depth (or depth and stencil) data into, instead of letting
+ Qt Quick create its own intermediate textures or buffers. An example of this
+ is \l{https://www.khronos.org/openxr/}{OpenXR} and its extensions such as
+ \l{https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_KHR_composition_layer_depth}{XR_KHR_composition_layer_depth}.
+ In order to "submit the depth buffer" to the XR compositor, one has to, in
+ practice, retrieve an already created depth (depth-stencil) texture from
+ OpenXR (from the XrSwapchain) and use that texture as the render target for
+ depth data. That would not be possible without this function.
+
+ \note The \a texture is always expected to be a non-multisample 2D texture
+ or texture array (for multiview). If MSAA is involved, the samples are
+ resolved into \a texture at the end of the render pass, regardless of having
+ the MultisampleResolve flag set or not. MSAA is only supported for depth
+ (depth-stencil) textures when the underlying 3D API supports this, and this
+ support is not universally available. See \l{QRhi::ResolveDepthStencil}{the
+ relevant QRhi feature flag} for details. When this is not supported and
+ multisampling is requested in combination with a custom depth texture, \a
+ texture is not going to be touched during rendering and a warning is
+ printed.
+
+ \since 6.8
+
+ \note When it comes to OpenGL and OpenGL ES, using depth textures is not
+ functional on OpenGL ES 2.0 and requires at least OpenGL ES 3.0. Multisample
+ (MSAA) support is not available without at least OpenGL ES 3.1, or OpenGL
+ 3.0 on desktop.
+ */
+void QQuickRenderTarget::setDepthTexture(QRhiTexture *texture)
+{
+ if (d->customDepthTexture == texture)
+ return;
+
+ detach();
+ d->customDepthTexture = texture;
+}
+
+/*!
+ \enum QQuickRenderTarget::Flag
+ Flags for the static QQuickRenderTarget constructor functions.
+
+ \value MultisampleResolve Indicates that the \c sampleCount argument is not
+ the number of samples for the provided texture (and that the texture is
+ still a non-multisample texture), but rather the desired samples for
+ multisample antialiasing. Triggers automatically creating and managing an
+ intermediate multisample texture (or texture array) as the color buffer,
+ transparently to the application. The samples are resolved into the provided
+ texture at the end of the render pass automatically. When this flag is not
+ set, and the \c sampleCount argument is greater than 1, it implies the
+ provided texture is multisample. The flag has no effect is the
+ \c sampleCount is 1 (indicating that multisampling is not involved).
+
+ \since 6.8
+*/
+
+/*!
\return a new QQuickRenderTarget referencing an OpenGL texture object
specified by \a textureId.
@@ -171,7 +251,7 @@ void QQuickRenderTarget::setMirrorVertically(bool enable)
\a pixelSize specifies the size of the image, in pixels. Currently only 2D
textures are supported.
- \a sampleCount specific the number of samples. 0 or 1 means no
+ \a sampleCount specifies the number of samples. 0 or 1 means no
multisampling, while a value like 4 or 8 states that the native object is a
multisample texture.
@@ -212,8 +292,9 @@ QQuickRenderTarget QQuickRenderTarget::fromOpenGLTexture(uint textureId, uint fo
d->pixelSize = pixelSize;
d->sampleCount = qMax(1, sampleCount);
- auto rhiFormat = QSGRhiSupport::toRhiTextureFormatFromGL(format);
- d->u.nativeTexture = { textureId, 0, uint(rhiFormat), 0 };
+ QRhiTexture::Flags formatFlags;
+ QRhiTexture::Format rhiFormat = QSGRhiSupport::toRhiTextureFormatFromGL(format, &formatFlags);
+ d->u.nativeTexture = { textureId, 0, uint(rhiFormat), uint(formatFlags), uint(rhiFormat), uint(formatFlags) };
return rt;
}
@@ -228,7 +309,7 @@ QQuickRenderTarget QQuickRenderTarget::fromOpenGLTexture(uint textureId, uint fo
\a pixelSize specifies the size of the image, in pixels. Currently
only 2D textures are supported.
- \a sampleCount specific the number of samples. 0 or 1 means no
+ \a sampleCount specifies the number of samples. 0 or 1 means no
multisampling, while a value like 4 or 8 states that the native
object is a multisample texture.
@@ -252,6 +333,90 @@ QQuickRenderTarget QQuickRenderTarget::fromOpenGLTexture(uint textureId, const Q
}
/*!
+ \overload
+
+ \return a new QQuickRenderTarget referencing an OpenGL 2D texture or texture
+ array object specified by \a textureId.
+
+ \a format specifies the native internal format of the texture. Only texture
+ formats that are supported by Qt's rendering infrastructure should be used.
+
+ \a pixelSize specifies the size of the image, in pixels. Currently only 2D
+ textures and 2D texture arrays are supported.
+
+ \a sampleCount specifies the number of samples. 0 or 1 means no
+ multisampling, while a value like 4 or 8 states that the native object is a
+ multisample texture, except when \a flags contains \l MultisampleResolve. In
+ that case, \a textureId is assumed to be a non-multisample 2D texture or 2D
+ texture array, and \a sampleCount defines the number of samples desired. The
+ resulting QQuickRenderTarget will use an intermediate, automatically created
+ multisample texture (or texture array) as its color attachment, and will
+ resolve the samples into \a textureId. This is the recommended approach to
+ perform MSAA when the native OpenGL texture is not already multisample.
+
+ When \a arraySize is greater than 1, it implies multiview rendering
+ (\l{https://registry.khronos.org/OpenGL/extensions/OVR/OVR_multiview.txt}{GL_OVR_multiview},
+ \l QRhiColorAttachment::setMultiViewCount()), which can be relevant with
+ VR/AR especially. In this case \a arraySize is the number of views,
+ typically \c 2. See \l QSGMaterial::viewCount() for details on enabling
+ multiview rendering within the Qt Quick scenegraph.
+
+ A depth-stencil buffer, if applicable, is created and used automatically.
+ When the color buffer is multisample, the depth-stencil buffer will
+ automatically be multisample too. For multiview rendering, the depth-stencil
+ texture will automatically be made an array with a matching \a arraySize.
+
+ The OpenGL object name \a textureId must be a valid 2D texture name in the
+ rendering context used by the Qt Quick scenegraph. When \a arraySize is
+ greater than 1, \a textureId must be a valid 2D texture array name.
+
+ \note the resulting QQuickRenderTarget does not own any native resources, it
+ merely contains references and the associated metadata of the size and
+ sample count. It is the caller's responsibility to ensure that the native
+ resource exists as long as necessary.
+
+ \since 6.8
+
+ \note The implementation of this overload is not compatible with OpenGL ES
+ 2.0 or 3.0, and requires OpenGL ES 3.1 at minimum. (or OpenGL 3.0 on
+ desktop)
+
+ \sa QQuickWindow::setRenderTarget(), QQuickRenderControl, fromOpenGLTexture()
+ */
+QQuickRenderTarget QQuickRenderTarget::fromOpenGLTexture(uint textureId, uint format, const QSize &pixelSize, int sampleCount, int arraySize, Flags flags)
+{
+ QQuickRenderTarget rt;
+ QQuickRenderTargetPrivate *d = QQuickRenderTargetPrivate::get(&rt);
+
+ if (!textureId) {
+ qWarning("QQuickRenderTarget: textureId is invalid");
+ return rt;
+ }
+
+ if (pixelSize.isEmpty()) {
+ qWarning("QQuickRenderTarget: Cannot create with empty size");
+ return rt;
+ }
+
+ QRhiTexture::Flags formatFlags;
+ QRhiTexture::Format rhiFormat = QSGRhiSupport::toRhiTextureFormatFromGL(format, &formatFlags);
+
+ d->pixelSize = pixelSize;
+ d->sampleCount = qMax(1, sampleCount);
+ d->multisampleResolve = flags.testFlag(Flag::MultisampleResolve);
+
+ if (arraySize <= 1) {
+ d->type = QQuickRenderTargetPrivate::Type::NativeTexture;
+ d->u.nativeTexture = { textureId, 0, uint(rhiFormat), uint(formatFlags), uint(rhiFormat), uint(formatFlags) };
+ } else {
+ d->type = QQuickRenderTargetPrivate::Type::NativeTextureArray;
+ d->u.nativeTextureArray = { textureId, 0, arraySize, uint(rhiFormat), uint(formatFlags), uint(rhiFormat), uint(formatFlags) };
+ }
+
+ return rt;
+}
+
+/*!
\return a new QQuickRenderTarget referencing an OpenGL renderbuffer object
specified by \a renderbufferId.
@@ -264,7 +429,7 @@ QQuickRenderTarget QQuickRenderTarget::fromOpenGLTexture(uint textureId, const Q
\a pixelSize specifies the size of the image, in pixels.
- \a sampleCount specific the number of samples. 0 or 1 means no
+ \a sampleCount specifies the number of samples. 0 or 1 means no
multisampling, while a value like 4 or 8 states that the native object is a
multisample renderbuffer.
@@ -311,7 +476,7 @@ QQuickRenderTarget QQuickRenderTarget::fromOpenGLRenderBuffer(uint renderbufferI
\a pixelSize specifies the size of the image, in pixels. Currently only 2D
textures are supported.
- \a sampleCount specific the number of samples. 0 or 1 means no
+ \a sampleCount specifies the number of samples. 0 or 1 means no
multisampling, while a value like 4 or 8 states that the native object is a
multisample texture.
@@ -349,9 +514,9 @@ QQuickRenderTarget QQuickRenderTarget::fromD3D11Texture(void *texture, uint form
d->pixelSize = pixelSize;
d->sampleCount = qMax(1, sampleCount);
- QRhiTexture::Flags flags;
- auto rhiFormat = QSGRhiSupport::toRhiTextureFormatFromD3D11(format, &flags);
- d->u.nativeTexture = { quint64(texture), 0, uint(rhiFormat), uint(flags) };
+ QRhiTexture::Flags formatFlags;
+ QRhiTexture::Format rhiFormat = QSGRhiSupport::toRhiTextureFormatFromDXGI(format, &formatFlags);
+ d->u.nativeTexture = { quint64(texture), 0, uint(rhiFormat), uint(formatFlags), uint(rhiFormat), uint(formatFlags) };
return rt;
}
@@ -366,7 +531,7 @@ QQuickRenderTarget QQuickRenderTarget::fromD3D11Texture(void *texture, uint form
\a pixelSize specifies the size of the image, in pixels. Currently only 2D
textures are supported.
- \a sampleCount specific the number of samples. 0 or 1 means no
+ \a sampleCount specifies the number of samples. 0 or 1 means no
multisampling, while a value like 4 or 8 states that the native object is a
multisample texture.
@@ -385,7 +550,208 @@ QQuickRenderTarget QQuickRenderTarget::fromD3D11Texture(void *texture, const QSi
{
return fromD3D11Texture(texture, 0 /* DXGI_FORMAT_UNKNOWN */, pixelSize, sampleCount);
}
-#endif
+
+/*!
+ \overload
+
+ \return a new QQuickRenderTarget referencing a D3D11 texture object
+ specified by \a texture.
+
+ \a format specifies the DXGI_FORMAT of the texture. Only texture formats
+ that are supported by Qt's rendering infrastructure should be used.
+
+ \a pixelSize specifies the size of the image, in pixels. Currently only 2D
+ textures are supported.
+
+ \a sampleCount specifies the number of samples. 0 or 1 means no
+ multisampling, while a value like 4 or 8 states that the native object is a
+ multisample texture, except when \a flags contains \l MultisampleResolve. In
+ that case, \a texture is assumed to be a non-multisample 2D texture and \a
+ sampleCount defines the number of samples desired. The resulting
+ QQuickRenderTarget will use an intermediate, automatically created
+ multisample texture as its color attachment, and will resolve the samples
+ into \a texture. This is the recommended approach to perform MSAA when the
+ native texture is not already multisample.
+
+ The texture is used as the first color attachment of the render target used
+ by the Qt Quick scenegraph. A depth-stencil buffer, if applicable, is
+ created and used automatically. When the color buffer is multisample, the
+ depth-stencil buffer will automatically be multisample too.
+
+ \note the resulting QQuickRenderTarget does not own any native resources, it
+ merely contains references and the associated metadata of the size and
+ sample count. It is the caller's responsibility to ensure that the native
+ resource exists as long as necessary.
+
+ \since 6.8
+
+ \sa QQuickWindow::setRenderTarget(), QQuickRenderControl, fromD3D11Texture()
+ */
+QQuickRenderTarget QQuickRenderTarget::fromD3D11Texture(void *texture, uint format, const QSize &pixelSize, int sampleCount, Flags flags)
+{
+ QQuickRenderTarget rt = fromD3D11Texture(texture, format, pixelSize, sampleCount);
+ QQuickRenderTargetPrivate::get(&rt)->multisampleResolve = flags.testFlag(Flag::MultisampleResolve);
+ return rt;
+}
+
+/*!
+ \return a new QQuickRenderTarget referencing a D3D12 texture object
+ specified by \a texture.
+
+ \a resourceState must a valid bitmask with bits from D3D12_RESOURCE_STATES,
+ specifying the resource's current state.
+
+ \a format specifies the DXGI_FORMAT of the texture. Only texture formats
+ that are supported by Qt's rendering infrastructure should be used.
+
+ \a pixelSize specifies the size of the image, in pixels. Currently only 2D
+ textures are supported.
+
+ \a sampleCount specifies the number of samples. 0 or 1 means no
+ multisampling, while a value like 4 or 8 states that the native object is a
+ multisample texture.
+
+ The texture is used as the first color attachment of the render target used
+ by the Qt Quick scenegraph. A depth-stencil buffer, if applicable, is
+ created and used automatically.
+
+ \note the resulting QQuickRenderTarget does not own any native resources,
+ it merely contains references and the associated metadata of the size and
+ sample count. It is the caller's responsibility to ensure that the native
+ resource exists as long as necessary.
+
+ \since 6.6
+
+ \sa QQuickWindow::setRenderTarget(), QQuickRenderControl
+ */
+QQuickRenderTarget QQuickRenderTarget::fromD3D12Texture(void *texture,
+ int resourceState,
+ uint format,
+ const QSize &pixelSize,
+ int sampleCount)
+{
+ QQuickRenderTarget rt;
+ QQuickRenderTargetPrivate *d = QQuickRenderTargetPrivate::get(&rt);
+
+ if (!texture) {
+ qWarning("QQuickRenderTarget: texture is null");
+ return rt;
+ }
+
+ if (pixelSize.isEmpty()) {
+ qWarning("QQuickRenderTarget: Cannot create with empty size");
+ return rt;
+ }
+
+ d->type = QQuickRenderTargetPrivate::Type::NativeTexture;
+ d->pixelSize = pixelSize;
+ d->sampleCount = qMax(1, sampleCount);
+
+ QRhiTexture::Flags formatFlags;
+ QRhiTexture::Format rhiFormat = QSGRhiSupport::toRhiTextureFormatFromDXGI(format, &formatFlags);
+ d->u.nativeTexture = { quint64(texture), resourceState, uint(rhiFormat), uint(formatFlags), uint(rhiFormat), uint(formatFlags) };
+
+ return rt;
+}
+
+/*!
+ \overload
+
+ \return a new QQuickRenderTarget referencing a D3D12 2D texture or 2D
+ texture array object specified by \a texture.
+
+ \a resourceState must a valid bitmask with bits from D3D12_RESOURCE_STATES,
+ specifying the resource's current state.
+
+ \a format specifies the DXGI_FORMAT of the texture. Only texture formats
+ that are supported by Qt's rendering infrastructure should be used.
+
+ \a viewFormat is the DXGI_FORMAT used for the render target view (RTV).
+ Often the same as \a format. Functional only when
+ \l{https://microsoft.github.io/DirectX-Specs/d3d/RelaxedCasting.html}{relaxed
+ format casting} is supported by the driver, the argument is ignored otherwise.
+ In practice support is expected to be always available on Windows 10 1703
+ and newer.
+
+ \a pixelSize specifies the size of the image, in pixels. Currently only 2D
+ textures and 2D texture arrays are supported.
+
+ \a sampleCount specifies the number of samples. 0 or 1 means no
+ multisampling, while a value like 4 or 8 states that the native object is a
+ multisample texture, except when \a flags contains \l MultisampleResolve. In
+ that case, \a texture is assumed to be a non-multisample 2D texture or 2D
+ texture array, and \a sampleCount defines the number of samples desired. The
+ resulting QQuickRenderTarget will use an intermediate, automatically created
+ multisample texture (or texture array) as its color attachment, and will
+ resolve the samples into \a texture. This is the recommended approach to
+ perform MSAA when the native D3D12 texture is not already multisample.
+
+ The number of array elements (layers) is given in \a arraySize. When greater
+ than 1, it implies multiview rendering
+ (\l{https://microsoft.github.io/DirectX-Specs/d3d/ViewInstancing.html}{view
+ instancing}), which can be relevant with VR/AR especially. \a arraySize is
+ the number of views, typically \c 2. See \l QSGMaterial::viewCount() for
+ details on enabling multiview rendering within the Qt Quick scenegraph.
+
+ The texture is used as the first color attachment of the render target used
+ by the Qt Quick scenegraph. A depth-stencil buffer, if applicable, is
+ created and used automatically. When the color buffer is multisample, the
+ depth-stencil buffer will automatically be multisample too. For multiview
+ rendering, the depth-stencil texture will automatically be made an array
+ with a matching \a arraySize.
+
+ \note the resulting QQuickRenderTarget does not own any native resources, it
+ merely contains references and the associated metadata of the size and
+ sample count. It is the caller's responsibility to ensure that the native
+ resource exists as long as necessary.
+
+ \since 6.8
+
+ \sa QQuickWindow::setRenderTarget(), QQuickRenderControl
+ */
+QQuickRenderTarget QQuickRenderTarget::fromD3D12Texture(void *texture,
+ int resourceState,
+ uint format,
+ uint viewFormat,
+ const QSize &pixelSize,
+ int sampleCount,
+ int arraySize,
+ Flags flags)
+{
+ QQuickRenderTarget rt;
+ QQuickRenderTargetPrivate *d = QQuickRenderTargetPrivate::get(&rt);
+
+ if (!texture) {
+ qWarning("QQuickRenderTarget: texture is null");
+ return rt;
+ }
+
+ if (pixelSize.isEmpty()) {
+ qWarning("QQuickRenderTarget: Cannot create with empty size");
+ return rt;
+ }
+
+ QRhiTexture::Flags formatFlags;
+ QRhiTexture::Format rhiFormat = QSGRhiSupport::toRhiTextureFormatFromDXGI(format, &formatFlags);
+ QRhiTexture::Flags viewFormatFlags;
+ QRhiTexture::Format rhiViewFormat = QSGRhiSupport::toRhiTextureFormatFromDXGI(viewFormat, &viewFormatFlags);
+
+ d->pixelSize = pixelSize;
+ d->sampleCount = qMax(1, sampleCount);
+ d->multisampleResolve = flags.testFlag(Flag::MultisampleResolve);
+
+ if (arraySize <= 1) {
+ d->type = QQuickRenderTargetPrivate::Type::NativeTexture;
+ d->u.nativeTexture = { quint64(texture), resourceState, uint(rhiFormat), uint(formatFlags), uint(rhiViewFormat), uint(viewFormatFlags) };
+ } else {
+ d->type = QQuickRenderTargetPrivate::Type::NativeTextureArray;
+ d->u.nativeTextureArray = { quint64(texture), resourceState, arraySize, uint(rhiFormat), uint(formatFlags), uint(rhiViewFormat), uint(viewFormatFlags) };
+ }
+
+ return rt;
+}
+
+#endif // Q_OS_WIN
/*!
\return a new QQuickRenderTarget referencing a Metal texture object
@@ -397,7 +763,7 @@ QQuickRenderTarget QQuickRenderTarget::fromD3D11Texture(void *texture, const QSi
\a pixelSize specifies the size of the image, in pixels. Currently only 2D
textures are supported.
- \a sampleCount specific the number of samples. 0 or 1 means no
+ \a sampleCount specifies the number of samples. 0 or 1 means no
multisampling, while a value like 4 or 8 states that the native object is a
multisample texture.
@@ -414,7 +780,7 @@ QQuickRenderTarget QQuickRenderTarget::fromD3D11Texture(void *texture, const QSi
\sa QQuickWindow::setRenderTarget(), QQuickRenderControl
*/
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) || defined(Q_QDOC)
+#if QT_CONFIG(metal) || defined(Q_QDOC)
QQuickRenderTarget QQuickRenderTarget::fromMetalTexture(MTLTexture *texture, uint format,
const QSize &pixelSize, int sampleCount)
{
@@ -435,9 +801,9 @@ QQuickRenderTarget QQuickRenderTarget::fromMetalTexture(MTLTexture *texture, uin
d->pixelSize = pixelSize;
d->sampleCount = qMax(1, sampleCount);
- QRhiTexture::Flags flags;
- auto rhiFormat = QSGRhiSupport::toRhiTextureFormatFromMetal(format, &flags);
- d->u.nativeTexture = { quint64(texture), 0, uint(rhiFormat), uint(flags) };
+ QRhiTexture::Flags formatFlags;
+ QRhiTexture::Format rhiFormat = QSGRhiSupport::toRhiTextureFormatFromMetal(format, &formatFlags);
+ d->u.nativeTexture = { quint64(texture), 0, uint(rhiFormat), uint(formatFlags), uint(rhiFormat), uint(formatFlags) };
return rt;
}
@@ -452,7 +818,7 @@ QQuickRenderTarget QQuickRenderTarget::fromMetalTexture(MTLTexture *texture, uin
\a pixelSize specifies the size of the image, in pixels. Currently only 2D
textures are supported.
- \a sampleCount specific the number of samples. 0 or 1 means no
+ \a sampleCount specifies the number of samples. 0 or 1 means no
multisampling, while a value like 4 or 8 states that the native object is a
multisample texture.
@@ -471,6 +837,94 @@ QQuickRenderTarget QQuickRenderTarget::fromMetalTexture(MTLTexture *texture, con
{
return fromMetalTexture(texture, 0 /* MTLPixelFormatInvalid */, pixelSize, sampleCount);
}
+
+/*!
+ \overload
+
+ \return a new QQuickRenderTarget referencing a Metal 2D texture or 2D
+ texture array given in \a texture.
+
+ \a format specifies the MTLPixelFormat of the texture. Only texture formats
+ that are supported by Qt's rendering infrastructure should be used.
+
+ \a viewFormat is usually set to the same value as \a format. In some cases,
+ such as when rendering into a texture with a \c{_SRGB} format and the
+ implicit linear->sRGB conversion on shader writes is not wanted, the value
+ can be different. Note however that the value may be ignored by Qt, when at
+ run time QRhi reports that the \l{QRhi::TextureViewFormat} feature is
+ unsupported.
+
+ \a pixelSize specifies the size of the image, in pixels. Currently only 2D
+ textures and 2D texture arrays are supported.
+
+ \a sampleCount specifies the number of samples. 0 or 1 means no
+ multisampling, while a value like 4 or 8 states that the native object is a
+ multisample texture, except when \a flags contains \l MultisampleResolve. In
+ that case, \a texture is assumed to be a non-multisample 2D texture or 2D
+ texture array, and \a sampleCount defines the number of samples desired. The
+ resulting QQuickRenderTarget will use an intermediate, automatically created
+ multisample texture (or texture array) as its color attachment, and will
+ resolve the samples into \a texture. This is the recommended approach to
+ perform MSAA when the native Metal texture is not already multisample.
+
+ The number of array elements (layers) is given in \a arraySize. When greater
+ than 1, it implies multiview rendering, which can be relevant with VR/AR
+ especially. \a arraySize is the number of views, typically \c 2. See
+ \l QSGMaterial::viewCount() for details on enabling multiview rendering within
+ the Qt Quick scenegraph.
+
+ The texture is used as the first color attachment of the render target used
+ by the Qt Quick scenegraph. A depth-stencil buffer, if applicable, is
+ created and used automatically. When the color buffer is multisample, the
+ depth-stencil buffer will automatically be multisample too. For multiview
+ rendering, the depth-stencil texture will automatically be made an array
+ with a matching \a arraySize.
+
+ \note the resulting QQuickRenderTarget does not own any native resources, it
+ merely contains references and the associated metadata of the size and
+ sample count. It is the caller's responsibility to ensure that the native
+ resource exists as long as necessary.
+
+ \since 6.8
+
+ \sa QQuickWindow::setRenderTarget(), QQuickRenderControl
+ */
+QQuickRenderTarget QQuickRenderTarget::fromMetalTexture(MTLTexture *texture, uint format, uint viewFormat,
+ const QSize &pixelSize, int sampleCount, int arraySize, Flags flags)
+{
+ QQuickRenderTarget rt;
+ QQuickRenderTargetPrivate *d = QQuickRenderTargetPrivate::get(&rt);
+
+ if (!texture) {
+ qWarning("QQuickRenderTarget: texture is null");
+ return rt;
+ }
+
+ if (pixelSize.isEmpty()) {
+ qWarning("QQuickRenderTarget: Cannot create with empty size");
+ return rt;
+ }
+
+ QRhiTexture::Flags formatFlags;
+ QRhiTexture::Format rhiFormat = QSGRhiSupport::toRhiTextureFormatFromMetal(format, &formatFlags);
+ QRhiTexture::Flags viewFormatFlags;
+ QRhiTexture::Format rhiViewFormat = QSGRhiSupport::toRhiTextureFormatFromMetal(viewFormat, &viewFormatFlags);
+
+ d->pixelSize = pixelSize;
+ d->sampleCount = qMax(1, sampleCount);
+ d->multisampleResolve = flags.testFlag(Flag::MultisampleResolve);
+
+ if (arraySize <= 1) {
+ d->type = QQuickRenderTargetPrivate::Type::NativeTexture;
+ d->u.nativeTexture = { quint64(texture), 0, uint(rhiFormat), uint(formatFlags), uint(rhiViewFormat), uint(viewFormatFlags) };
+ } else {
+ d->type = QQuickRenderTargetPrivate::Type::NativeTextureArray;
+ d->u.nativeTextureArray = { quint64(texture), 0, arraySize, uint(rhiFormat), uint(formatFlags), uint(rhiViewFormat), uint(viewFormatFlags) };
+ }
+
+ return rt;
+}
+
#endif
/*!
@@ -484,7 +938,7 @@ QQuickRenderTarget QQuickRenderTarget::fromMetalTexture(MTLTexture *texture, con
\a pixelSize specifies the size of the image, in pixels. Currently only 2D
textures are supported.
- \a sampleCount specific the number of samples. 0 or 1 means no
+ \a sampleCount specifies the number of samples. 0 or 1 means no
multisampling, while a value like 4 or 8 states that the native object is a
multisample texture.
@@ -502,8 +956,7 @@ QQuickRenderTarget QQuickRenderTarget::fromMetalTexture(MTLTexture *texture, con
\sa QQuickWindow::setRenderTarget(), QQuickRenderControl
*/
#if QT_CONFIG(vulkan) || defined(Q_QDOC)
-QQuickRenderTarget QQuickRenderTarget::fromVulkanImage(VkImage image, VkImageLayout layout, VkFormat format,
- const QSize &pixelSize, int sampleCount)
+QQuickRenderTarget QQuickRenderTarget::fromVulkanImage(VkImage image, VkImageLayout layout, VkFormat format, const QSize &pixelSize, int sampleCount)
{
QQuickRenderTarget rt;
QQuickRenderTargetPrivate *d = QQuickRenderTargetPrivate::get(&rt);
@@ -522,9 +975,9 @@ QQuickRenderTarget QQuickRenderTarget::fromVulkanImage(VkImage image, VkImageLay
d->pixelSize = pixelSize;
d->sampleCount = qMax(1, sampleCount);
- QRhiTexture::Flags flags;
- auto rhiFormat = QSGRhiSupport::toRhiTextureFormatFromVulkan(format, &flags);
- d->u.nativeTexture = { quint64(image), layout, uint(rhiFormat), uint(flags) };
+ QRhiTexture::Flags formatFlags;
+ QRhiTexture::Format rhiFormat = QSGRhiSupport::toRhiTextureFormatFromVulkan(format, &formatFlags);
+ d->u.nativeTexture = { quint64(image), layout, uint(rhiFormat), uint(formatFlags), uint(rhiFormat), uint(formatFlags) };
return rt;
}
@@ -532,14 +985,14 @@ QQuickRenderTarget QQuickRenderTarget::fromVulkanImage(VkImage image, VkImageLay
/*!
\overload
- \return a new QQuickRenderTarget referencing n Vulkan image object specified
+ \return a new QQuickRenderTarget referencing a Vulkan image object specified
by \a image. The image is assumed to have a format of
VK_FORMAT_R8G8B8A8_UNORM.
\a pixelSize specifies the size of the image, in pixels. Currently only 2D
textures are supported.
- \a sampleCount specific the number of samples. 0 or 1 means no
+ \a sampleCount specifies the number of samples. 0 or 1 means no
multisampling, while a value like 4 or 8 states that the native object is a
multisample texture.
@@ -558,11 +1011,113 @@ QQuickRenderTarget QQuickRenderTarget::fromVulkanImage(VkImage image, VkImageLay
{
return fromVulkanImage(image, layout, VK_FORMAT_UNDEFINED, pixelSize, sampleCount);
}
-#endif
/*!
- \internal
+ \overload
+
+ \return a new QQuickRenderTarget referencing a Vulkan image object
+ specified by \a image. The current \a layout of the image must be provided
+ as well. The image must be either a 2D texture or 2D texture array.
+
+ \a format specifies the VkFormat of the image. Only image formats that are
+ supported by Qt's rendering infrastructure should be used.
+
+ \a viewFormat is usually set to the same value as \a format. In some cases,
+ such as when rendering into a texture with a \c{_SRGB} format and the
+ implicit linear->sRGB conversion on shader writes is not wanted, the value
+ can be different. (for example, a \a format of \c VK_FORMAT_R8G8B8A8_SRGB
+ and \a viewFormat of \c VK_FORMAT_R8G8B8A8_UNORM).
+
+ \a pixelSize specifies the size of the image, in pixels. Currently only 2D
+ textures are supported.
+
+ \a sampleCount specifies the number of samples. 0 or 1 means no
+ multisampling, while a value like 4 or 8 states that the native object is a
+ multisample texture, except when \a flags contains \l MultisampleResolve. In
+ that case, \a image is assumed to be a non-multisample 2D texture or 2D
+ texture array, and \a sampleCount defines the number of samples desired. The
+ resulting QQuickRenderTarget will use an intermediate, automatically created
+ multisample texture (or texture array) as its color attachment, and will
+ resolve the samples into \a image. This is the recommended approach to
+ perform MSAA when the native Vulkan image is not already multisample.
+
+ The number of array elements (layers) is given in \a arraySize. When greater
+ than 1, it implies multiview rendering
+ (\l{https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_multiview.html}{VK_KHR_multiview}),
+ which can be relevant with VR/AR especially. \a arraySize is the number of
+ views, typically \c 2. See \l QSGMaterial::viewCount() for details on
+ enabling multiview rendering within the Qt Quick scenegraph.
+
+ The texture is used as the first color attachment of the render target used
+ by the Qt Quick scenegraph. A depth-stencil buffer, if applicable, is
+ created and used automatically. When the color buffer is multisample, the
+ depth-stencil buffer will automatically be multisample too. For multiview
+ rendering, the depth-stencil texture will automatically be made an array
+ with a matching \a arraySize.
+
+ \note the resulting QQuickRenderTarget does not own any native resources, it
+ merely contains references and the associated metadata of the size and
+ sample count. It is the caller's responsibility to ensure that the native
+ resource exists as long as necessary.
+
+ \since 6.8
+
+ \sa QQuickWindow::setRenderTarget(), QQuickRenderControl
*/
+QQuickRenderTarget QQuickRenderTarget::fromVulkanImage(VkImage image, VkImageLayout layout, VkFormat format, VkFormat viewFormat,
+ const QSize &pixelSize, int sampleCount, int arraySize, Flags flags)
+{
+ QQuickRenderTarget rt;
+ QQuickRenderTargetPrivate *d = QQuickRenderTargetPrivate::get(&rt);
+
+ if (image == VK_NULL_HANDLE) {
+ qWarning("QQuickRenderTarget: image is invalid");
+ return rt;
+ }
+
+ if (pixelSize.isEmpty()) {
+ qWarning("QQuickRenderTarget: Cannot create with empty size");
+ return rt;
+ }
+
+ QRhiTexture::Flags formatFlags;
+ QRhiTexture::Format rhiFormat = QSGRhiSupport::toRhiTextureFormatFromVulkan(format, &formatFlags);
+ QRhiTexture::Flags viewFormatFlags;
+ QRhiTexture::Format rhiViewFormat = QSGRhiSupport::toRhiTextureFormatFromVulkan(viewFormat, &viewFormatFlags);
+
+ d->pixelSize = pixelSize;
+ d->sampleCount = qMax(1, sampleCount);
+ d->multisampleResolve = flags.testFlag(Flag::MultisampleResolve);
+
+ if (arraySize <= 1) {
+ d->type = QQuickRenderTargetPrivate::Type::NativeTexture;
+ d->u.nativeTexture = { quint64(image), layout, uint(rhiFormat), uint(formatFlags), uint(rhiViewFormat), uint(viewFormatFlags) };
+ } else {
+ d->type = QQuickRenderTargetPrivate::Type::NativeTextureArray;
+ d->u.nativeTextureArray = { quint64(image), layout, arraySize, uint(rhiFormat), uint(formatFlags), uint(rhiViewFormat), uint(viewFormatFlags) };
+ }
+
+ return rt;
+}
+
+#endif // Vulkan
+
+/*!
+ \return a new QQuickRenderTarget referencing an existing \a renderTarget.
+
+ \a renderTarget will in most cases be a QRhiTextureRenderTarget, which
+ allows directing the Qt Quick scene's rendering into a QRhiTexture.
+
+ \note the resulting QQuickRenderTarget does not own \a renderTarget and any
+ underlying native resources, it merely contains references and the
+ associated metadata of the size and sample count. It is the caller's
+ responsibility to ensure that the referenced resources exists as long as
+ necessary.
+
+ \since 6.6
+
+ \sa QQuickWindow::setRenderTarget(), QQuickRenderControl
+*/
QQuickRenderTarget QQuickRenderTarget::fromRhiRenderTarget(QRhiRenderTarget *renderTarget)
{
QQuickRenderTarget rt;
@@ -628,7 +1183,8 @@ bool QQuickRenderTarget::isEqual(const QQuickRenderTarget &other) const noexcept
|| d->pixelSize != other.d->pixelSize
|| d->devicePixelRatio != other.d->devicePixelRatio
|| d->sampleCount != other.d->sampleCount
- || d->mirrorVertically != other.d->mirrorVertically)
+ || d->mirrorVertically != other.d->mirrorVertically
+ || d->multisampleResolve != other.d->multisampleResolve)
{
return false;
}
@@ -638,9 +1194,21 @@ bool QQuickRenderTarget::isEqual(const QQuickRenderTarget &other) const noexcept
break;
case QQuickRenderTargetPrivate::Type::NativeTexture:
if (d->u.nativeTexture.object != other.d->u.nativeTexture.object
- || d->u.nativeTexture.layout != other.d->u.nativeTexture.layout
+ || d->u.nativeTexture.layoutOrState != other.d->u.nativeTexture.layoutOrState
|| d->u.nativeTexture.rhiFormat != other.d->u.nativeTexture.rhiFormat
- || d->u.nativeTexture.rhiFlags != other.d->u.nativeTexture.rhiFlags)
+ || d->u.nativeTexture.rhiFormatFlags != other.d->u.nativeTexture.rhiFormatFlags
+ || d->u.nativeTexture.rhiViewFormat != other.d->u.nativeTexture.rhiViewFormat
+ || d->u.nativeTexture.rhiViewFormatFlags != other.d->u.nativeTexture.rhiViewFormatFlags)
+ return false;
+ break;
+ case QQuickRenderTargetPrivate::Type::NativeTextureArray:
+ if (d->u.nativeTextureArray.object != other.d->u.nativeTextureArray.object
+ || d->u.nativeTextureArray.layoutOrState != other.d->u.nativeTextureArray.layoutOrState
+ || d->u.nativeTextureArray.arraySize != other.d->u.nativeTextureArray.arraySize
+ || d->u.nativeTextureArray.rhiFormat != other.d->u.nativeTextureArray.rhiFormat
+ || d->u.nativeTextureArray.rhiFormatFlags != other.d->u.nativeTextureArray.rhiFormatFlags
+ || d->u.nativeTextureArray.rhiViewFormat != other.d->u.nativeTextureArray.rhiViewFormat
+ || d->u.nativeTextureArray.rhiViewFormatFlags != other.d->u.nativeTextureArray.rhiViewFormatFlags)
return false;
break;
case QQuickRenderTargetPrivate::Type::NativeRenderbuffer:
@@ -662,21 +1230,261 @@ bool QQuickRenderTarget::isEqual(const QQuickRenderTarget &other) const noexcept
return true;
}
-static bool createRhiRenderTarget(const QRhiColorAttachment &colorAttachment,
+static bool createRhiRenderTargetWithRenderBuffer(QRhiRenderBuffer *renderBuffer,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhi *rhi,
+ QQuickWindowRenderTarget *dst)
+{
+ sampleCount = QSGRhiSupport::chooseSampleCount(sampleCount, rhi);
+
+ std::unique_ptr<QRhiRenderBuffer> depthStencil;
+ if (dst->implicitBuffers.depthStencil) {
+ if (dst->implicitBuffers.depthStencil->pixelSize() == pixelSize
+ && dst->implicitBuffers.depthStencil->sampleCount() == sampleCount)
+ {
+ depthStencil.reset(dst->implicitBuffers.depthStencil);
+ dst->implicitBuffers.depthStencil = nullptr;
+ }
+ }
+ dst->implicitBuffers.reset(rhi);
+
+ if (!depthStencil) {
+ depthStencil.reset(rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, pixelSize, sampleCount));
+ depthStencil->setName(QByteArrayLiteral("Depth-stencil buffer for QQuickRenderTarget"));
+ if (!depthStencil->create()) {
+ qWarning("Failed to build depth-stencil buffer for QQuickRenderTarget");
+ return false;
+ }
+ }
+
+ QRhiColorAttachment colorAttachment(renderBuffer);
+ QRhiTextureRenderTargetDescription rtDesc(colorAttachment);
+ rtDesc.setDepthStencilBuffer(depthStencil.get());
+ std::unique_ptr<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(rtDesc));
+ rt->setName(QByteArrayLiteral("RT for QQuickRenderTarget with renderbuffer"));
+ std::unique_ptr<QRhiRenderPassDescriptor> rp(rt->newCompatibleRenderPassDescriptor());
+ rt->setRenderPassDescriptor(rp.get());
+
+ if (!rt->create()) {
+ qWarning("Failed to build renderbuffer-based render target for QQuickRenderTarget");
+ return false;
+ }
+
+ dst->rt.renderTarget = rt.release();
+ dst->rt.owns = true;
+ dst->res.rpDesc = rp.release();
+ dst->implicitBuffers.depthStencil = depthStencil.release();
+
+ return true;
+}
+
+static bool createRhiRenderTarget(QRhiTexture *texture,
const QSize &pixelSize,
int sampleCount,
+ bool multisampleResolve,
QRhi *rhi,
QQuickWindowRenderTarget *dst)
{
- std::unique_ptr<QRhiRenderBuffer> depthStencil(rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, pixelSize, sampleCount));
- if (!depthStencil->create()) {
- qWarning("Failed to build depth-stencil buffer for QQuickRenderTarget");
- return false;
+ // Simple path: no user-supplied depth texture. So create our own
+ // depth-stencil buffer, using renderbuffers (so this is still GLES 2.0
+ // compatible), with MSAA support being GLES 3.0 compatible.
+
+ sampleCount = QSGRhiSupport::chooseSampleCount(sampleCount, rhi);
+ if (sampleCount <= 1)
+ multisampleResolve = false;
+
+ std::unique_ptr<QRhiRenderBuffer> depthStencil;
+ if (dst->implicitBuffers.depthStencil) {
+ if (dst->implicitBuffers.depthStencil->pixelSize() == pixelSize
+ && dst->implicitBuffers.depthStencil->sampleCount() == sampleCount)
+ {
+ depthStencil.reset(dst->implicitBuffers.depthStencil);
+ dst->implicitBuffers.depthStencil = nullptr;
+ }
+ }
+
+ std::unique_ptr<QRhiTexture> colorBuffer;
+ QRhiTexture::Flags multisampleTextureFlags;
+ QRhiTexture::Format multisampleTextureFormat = texture->format();
+ if (multisampleResolve) {
+ multisampleTextureFlags = QRhiTexture::RenderTarget;
+ if (texture->flags().testFlag(QRhiTexture::sRGB))
+ multisampleTextureFlags |= QRhiTexture::sRGB;
+
+ if (dst->implicitBuffers.multisampleTexture) {
+ if (dst->implicitBuffers.multisampleTexture->pixelSize() == pixelSize
+ && dst->implicitBuffers.multisampleTexture->format() == multisampleTextureFormat
+ && dst->implicitBuffers.multisampleTexture->sampleCount() == sampleCount
+ && dst->implicitBuffers.multisampleTexture->flags().testFlags(multisampleTextureFlags))
+ {
+ colorBuffer.reset(dst->implicitBuffers.multisampleTexture);
+ dst->implicitBuffers.multisampleTexture = nullptr;
+ }
+ }
}
+ dst->implicitBuffers.reset(rhi);
+
+ if (!depthStencil) {
+ depthStencil.reset(rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, pixelSize, sampleCount));
+ depthStencil->setName(QByteArrayLiteral("Depth-stencil buffer for QQuickRenderTarget"));
+ if (!depthStencil->create()) {
+ qWarning("Failed to build depth-stencil buffer for QQuickRenderTarget");
+ return false;
+ }
+ }
+
+ if (multisampleResolve && !colorBuffer) {
+ colorBuffer.reset(rhi->newTexture(multisampleTextureFormat, pixelSize, sampleCount, multisampleTextureFlags));
+ colorBuffer->setName(QByteArrayLiteral("Multisample color buffer for QQuickRenderTarget"));
+ colorBuffer->setWriteViewFormat(texture->writeViewFormat());
+ if (!colorBuffer->create()) {
+ qWarning("Failed to build multisample color buffer for QQuickRenderTarget");
+ return false;
+ }
+ }
+
+ QRhiColorAttachment colorAttachment;
+ if (multisampleResolve) {
+ colorAttachment.setTexture(colorBuffer.get());
+ colorAttachment.setResolveTexture(texture);
+ } else {
+ colorAttachment.setTexture(texture);
+ }
QRhiTextureRenderTargetDescription rtDesc(colorAttachment);
rtDesc.setDepthStencilBuffer(depthStencil.get());
std::unique_ptr<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(rtDesc));
+ rt->setName(QByteArrayLiteral("RT for QQuickRenderTarget"));
+ std::unique_ptr<QRhiRenderPassDescriptor> rp(rt->newCompatibleRenderPassDescriptor());
+ rt->setRenderPassDescriptor(rp.get());
+
+ if (!rt->create()) {
+ qWarning("Failed to build texture render target for QQuickRenderTarget");
+ return false;
+ }
+
+ dst->rt.renderTarget = rt.release();
+ dst->rt.owns = true;
+ dst->res.rpDesc = rp.release();
+ dst->implicitBuffers.depthStencil = depthStencil.release();
+ if (multisampleResolve)
+ dst->implicitBuffers.multisampleTexture = colorBuffer.release();
+
+ return true;
+}
+
+static bool createRhiRenderTargetWithDepthTexture(QRhiTexture *texture,
+ QRhiTexture *depthTexture,
+ const QSize &pixelSize,
+ int sampleCount,
+ bool multisampleResolve,
+ QRhi *rhi,
+ QQuickWindowRenderTarget *dst)
+{
+ // This version takes a user-supplied depthTexture. That texture is always
+ // non-multisample. If sample count is > 1, we still need our own
+ // multisample depth-stencil buffer, and the depth(stencil) data is expected
+ // to be resolved (and written out) to depthTexture, _if_ the underlying API
+ // supports it (see QRhi's ResolveDepthStencil feature). The intermediate,
+ // multisample depth-stencil buffer must be a texture here (not
+ // renderbuffer), specifically for OpenGL ES and its related multisample
+ // extensions.
+
+ sampleCount = QSGRhiSupport::chooseSampleCount(sampleCount, rhi);
+ if (sampleCount <= 1)
+ multisampleResolve = false;
+
+ std::unique_ptr<QRhiTexture> depthStencil;
+ if (dst->implicitBuffers.depthStencilTexture) {
+ if (dst->implicitBuffers.depthStencilTexture->pixelSize() == pixelSize
+ && dst->implicitBuffers.depthStencilTexture->sampleCount() == sampleCount)
+ {
+ depthStencil.reset(dst->implicitBuffers.depthStencilTexture);
+ dst->implicitBuffers.depthStencilTexture = nullptr;
+ }
+ }
+
+ std::unique_ptr<QRhiTexture> colorBuffer;
+ QRhiTexture::Flags multisampleTextureFlags;
+ QRhiTexture::Format multisampleTextureFormat = texture->format();
+ if (multisampleResolve) {
+ multisampleTextureFlags = QRhiTexture::RenderTarget;
+ if (texture->flags().testFlag(QRhiTexture::sRGB))
+ multisampleTextureFlags |= QRhiTexture::sRGB;
+
+ if (dst->implicitBuffers.multisampleTexture) {
+ if (dst->implicitBuffers.multisampleTexture->pixelSize() == pixelSize
+ && dst->implicitBuffers.multisampleTexture->format() == multisampleTextureFormat
+ && dst->implicitBuffers.multisampleTexture->sampleCount() == sampleCount
+ && dst->implicitBuffers.multisampleTexture->flags().testFlags(multisampleTextureFlags))
+ {
+ colorBuffer.reset(dst->implicitBuffers.multisampleTexture);
+ dst->implicitBuffers.multisampleTexture = nullptr;
+ }
+ }
+ }
+
+ dst->implicitBuffers.reset(rhi);
+
+ bool needsDepthStencilBuffer = true;
+ if (sampleCount <= 1) {
+ depthStencil.reset();
+ needsDepthStencilBuffer = false;
+ }
+ if (depthTexture->pixelSize() != pixelSize) {
+ qWarning("Custom depth texture size (%dx%d) does not match the QQuickRenderTarget (%dx%d)",
+ depthTexture->pixelSize().width(),
+ depthTexture->pixelSize().height(),
+ pixelSize.width(),
+ pixelSize.height());
+ return false;
+ }
+ if (depthTexture->sampleCount() > 1) {
+ qWarning("Custom depth texture cannot be multisample");
+ return false;
+ }
+ if (needsDepthStencilBuffer && !depthStencil) {
+ QRhiTexture::Format multisampleDepthTextureFormat = depthTexture->format();
+ depthStencil.reset(rhi->newTexture(multisampleDepthTextureFormat, pixelSize, sampleCount, QRhiTexture::RenderTarget));
+ depthStencil->setName(QByteArrayLiteral("Depth-stencil texture for QQuickRenderTarget"));
+ if (!depthStencil->create()) {
+ qWarning("Failed to build depth-stencil buffer for QQuickRenderTarget");
+ return false;
+ }
+ }
+
+ if (multisampleResolve && !colorBuffer) {
+ colorBuffer.reset(rhi->newTexture(multisampleTextureFormat, pixelSize, sampleCount, multisampleTextureFlags));
+ colorBuffer->setName(QByteArrayLiteral("Multisample color buffer for QQuickRenderTarget"));
+ colorBuffer->setWriteViewFormat(texture->writeViewFormat());
+ if (!colorBuffer->create()) {
+ qWarning("Failed to build multisample color buffer for QQuickRenderTarget");
+ return false;
+ }
+ }
+
+ QRhiColorAttachment colorAttachment;
+ if (multisampleResolve) {
+ colorAttachment.setTexture(colorBuffer.get());
+ colorAttachment.setResolveTexture(texture);
+ } else {
+ colorAttachment.setTexture(texture);
+ }
+
+ QRhiTextureRenderTargetDescription rtDesc(colorAttachment);
+ if (sampleCount > 1) {
+ rtDesc.setDepthTexture(depthStencil.get());
+ if (rhi->isFeatureSupported(QRhi::ResolveDepthStencil))
+ rtDesc.setDepthResolveTexture(depthTexture);
+ else
+ qWarning("Depth-stencil resolve is not supported by the underlying 3D API, depth contents will not be resolved");
+ } else {
+ rtDesc.setDepthTexture(depthTexture);
+ }
+
+ std::unique_ptr<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(rtDesc));
+ rt->setName(QByteArrayLiteral("RT for QQuickRenderTarget"));
std::unique_ptr<QRhiRenderPassDescriptor> rp(rt->newCompatibleRenderPassDescriptor());
rt->setRenderPassDescriptor(rp.get());
@@ -685,37 +1493,222 @@ static bool createRhiRenderTarget(const QRhiColorAttachment &colorAttachment,
return false;
}
- dst->renderTarget = rt.release();
- dst->rpDesc = rp.release();
- dst->depthStencil = depthStencil.release();
- dst->owns = true; // ownership of the native resource itself is not transferred but the QRhi objects are on us now
+ dst->rt.renderTarget = rt.release();
+ dst->rt.owns = true;
+ dst->res.rpDesc = rp.release();
+ if (depthStencil)
+ dst->implicitBuffers.depthStencilTexture = depthStencil.release();
+ if (multisampleResolve)
+ dst->implicitBuffers.multisampleTexture = colorBuffer.release();
+
+ return true;
+}
+
+static bool createRhiRenderTargetMultiView(QRhiTexture *texture,
+ QRhiTexture *maybeCustomDepthTexture,
+ const QSize &pixelSize,
+ int arraySize,
+ int sampleCount,
+ bool multisampleResolve,
+ QRhi *rhi,
+ QQuickWindowRenderTarget *dst)
+{
+ // Multiview path, working with texture arrays. Optionally with a
+ // user-supplied, non-multisample depth texture (array). (same semantics
+ // then as with createRhiRenderTargetWithDepthTexture, but everything is a
+ // 2D texture array here)
+
+ sampleCount = QSGRhiSupport::chooseSampleCount(sampleCount, rhi);
+ if (sampleCount <= 1)
+ multisampleResolve = false;
+
+ std::unique_ptr<QRhiTexture> depthStencil;
+ if (dst->implicitBuffers.depthStencilTexture) {
+ if (dst->implicitBuffers.depthStencilTexture->pixelSize() == pixelSize
+ && dst->implicitBuffers.depthStencilTexture->sampleCount() == sampleCount
+ && dst->implicitBuffers.depthStencilTexture->arraySize() == arraySize)
+ {
+ depthStencil.reset(dst->implicitBuffers.depthStencilTexture);
+ dst->implicitBuffers.depthStencilTexture = nullptr;
+ }
+ }
+
+ std::unique_ptr<QRhiTexture> colorBuffer;
+ QRhiTexture::Flags multisampleTextureFlags;
+ QRhiTexture::Format multisampleTextureFormat = texture->format();
+ if (multisampleResolve) {
+ multisampleTextureFlags = QRhiTexture::RenderTarget;
+ if (texture->flags().testFlag(QRhiTexture::sRGB))
+ multisampleTextureFlags |= QRhiTexture::sRGB;
+
+ if (dst->implicitBuffers.multisampleTexture) {
+ if (dst->implicitBuffers.multisampleTexture->pixelSize() == pixelSize
+ && dst->implicitBuffers.multisampleTexture->format() == multisampleTextureFormat
+ && dst->implicitBuffers.multisampleTexture->sampleCount() == sampleCount
+ && dst->implicitBuffers.multisampleTexture->arraySize() == arraySize
+ && dst->implicitBuffers.multisampleTexture->flags().testFlags(multisampleTextureFlags))
+ {
+ colorBuffer.reset(dst->implicitBuffers.multisampleTexture);
+ dst->implicitBuffers.multisampleTexture = nullptr;
+ }
+ }
+ }
+
+ dst->implicitBuffers.reset(rhi);
+
+ bool needsDepthStencilBuffer = true;
+ if (maybeCustomDepthTexture) {
+ if (sampleCount <= 1) {
+ depthStencil.reset();
+ needsDepthStencilBuffer = false;
+ }
+ if (maybeCustomDepthTexture->arraySize() != arraySize) {
+ qWarning("Custom depth texture array size (%d) does not match QQuickRenderTarget (%d)",
+ maybeCustomDepthTexture->arraySize(), arraySize);
+ return false;
+ }
+ if (maybeCustomDepthTexture->pixelSize() != pixelSize) {
+ qWarning("Custom depth texture size (%dx%d) does not match the QQuickRenderTarget (%dx%d)",
+ maybeCustomDepthTexture->pixelSize().width(),
+ maybeCustomDepthTexture->pixelSize().height(),
+ pixelSize.width(),
+ pixelSize.height());
+ return false;
+ }
+ if (maybeCustomDepthTexture->sampleCount() > 1) {
+ qWarning("Custom depth texture cannot be multisample");
+ return false;
+ }
+ }
+ if (needsDepthStencilBuffer && !depthStencil) {
+ depthStencil.reset(rhi->newTextureArray(QRhiTexture::D24S8, arraySize, pixelSize, sampleCount, QRhiTexture::RenderTarget));
+ depthStencil->setName(QByteArrayLiteral("Depth-stencil buffer (multiview) for QQuickRenderTarget"));
+ if (!depthStencil->create()) {
+ qWarning("Failed to build depth-stencil texture array for QQuickRenderTarget");
+ return false;
+ }
+ }
+
+ if (multisampleResolve && !colorBuffer) {
+ colorBuffer.reset(rhi->newTextureArray(multisampleTextureFormat, arraySize, pixelSize, sampleCount, multisampleTextureFlags));
+ colorBuffer->setName(QByteArrayLiteral("Multisample color buffer (multiview) for QQuickRenderTarget"));
+ colorBuffer->setWriteViewFormat(texture->writeViewFormat());
+ if (!colorBuffer->create()) {
+ qWarning("Failed to build multisample texture array for QQuickRenderTarget");
+ return false;
+ }
+ }
+
+ QRhiColorAttachment colorAttachment;
+ colorAttachment.setMultiViewCount(arraySize);
+ if (multisampleResolve) {
+ colorAttachment.setTexture(colorBuffer.get());
+ colorAttachment.setResolveTexture(texture);
+ } else {
+ colorAttachment.setTexture(texture);
+ }
+
+ QRhiTextureRenderTargetDescription rtDesc(colorAttachment);
+ if (sampleCount > 1) {
+ rtDesc.setDepthTexture(depthStencil.get());
+ if (maybeCustomDepthTexture) {
+ if (rhi->isFeatureSupported(QRhi::ResolveDepthStencil))
+ rtDesc.setDepthResolveTexture(maybeCustomDepthTexture);
+ else
+ qWarning("Depth-stencil resolve is not supported by the underlying 3D API, depth contents will not be resolved");
+ }
+ } else {
+ if (depthStencil)
+ rtDesc.setDepthTexture(depthStencil.get());
+ else if (maybeCustomDepthTexture)
+ rtDesc.setDepthTexture(maybeCustomDepthTexture);
+ }
+
+ QRhiTextureRenderTarget::Flags rtFlags;
+ if (!maybeCustomDepthTexture)
+ rtFlags |= QRhiTextureRenderTarget::DoNotStoreDepthStencilContents;
+
+ std::unique_ptr<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(rtDesc, rtFlags));
+ rt->setName(QByteArrayLiteral("RT for multiview QQuickRenderTarget"));
+ std::unique_ptr<QRhiRenderPassDescriptor> rp(rt->newCompatibleRenderPassDescriptor());
+ rt->setRenderPassDescriptor(rp.get());
+
+ if (!rt->create()) {
+ qWarning("Failed to build multiview texture render target for QQuickRenderTarget");
+ return false;
+ }
+
+ dst->rt.renderTarget = rt.release();
+ dst->rt.owns = true;
+ dst->res.rpDesc = rp.release();
+ if (depthStencil)
+ dst->implicitBuffers.depthStencilTexture = depthStencil.release();
+ if (multisampleResolve)
+ dst->implicitBuffers.multisampleTexture = colorBuffer.release();
+
+ dst->rt.multiViewCount = arraySize;
return true;
}
bool QQuickRenderTargetPrivate::resolve(QRhi *rhi, QQuickWindowRenderTarget *dst)
{
+ // dst->implicitBuffers may contain valid objects. If so, and their
+ // properties are suitable, they are expected to be reused. Once taken what
+ // we can reuse, it needs to be reset().
+
switch (type) {
case Type::Null:
- dst->renderTarget = nullptr;
- dst->paintDevice = nullptr;
- dst->owns = false;
+ dst->implicitBuffers.reset(rhi);
return true;
case Type::NativeTexture:
{
- const auto format = u.nativeTexture.rhiFormat == QRhiTexture::UnknownFormat ? QRhiTexture::RGBA8
- : QRhiTexture::Format(u.nativeTexture.rhiFormat);
- const auto flags = QRhiTexture::RenderTarget | QRhiTexture::Flags(u.nativeTexture.rhiFlags);
- std::unique_ptr<QRhiTexture> texture(rhi->newTexture(format, pixelSize, sampleCount, flags));
- if (!texture->createFrom({ u.nativeTexture.object, u.nativeTexture.layout })) {
+ QRhiTexture::Format format = u.nativeTexture.rhiFormat == QRhiTexture::UnknownFormat ? QRhiTexture::RGBA8
+ : QRhiTexture::Format(u.nativeTexture.rhiFormat);
+ QRhiTexture::Format viewFormat = u.nativeTexture.rhiViewFormat == QRhiTexture::UnknownFormat ? QRhiTexture::RGBA8
+ : QRhiTexture::Format(u.nativeTexture.rhiViewFormat);
+ const auto flags = QRhiTexture::RenderTarget | QRhiTexture::Flags(u.nativeTexture.rhiFormatFlags);
+ std::unique_ptr<QRhiTexture> texture(rhi->newTexture(format, pixelSize, multisampleResolve ? 1 : sampleCount, flags));
+ const bool textureIsSrgb = flags.testFlag(QRhiTexture::sRGB);
+ const bool viewIsSrgb = QRhiTexture::Flags(u.nativeTexture.rhiViewFormatFlags).testFlag(QRhiTexture::sRGB);
+ if (viewFormat != format || viewIsSrgb != textureIsSrgb)
+ texture->setWriteViewFormat({ viewFormat, viewIsSrgb });
+ if (!texture->createFrom({ u.nativeTexture.object, u.nativeTexture.layoutOrState })) {
qWarning("Failed to build wrapper texture for QQuickRenderTarget");
return false;
}
- QRhiColorAttachment att(texture.get());
- if (!createRhiRenderTarget(att, pixelSize, sampleCount, rhi, dst))
+ if (customDepthTexture) {
+ if (!createRhiRenderTargetWithDepthTexture(texture.get(), customDepthTexture, pixelSize, sampleCount, multisampleResolve, rhi, dst))
+ return false;
+ } else {
+ if (!createRhiRenderTarget(texture.get(), pixelSize, sampleCount, multisampleResolve, rhi, dst))
+ return false;
+ }
+ dst->res.texture = texture.release();
+ }
+ return true;
+
+ case Type::NativeTextureArray:
+ {
+ QRhiTexture::Format format = u.nativeTextureArray.rhiFormat == QRhiTexture::UnknownFormat ? QRhiTexture::RGBA8
+ : QRhiTexture::Format(u.nativeTextureArray.rhiFormat);
+ QRhiTexture::Format viewFormat = u.nativeTextureArray.rhiViewFormat == QRhiTexture::UnknownFormat ? QRhiTexture::RGBA8
+ : QRhiTexture::Format(u.nativeTextureArray.rhiViewFormat);
+ const auto flags = QRhiTexture::RenderTarget | QRhiTexture::Flags(u.nativeTextureArray.rhiFormatFlags);
+ const int arraySize = u.nativeTextureArray.arraySize;
+ std::unique_ptr<QRhiTexture> texture(rhi->newTextureArray(format, arraySize, pixelSize, multisampleResolve ? 1 : sampleCount, flags));
+ const bool textureIsSrgb = flags.testFlag(QRhiTexture::sRGB);
+ const bool viewIsSrgb = QRhiTexture::Flags(u.nativeTextureArray.rhiViewFormatFlags).testFlag(QRhiTexture::sRGB);
+ if (viewFormat != format || viewIsSrgb != textureIsSrgb)
+ texture->setWriteViewFormat({ viewFormat, viewIsSrgb });
+ if (!texture->createFrom({ u.nativeTextureArray.object, u.nativeTextureArray.layoutOrState })) {
+ qWarning("Failed to build wrapper texture array for QQuickRenderTarget");
return false;
- dst->texture = texture.release();
+ }
+ if (!createRhiRenderTargetMultiView(texture.get(), customDepthTexture, pixelSize, arraySize, sampleCount, multisampleResolve, rhi, dst))
+ return false;
+ dst->res.texture = texture.release();
}
return true;
@@ -726,27 +1719,59 @@ bool QQuickRenderTargetPrivate::resolve(QRhi *rhi, QQuickWindowRenderTarget *dst
qWarning("Failed to build wrapper renderbuffer for QQuickRenderTarget");
return false;
}
- QRhiColorAttachment att(renderbuffer.get());
- if (!createRhiRenderTarget(att, pixelSize, sampleCount, rhi, dst))
+ if (customDepthTexture)
+ qWarning("Custom depth texture is not supported with renderbuffers in QQuickRenderTarget");
+ if (!createRhiRenderTargetWithRenderBuffer(renderbuffer.get(), pixelSize, sampleCount, rhi, dst))
return false;
- dst->renderBuffer = renderbuffer.release();
+ dst->res.renderBuffer = renderbuffer.release();
}
return true;
case Type::RhiRenderTarget:
- dst->renderTarget = u.rhiRt;
- dst->rpDesc = u.rhiRt->renderPassDescriptor(); // just for QQuickWindowRenderTarget::reset()
- dst->owns = false;
+ dst->implicitBuffers.reset(rhi);
+ dst->rt.renderTarget = u.rhiRt;
+ dst->rt.owns = false;
+ if (dst->rt.renderTarget->resourceType() == QRhiResource::TextureRenderTarget) {
+ auto texRt = static_cast<QRhiTextureRenderTarget *>(dst->rt.renderTarget);
+ const QRhiTextureRenderTargetDescription desc = texRt->description();
+ bool first = true;
+ for (auto it = desc.cbeginColorAttachments(), end = desc.cendColorAttachments(); it != end; ++it) {
+ if (it->multiViewCount() <= 1)
+ continue;
+ if (first || dst->rt.multiViewCount == it->multiViewCount()) {
+ first = false;
+ if (it->texture() && it->texture()->flags().testFlag(QRhiTexture::TextureArray)) {
+ if (it->texture()->arraySize() >= it->layer() + it->multiViewCount()) {
+ dst->rt.multiViewCount = it->multiViewCount();
+ } else {
+ qWarning("Invalid QQuickRenderTarget; needs at least %d elements in texture array, got %d",
+ it->layer() + it->multiViewCount(),
+ it->texture()->arraySize());
+ return false;
+ }
+ } else {
+ qWarning("Invalid QQuickRenderTarget; multiview requires a texture array");
+ return false;
+ }
+ } else {
+ qWarning("Inconsistent multiViewCount in QQuickRenderTarget (was %d, now found an attachment with %d)",
+ dst->rt.multiViewCount, it->multiViewCount());
+ return false;
+ }
+ }
+ }
+ if (customDepthTexture)
+ qWarning("Custom depth texture is not supported with QRhiRenderTarget in QQuickRenderTarget");
return true;
+
case Type::PaintDevice:
- dst->paintDevice = u.paintDevice;
- dst->owns = false;
+ dst->implicitBuffers.reset(rhi);
+ dst->sw.paintDevice = u.paintDevice;
+ dst->sw.owns = false;
return true;
-
- default:
- break;
}
- return false;
+
+ Q_UNREACHABLE_RETURN(false);
}
QT_END_NAMESPACE
diff --git a/src/quick/items/qquickrendertarget.h b/src/quick/items/qquickrendertarget.h
index ded9c80274..1b93db7695 100644
--- a/src/quick/items/qquickrendertarget.h
+++ b/src/quick/items/qquickrendertarget.h
@@ -11,7 +11,7 @@
#include <QtGui/qvulkaninstance.h>
#endif
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) || defined(Q_QDOC)
+#if QT_CONFIG(metal) || defined(Q_QDOC)
Q_FORWARD_DECLARE_OBJC_CLASS(MTLTexture);
#endif
@@ -19,11 +19,17 @@ QT_BEGIN_NAMESPACE
class QQuickRenderTargetPrivate;
class QRhiRenderTarget;
+class QRhiTexture;
class QPaintDevice;
class Q_QUICK_EXPORT QQuickRenderTarget
{
public:
+ enum class Flag {
+ MultisampleResolve = 0x01
+ };
+ Q_DECLARE_FLAGS(Flags, Flag)
+
QQuickRenderTarget();
~QQuickRenderTarget();
QQuickRenderTarget(const QQuickRenderTarget &other);
@@ -37,25 +43,36 @@ public:
bool mirrorVertically() const;
void setMirrorVertically(bool enable);
+ QRhiTexture *depthTexture() const;
+ void setDepthTexture(QRhiTexture *texture);
+
#if QT_CONFIG(opengl) || defined(Q_QDOC)
- static QQuickRenderTarget fromOpenGLTexture(uint textureId, uint format, const QSize &pixelSize, int sampleCount = 1);
static QQuickRenderTarget fromOpenGLTexture(uint textureId, const QSize &pixelSize, int sampleCount = 1);
+ static QQuickRenderTarget fromOpenGLTexture(uint textureId, uint format, const QSize &pixelSize, int sampleCount = 1);
+ static QQuickRenderTarget fromOpenGLTexture(uint textureId, uint format, const QSize &pixelSize, int sampleCount, int arraySize, Flags flags);
+
static QQuickRenderTarget fromOpenGLRenderBuffer(uint renderbufferId, const QSize &pixelSize, int sampleCount = 1);
#endif
#if defined(Q_OS_WIN) || defined(Q_QDOC)
- static QQuickRenderTarget fromD3D11Texture(void *texture, uint format, const QSize &pixelSize, int sampleCount = 1);
static QQuickRenderTarget fromD3D11Texture(void *texture, const QSize &pixelSize, int sampleCount = 1);
+ static QQuickRenderTarget fromD3D11Texture(void *texture, uint format, const QSize &pixelSize, int sampleCount = 1);
+ static QQuickRenderTarget fromD3D11Texture(void *texture, uint format, const QSize &pixelSize, int sampleCount, Flags flags);
+
+ static QQuickRenderTarget fromD3D12Texture(void *texture, int resourceState, uint format, const QSize &pixelSize, int sampleCount = 1);
+ static QQuickRenderTarget fromD3D12Texture(void *texture, int resourceState, uint format, uint viewFormat, const QSize &pixelSize, int sampleCount, int arraySize, Flags flags);
#endif
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) || defined(Q_QDOC)
- static QQuickRenderTarget fromMetalTexture(MTLTexture *texture, uint format, const QSize &pixelSize, int sampleCount = 1);
+#if QT_CONFIG(metal) || defined(Q_QDOC)
static QQuickRenderTarget fromMetalTexture(MTLTexture *texture, const QSize &pixelSize, int sampleCount = 1);
+ static QQuickRenderTarget fromMetalTexture(MTLTexture *texture, uint format, const QSize &pixelSize, int sampleCount = 1);
+ static QQuickRenderTarget fromMetalTexture(MTLTexture *texture, uint format, uint viewFormat, const QSize &pixelSize, int sampleCount, int arraySize, Flags flags);
#endif
#if QT_CONFIG(vulkan) || defined(Q_QDOC)
- static QQuickRenderTarget fromVulkanImage(VkImage image, VkImageLayout layout, VkFormat format, const QSize &pixelSize, int sampleCount = 1);
static QQuickRenderTarget fromVulkanImage(VkImage image, VkImageLayout layout, const QSize &pixelSize, int sampleCount = 1);
+ static QQuickRenderTarget fromVulkanImage(VkImage image, VkImageLayout layout, VkFormat format, const QSize &pixelSize, int sampleCount = 1);
+ static QQuickRenderTarget fromVulkanImage(VkImage image, VkImageLayout layout, VkFormat format, VkFormat viewFormat, const QSize &pixelSize, int sampleCount, int arraySize, Flags flags);
#endif
static QQuickRenderTarget fromRhiRenderTarget(QRhiRenderTarget *renderTarget);
@@ -74,6 +91,8 @@ private:
{ return !lhs.isEqual(rhs); }
};
+Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickRenderTarget::Flags)
+
QT_END_NAMESPACE
#endif // QQUICKRENDERTARGET_H
diff --git a/src/quick/items/qquickrendertarget_p.h b/src/quick/items/qquickrendertarget_p.h
index dd781709af..d2e9962693 100644
--- a/src/quick/items/qquickrendertarget_p.h
+++ b/src/quick/items/qquickrendertarget_p.h
@@ -22,20 +22,21 @@
QT_BEGIN_NAMESPACE
class QRhi;
-class QQuickWindowRenderTarget;
+struct QQuickWindowRenderTarget;
-class Q_QUICK_PRIVATE_EXPORT QQuickRenderTargetPrivate
+class Q_QUICK_EXPORT QQuickRenderTargetPrivate
{
public:
static QQuickRenderTargetPrivate *get(QQuickRenderTarget *rt) { return rt->d; }
static const QQuickRenderTargetPrivate *get(const QQuickRenderTarget *rt) { return rt->d; }
QQuickRenderTargetPrivate();
- QQuickRenderTargetPrivate(const QQuickRenderTargetPrivate *other);
+ QQuickRenderTargetPrivate(const QQuickRenderTargetPrivate &other);
bool resolve(QRhi *rhi, QQuickWindowRenderTarget *dst);
enum class Type {
Null,
NativeTexture,
+ NativeTextureArray,
NativeRenderbuffer,
RhiRenderTarget,
PaintDevice
@@ -48,18 +49,32 @@ public:
int sampleCount = 1;
struct NativeTexture {
quint64 object;
- int layout;
+ int layoutOrState;
uint rhiFormat;
- uint rhiFlags;
+ uint rhiFormatFlags;
+ uint rhiViewFormat;
+ uint rhiViewFormatFlags;
+ };
+ struct NativeTextureArray {
+ quint64 object;
+ int layoutOrState;
+ int arraySize;
+ uint rhiFormat;
+ uint rhiFormatFlags;
+ uint rhiViewFormat;
+ uint rhiViewFormatFlags;
};
union {
NativeTexture nativeTexture;
+ NativeTextureArray nativeTextureArray;
quint64 nativeRenderbufferObject;
QRhiRenderTarget *rhiRt;
QPaintDevice *paintDevice;
} u;
+ QRhiTexture *customDepthTexture = nullptr;
bool mirrorVertically = false;
+ bool multisampleResolve = false;
};
QT_END_NAMESPACE
diff --git a/src/quick/items/qquickrepeater_p.h b/src/quick/items/qquickrepeater_p.h
index 2a3b077feb..e3519b9602 100644
--- a/src/quick/items/qquickrepeater_p.h
+++ b/src/quick/items/qquickrepeater_p.h
@@ -26,7 +26,7 @@ QT_BEGIN_NAMESPACE
class QQmlChangeSet;
class QQuickRepeaterPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickRepeater : public QQuickItem
+class Q_QUICK_EXPORT QQuickRepeater : public QQuickItem
{
Q_OBJECT
@@ -79,6 +79,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickRepeater)
-
#endif // QQUICKREPEATER_P_H
diff --git a/src/quick/items/qquickrhiitem.cpp b/src/quick/items/qquickrhiitem.cpp
new file mode 100644
index 0000000000..b83df7f33a
--- /dev/null
+++ b/src/quick/items/qquickrhiitem.cpp
@@ -0,0 +1,1158 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qquickrhiitem_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QQuickRhiItem
+ \inmodule QtQuick
+ \since 6.7
+
+ \brief The QQuickRhiItem class is a portable alternative to
+ QQuickFramebufferObject that is not tied to OpenGL, but rather allows
+ integrating rendering with the QRhi APIs with Qt Quick.
+
+ \preliminary
+
+ \note QQuickRhiItem is in tech preview in Qt 6.7. \b {The API is under
+ development and subject to change.}
+
+ QQuickRhiItem is effectively the counterpart of \l QRhiWidget in the world of
+ Qt Quick. Both of these are meant to be subclassed, and they both enable
+ recording QRhi-based rendering that targets an offscreen color buffer. The
+ resulting 2D image is then composited with the rest of the Qt Quick scene.
+
+ \note While QQuickRhiItem is a public Qt API, the QRhi family of classes in
+ the Qt Gui module, including QShader and QShaderDescription, offer limited
+ compatibility guarantees. There are no source or binary compatibility
+ guarantees for these classes, meaning the API is only guaranteed to work
+ with the Qt version the application was developed against. Source
+ incompatible changes are however aimed to be kept at a minimum and will
+ only be made in minor releases (6.7, 6.8, and so on). \c{qquickrhiitem.h}
+ does not directly include any QRhi-related headers. To use those classes
+ when implementing a QQuickRhiItem subclass, link to
+ \c{Qt::GuiPrivate} (if using CMake), and include the appropriate headers
+ with the \c rhi prefix, for example \c{#include <rhi/qrhi.h>}.
+
+ QQuickRhiItem is a replacement for the legacy \l QQuickFramebufferObject
+ class. The latter is inherently tied to OpenGL / OpenGL ES, whereas
+ QQuickRhiItem works with the QRhi classes, allowing to run the same
+ rendering code with Vulkan, Metal, Direct 3D 11/12, and OpenGL / OpenGL ES.
+ Conceptually and functionally they are very close, and migrating from
+ QQuickFramebufferObject to QQuickRhiItem is straightforward.
+ QQuickFramebufferObject continues to be available to ensure compatibility
+ for existing application code that works directly with the OpenGL API.
+
+ \note QQuickRhiItem will not be functional when using the \c software
+ adaptation of the Qt Quick scene graph.
+
+ On most platforms, the scene graph rendering, and thus the rendering
+ performed by the QQuickRhiItem will occur on a \l {Scene Graph and
+ Rendering}{dedicated thread}. For this reason, the QQuickRhiItem class
+ enforces a strict separation between the item implementation (the
+ QQuickItem subclass) and the actual rendering logic. All item logic, such
+ as properties and UI-related helper functions exposed to QML must be
+ located in the QQuickRhiItem subclass. Everything that relates to rendering
+ must be located in the QQuickRhiItemRenderer class. To avoid race
+ conditions and read/write issues from two threads it is important that the
+ renderer and the item never read or write shared variables. Communication
+ between the item and the renderer should primarily happen via the
+ QQuickRhiItem::synchronize() function. This function will be called on the
+ render thread while the GUI thread is blocked. Using queued connections or
+ events for communication between item and renderer is also possible.
+
+ Applications must subclass both QQuickRhiItem and QQuickRhiItemRenderer.
+ The pure virtual createRenderer() function must be reimplemented to return
+ a new instance of the QQuickRhiItemRenderer subclass.
+
+ As with QRhiWidget, QQuickRhiItem automatically managed the color buffer,
+ which is a 2D texture (QRhiTexture) normally, or a QRhiRenderBuffer when
+ multisampling is in use. (some 3D APIs differentiate between textures and
+ renderbuffers, while with some others the underlying native resource is the
+ same; renderbuffers are used mainly to allow multisampling with OpenGL ES
+ 3.0)
+
+ The size of the texture will by default adapt to the size of the item (with
+ the \l{QQuickWindow::effectiveDevicePixelRatio()}{device pixel ratio} taken
+ into account). If the item size changes, the texture is recreated with the
+ correct size. If a fixed size is preferred, set \l fixedColorBufferWidth and
+ \l fixedColorBufferHeight to non-zero values.
+
+ QQuickRhiItem is a \l{QSGTextureProvider}{texture provider} and can be used
+ directly in \l {ShaderEffect}{ShaderEffects} and other classes that consume
+ texture providers.
+
+ While not a primary use case, QQuickRhiItem also allows incorporating
+ rendering code that directly uses a 3D graphics API such as Vulkan, Metal,
+ Direct 3D, or OpenGL. See \l QRhiCommandBuffer::beginExternal() for details
+ on recording native commands within a QRhi render pass, as well as
+ \l QRhiTexture::createFrom() for a way to wrap an existing native texture and
+ then use it with QRhi in a subsequent render pass. See also
+ \l QQuickGraphicsConfiguration regarding configuring the native 3D API
+ environment (e.g. device extensions) and note that the \l QQuickWindow can be
+ associated with a custom \l QVulkanInstance by calling
+ \l QWindow::setVulkanInstance() early enough.
+
+ \note QQuickRhiItem always uses the same QRhi instance the QQuickWindow
+ uses (and by extension, the same OpenGL context, Vulkan device, etc.). To
+ choose which underlying 3D graphics API is used, call
+ \l{QQuickWindow::setGraphicsApi()}{setGraphicsApi()} on the QQuickWindow
+ early enough. Changing it is not possible once the scene graph has
+ initialized, and all QQuickRhiItem instances in the scene will render using
+ the same 3D API.
+
+ \section2 A simple example
+
+ Take the following subclass of QQuickRhiItem. It is shown here in complete
+ form. It renders a single triangle with a perspective projection, where the
+ triangle is rotated based on the \c angle property of the custom item.
+ (meaning it can be driven for example with animations such as
+ \l NumberAnimation from QML)
+
+ \snippet qquickrhiitem/qquickrhiitem_intro.cpp 0
+
+ It is notable that this simple class is almost exactly the same as the code
+ shown in the \l QRhiWidget introduction. The vertex and fragment shaders are
+ the same as well. These are provided as Vulkan-style GLSL source code and
+ must be processed first by the Qt shader infrastructure first. This is
+ achieved either by running the \c qsb command-line tool manually, or by
+ using the \l{Qt Shader Tools Build System Integration}{qt_add_shaders()}
+ function in CMake. The QQuickRhiItem loads these pre-processed \c{.qsb}
+ files that are shipped with the application. See \l{Qt Shader Tools} for
+ more information about Qt's shader translation infrastructure.
+
+ \c{color.vert}
+
+ \snippet qquickrhiitem/qquickrhiitem_intro.vert 0
+
+ \c{color.frag}
+
+ \snippet qquickrhiitem/qquickrhiitem_intro.frag 0
+
+ Once exposed to QML (note the \c QML_NAMED_ELEMENT), our custom item can be
+ instantiated in any scene. (after importing the appropriate \c URI specified
+ for \l{qt6_add_qml_module}{qt_add_qml_module} in the CMake project)
+
+ \code
+ ExampleRhiItem {
+ anchors.fill: parent
+ anchors.margins: 10
+ NumberAnimation on angle { from: 0; to: 360; duration: 5000; loops: Animation.Infinite }
+ }
+ \endcode
+
+ See \l{Scene Graph - RHI Texture Item} for a more complex example.
+
+ \sa QQuickRhiItemRenderer, {Scene Graph - RHI Texture Item}, QRhi, {Scene Graph and Rendering}
+ */
+
+/*!
+ \class QQuickRhiItemRenderer
+ \inmodule QtQuick
+ \since 6.7
+
+ \brief A QQuickRhiItemRenderer implements the rendering logic of a
+ QQuickRhiItem.
+
+ \preliminary
+
+ \note QQuickRhiItem and QQuickRhiItemRenderer are in tech preview in Qt
+ 6.7. \b {The API is under development and subject to change.}
+
+ \sa QQuickRhiItem, QRhi
+ */
+
+QQuickRhiItemNode::QQuickRhiItemNode(QQuickRhiItem *item)
+ : m_item(item)
+{
+ m_window = m_item->window();
+ connect(m_window, &QQuickWindow::beforeRendering, this, &QQuickRhiItemNode::render,
+ Qt::DirectConnection);
+ connect(m_window, &QQuickWindow::screenChanged, this, [this]() {
+ if (m_window->effectiveDevicePixelRatio() != m_dpr)
+ m_item->update();
+ }, Qt::DirectConnection);
+}
+
+QSGTexture *QQuickRhiItemNode::texture() const
+{
+ return m_sgTexture.get();
+}
+
+void QQuickRhiItemNode::resetColorBufferObjects()
+{
+ // owns either m_colorTexture or m_resolveTexture
+ m_sgTexture.reset();
+
+ m_colorTexture = nullptr;
+ m_resolveTexture = nullptr;
+
+ m_msaaColorBuffer.reset();
+}
+
+void QQuickRhiItemNode::resetRenderTargetObjects()
+{
+ m_renderTarget.reset();
+ m_renderPassDescriptor.reset();
+ m_depthStencilBuffer.reset();
+}
+
+void QQuickRhiItemNode::sync()
+{
+ if (!m_rhi) {
+ m_rhi = m_window->rhi();
+ if (!m_rhi) {
+ qWarning("No QRhi found for window %p, QQuickRhiItem will not be functional", m_window);
+ return;
+ }
+ }
+
+ m_dpr = m_window->effectiveDevicePixelRatio();
+ const int minTexSize = m_rhi->resourceLimit(QRhi::TextureSizeMin);
+ const int maxTexSize = m_rhi->resourceLimit(QRhi::TextureSizeMax);
+
+ QQuickRhiItemPrivate *itemD = m_item->d_func();
+ QSize newSize = QSize(itemD->fixedTextureWidth, itemD->fixedTextureHeight);
+ if (newSize.isEmpty())
+ newSize = QSize(int(m_item->width()), int(m_item->height())) * m_dpr;
+
+ newSize.setWidth(qMin(maxTexSize, qMax(minTexSize, newSize.width())));
+ newSize.setHeight(qMin(maxTexSize, qMax(minTexSize, newSize.height())));
+
+ if (m_colorTexture) {
+ if (m_colorTexture->format() != itemD->rhiTextureFormat
+ || m_colorTexture->sampleCount() != itemD->samples)
+ {
+ resetColorBufferObjects();
+ resetRenderTargetObjects();
+ }
+ }
+
+ if (m_msaaColorBuffer) {
+ if (m_msaaColorBuffer->backingFormat() != itemD->rhiTextureFormat
+ || m_msaaColorBuffer->sampleCount() != itemD->samples)
+ {
+ resetColorBufferObjects();
+ resetRenderTargetObjects();
+ }
+ }
+
+ if (m_sgTexture && m_sgTexture->hasAlphaChannel() != itemD->blend) {
+ resetColorBufferObjects();
+ resetRenderTargetObjects();
+ }
+
+ if (!m_colorTexture && itemD->samples <= 1) {
+ if (!m_rhi->isTextureFormatSupported(itemD->rhiTextureFormat)) {
+ qWarning("QQuickRhiItem: The requested texture format (%d) is not supported by the "
+ "underlying 3D graphics API implementation", int(itemD->rhiTextureFormat));
+ }
+ m_colorTexture = m_rhi->newTexture(itemD->rhiTextureFormat, newSize, itemD->samples,
+ QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
+ if (!m_colorTexture->create()) {
+ qWarning("Failed to create backing texture for QQuickRhiItem");
+ delete m_colorTexture;
+ m_colorTexture = nullptr;
+ return;
+ }
+ }
+
+ if (itemD->samples > 1) {
+ if (!m_msaaColorBuffer) {
+ if (!m_rhi->isFeatureSupported(QRhi::MultisampleRenderBuffer)) {
+ qWarning("QQuickRhiItem: Multisample renderbuffers are reported as unsupported; "
+ "sample count %d will not work as expected", itemD->samples);
+ }
+ if (!m_rhi->isTextureFormatSupported(itemD->rhiTextureFormat)) {
+ qWarning("QQuickRhiItem: The requested texture format (%d) is not supported by the "
+ "underlying 3D graphics API implementation", int(itemD->rhiTextureFormat));
+ }
+ m_msaaColorBuffer.reset(m_rhi->newRenderBuffer(QRhiRenderBuffer::Color, newSize, itemD->samples,
+ {}, itemD->rhiTextureFormat));
+ if (!m_msaaColorBuffer->create()) {
+ qWarning("Failed to create multisample color buffer for QQuickRhiItem");
+ m_msaaColorBuffer.reset();
+ return;
+ }
+ }
+ if (!m_resolveTexture) {
+ m_resolveTexture = m_rhi->newTexture(itemD->rhiTextureFormat, newSize, 1,
+ QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
+ if (!m_resolveTexture->create()) {
+ qWarning("Failed to create resolve texture for QQuickRhiItem");
+ delete m_resolveTexture;
+ m_resolveTexture = nullptr;
+ return;
+ }
+ }
+ } else if (m_resolveTexture) {
+ m_resolveTexture->deleteLater();
+ m_resolveTexture = nullptr;
+ }
+
+ if (m_colorTexture && m_colorTexture->pixelSize() != newSize) {
+ m_colorTexture->setPixelSize(newSize);
+ if (!m_colorTexture->create())
+ qWarning("Failed to rebuild texture for QQuickRhiItem after resizing");
+ }
+
+ if (m_msaaColorBuffer && m_msaaColorBuffer->pixelSize() != newSize) {
+ m_msaaColorBuffer->setPixelSize(newSize);
+ if (!m_msaaColorBuffer->create())
+ qWarning("Failed to rebuild multisample color buffer for QQuickRhiitem after resizing");
+ }
+
+ if (m_resolveTexture && m_resolveTexture->pixelSize() != newSize) {
+ m_resolveTexture->setPixelSize(newSize);
+ if (!m_resolveTexture->create())
+ qWarning("Failed to rebuild resolve texture for QQuickRhiItem after resizing");
+ }
+
+ if (!m_sgTexture) {
+ QQuickWindow::CreateTextureOptions options;
+ if (itemD->blend)
+ options |= QQuickWindow::TextureHasAlphaChannel;
+ // the QSGTexture takes ownership of the QRhiTexture
+ m_sgTexture.reset(m_window->createTextureFromRhiTexture(m_colorTexture ? m_colorTexture : m_resolveTexture,
+ options));
+ setTexture(m_sgTexture.get());
+ }
+
+ if (itemD->autoRenderTarget) {
+ const QSize pixelSize = m_colorTexture ? m_colorTexture->pixelSize()
+ : m_msaaColorBuffer->pixelSize();
+ if (!m_depthStencilBuffer) {
+ m_depthStencilBuffer.reset(m_rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, pixelSize, itemD->samples));
+ if (!m_depthStencilBuffer->create()) {
+ qWarning("Failed to create depth-stencil buffer for QQuickRhiItem");
+ resetRenderTargetObjects();
+ return;
+ }
+ } else if (m_depthStencilBuffer->pixelSize() != pixelSize) {
+ m_depthStencilBuffer->setPixelSize(pixelSize);
+ if (!m_depthStencilBuffer->create()) {
+ qWarning("Failed to rebuild depth-stencil buffer for QQuickRhiItem with new size");
+ return;
+ }
+ }
+ if (!m_renderTarget) {
+ QRhiColorAttachment color0;
+ if (m_colorTexture)
+ color0.setTexture(m_colorTexture);
+ else
+ color0.setRenderBuffer(m_msaaColorBuffer.get());
+ if (itemD->samples > 1)
+ color0.setResolveTexture(m_resolveTexture);
+ QRhiTextureRenderTargetDescription rtDesc(color0, m_depthStencilBuffer.get());
+ m_renderTarget.reset(m_rhi->newTextureRenderTarget(rtDesc));
+ m_renderPassDescriptor.reset(m_renderTarget->newCompatibleRenderPassDescriptor());
+ m_renderTarget->setRenderPassDescriptor(m_renderPassDescriptor.get());
+ if (!m_renderTarget->create()) {
+ qWarning("Failed to create render target for QQuickRhiitem");
+ resetRenderTargetObjects();
+ return;
+ }
+ }
+ } else {
+ resetRenderTargetObjects();
+ }
+
+ if (newSize != itemD->effectiveTextureSize) {
+ itemD->effectiveTextureSize = newSize;
+ emit m_item->effectiveColorBufferSizeChanged();
+ }
+
+ QRhiCommandBuffer *cb = queryCommandBuffer();
+ if (cb)
+ m_renderer->initialize(cb);
+
+ m_renderer->synchronize(m_item);
+}
+
+QRhiCommandBuffer *QQuickRhiItemNode::queryCommandBuffer()
+{
+ QRhiSwapChain *swapchain = m_window->swapChain();
+ QSGRendererInterface *rif = m_window->rendererInterface();
+
+ // Handle both cases: on-screen QQuickWindow vs. off-screen QQuickWindow
+ // e.g. by using QQuickRenderControl to redirect into a texture.
+ QRhiCommandBuffer *cb = swapchain ? swapchain->currentFrameCommandBuffer()
+ : static_cast<QRhiCommandBuffer *>(
+ rif->getResource(m_window, QSGRendererInterface::RhiRedirectCommandBuffer));
+
+ if (!cb) {
+ qWarning("QQuickRhiItem: Neither swapchain nor redirected command buffer are available.");
+ return nullptr;
+ }
+
+ return cb;
+}
+
+void QQuickRhiItemNode::render()
+{
+ // called before Qt Quick starts recording its main render pass
+
+ if (!isValid() || !m_renderPending)
+ return;
+
+ QRhiCommandBuffer *cb = queryCommandBuffer();
+ if (!cb)
+ return;
+
+ m_renderPending = false;
+ m_renderer->render(cb);
+
+ markDirty(QSGNode::DirtyMaterial);
+ emit textureChanged();
+}
+
+/*!
+ Constructs a new QQuickRhiItem with the given \a parent.
+ */
+QQuickRhiItem::QQuickRhiItem(QQuickItem *parent)
+ : QQuickItem(*new QQuickRhiItemPrivate, parent)
+{
+ setFlag(ItemHasContents);
+}
+
+/*!
+ Destructor.
+*/
+QQuickRhiItem::~QQuickRhiItem()
+{
+}
+
+/*!
+ \internal
+ */
+QSGNode *QQuickRhiItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
+{
+ // Changing to an empty size should not involve destroying and then later
+ // recreating the node, because we do not know how expensive the user's
+ // renderer setup is. Rather, keep the node if it already exist, and clamp
+ // all accesses to width and height. Hence the unusual !oldNode condition here.
+ if (!oldNode && (width() <= 0 || height() <= 0))
+ return nullptr;
+
+ Q_D(QQuickRhiItem);
+ QQuickRhiItemNode *n = static_cast<QQuickRhiItemNode *>(oldNode);
+ if (!n) {
+ if (!d->node)
+ d->node = new QQuickRhiItemNode(this);
+ if (!d->node->hasRenderer()) {
+ QQuickRhiItemRenderer *r = createRenderer();
+ if (r) {
+ r->node = d->node;
+ d->node->setRenderer(r);
+ } else {
+ qWarning("No QQuickRhiItemRenderer was created; the item will not render");
+ delete d->node;
+ d->node = nullptr;
+ return nullptr;
+ }
+ }
+ n = d->node;
+ }
+
+ n->sync();
+
+ if (!n->isValid()) {
+ delete n;
+ d->node = nullptr;
+ return nullptr;
+ }
+
+ if (window()->rhi()->isYUpInFramebuffer()) {
+ n->setTextureCoordinatesTransform(d->mirrorVertically
+ ? QSGSimpleTextureNode::NoTransform
+ : QSGSimpleTextureNode::MirrorVertically);
+ } else {
+ n->setTextureCoordinatesTransform(d->mirrorVertically
+ ? QSGSimpleTextureNode::MirrorVertically
+ : QSGSimpleTextureNode::NoTransform);
+ }
+ n->setFiltering(d->smooth ? QSGTexture::Linear : QSGTexture::Nearest);
+ n->setRect(0, 0, qMax<int>(0, width()), qMax<int>(0, height()));
+
+ n->scheduleUpdate();
+
+ return n;
+}
+
+/*!
+ \reimp
+ */
+bool QQuickRhiItem::event(QEvent *e)
+{
+ return QQuickItem::event(e);
+}
+
+/*!
+ \reimp
+ */
+void QQuickRhiItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
+{
+ QQuickItem::geometryChange(newGeometry, oldGeometry);
+ if (newGeometry.size() != oldGeometry.size())
+ update();
+}
+
+/*!
+ \reimp
+ */
+void QQuickRhiItem::releaseResources()
+{
+ // called on the gui thread if the item is removed from scene
+
+ Q_D(QQuickRhiItem);
+ d->node = nullptr;
+}
+
+void QQuickRhiItem::invalidateSceneGraph()
+{
+ // called on the render thread when the scenegraph is invalidated
+
+ Q_D(QQuickRhiItem);
+ d->node = nullptr;
+}
+
+/*!
+ \reimp
+ */
+bool QQuickRhiItem::isTextureProvider() const
+{
+ return true;
+}
+
+/*!
+ \reimp
+ */
+QSGTextureProvider *QQuickRhiItem::textureProvider() const
+{
+ if (QQuickItem::isTextureProvider()) // e.g. if Item::layer::enabled == true
+ return QQuickItem::textureProvider();
+
+ Q_D(const QQuickRhiItem);
+ if (!d->node) // create a node to have a provider, the texture will be null but that's ok
+ d->node = new QQuickRhiItemNode(const_cast<QQuickRhiItem *>(this));
+
+ return d->node;
+}
+
+/*!
+ \property QQuickRhiItem::sampleCount
+
+ This property controls for sample count for multisample antialiasing.
+ By default the value is \c 1 which means MSAA is disabled.
+
+ Valid values are 1, 4, 8, and sometimes 16 and 32.
+ \l QRhi::supportedSampleCounts() can be used to query the supported sample
+ counts at run time, but typically applications should request 1 (no MSAA),
+ 4x (normal MSAA) or 8x (high MSAA).
+
+ \note Setting a new value implies that all QRhiGraphicsPipeline objects
+ created by the renderer must use the same sample count from then on.
+ Existing QRhiGraphicsPipeline objects created with a different sample count
+ must not be used anymore. When the value changes, all color and
+ depth-stencil buffers are destroyed and recreated automatically, and
+ \l {QQuickRhiItemRenderer::}{initialize()} is invoked again. However, when
+ isAutoRenderTargetEnabled() is \c false, it will be up to the application to
+ manage this with regards to the depth-stencil buffer or additional color
+ buffers.
+
+ Changing the sample count from the default 1 to a higher value implies that
+ \l {QQuickRhiItemRenderer::}{colorTexture()} becomes \nullptr and
+ \l {QQuickRhiItemRenderer::}{msaaColorBuffer()} starts returning a
+ valid object. Switching back to 1 (or 0), implies the opposite: in the next
+ call to initialize() msaaColorBuffer() is going to return \nullptr, whereas
+ colorTexture() becomes once again valid. In addition,
+ \l {QQuickRhiItemRenderer::}{resolveTexture()}
+ returns a valid (non-multisample) QRhiTexture whenever the sample count is
+ greater than 1 (i.e., MSAA is in use).
+
+ \sa QQuickRhiItemRenderer::msaaColorBuffer(),
+ QQuickRhiItemRenderer::resolveTexture()
+ */
+
+int QQuickRhiItem::sampleCount() const
+{
+ Q_D(const QQuickRhiItem);
+ return d->samples;
+}
+
+void QQuickRhiItem::setSampleCount(int samples)
+{
+ Q_D(QQuickRhiItem);
+ if (d->samples == samples)
+ return;
+
+ d->samples = samples;
+ emit sampleCountChanged();
+ update();
+}
+
+/*!
+ \property QQuickRhiItem::colorBufferFormat
+
+ This property controls the texture format for the texture used as the color
+ buffer. The default value is TextureFormat::RGBA8. QQuickRhiItem supports
+ rendering to a subset of the formats supported by \l QRhiTexture. Only
+ formats that are reported as supported from
+ \l QRhi::isTextureFormatSupported() should be specified, rendering will not be
+ functional otherwise.
+
+ \note Setting a new format when the item and its renderer are already
+ initialized and have rendered implies that all QRhiGraphicsPipeline objects
+ created by the renderer may become unusable, if the associated
+ QRhiRenderPassDescriptor is now incompatible due to the different texture
+ format. Similarly to changing
+ \l sampleCount dynamically, this means that initialize() or render()
+ implementations must then take care of releasing the existing pipelines and
+ creating new ones.
+ */
+
+QQuickRhiItem::TextureFormat QQuickRhiItem::colorBufferFormat() const
+{
+ Q_D(const QQuickRhiItem);
+ return d->itemTextureFormat;
+}
+
+void QQuickRhiItem::setColorBufferFormat(TextureFormat format)
+{
+ Q_D(QQuickRhiItem);
+ if (d->itemTextureFormat == format)
+ return;
+
+ d->itemTextureFormat = format;
+ switch (format) {
+ case TextureFormat::RGBA8:
+ d->rhiTextureFormat = QRhiTexture::RGBA8;
+ break;
+ case TextureFormat::RGBA16F:
+ d->rhiTextureFormat = QRhiTexture::RGBA16F;
+ break;
+ case TextureFormat::RGBA32F:
+ d->rhiTextureFormat = QRhiTexture::RGBA32F;
+ break;
+ case TextureFormat::RGB10A2:
+ d->rhiTextureFormat = QRhiTexture::RGB10A2;
+ break;
+ }
+ emit colorBufferFormatChanged();
+ update();
+}
+
+/*!
+ \return the current automatic depth-stencil buffer and render target management setting.
+
+ By default this value is \c true.
+
+ \sa setAutoRenderTarget()
+ */
+bool QQuickRhiItem::isAutoRenderTargetEnabled() const
+{
+ Q_D(const QQuickRhiItem);
+ return d->autoRenderTarget;
+}
+
+/*!
+ Controls if a depth-stencil QRhiRenderBuffer and a QRhiTextureRenderTarget
+ is created and maintained automatically by the item. The default value is
+ \c true. Call this function early on, for example from the derived class'
+ constructor, with \a enabled set to \c false to disable this.
+
+ In automatic mode, the size and sample count of the depth-stencil buffer
+ follows the color buffer texture's settings. In non-automatic mode,
+ renderTarget() and depthStencilBuffer() always return \nullptr and it is
+ then up to the application's implementation of initialize() to take care of
+ setting up and managing these objects.
+ */
+void QQuickRhiItem::setAutoRenderTarget(bool enabled)
+{
+ Q_D(QQuickRhiItem);
+ if (d->autoRenderTarget == enabled)
+ return;
+
+ d->autoRenderTarget = enabled;
+ emit autoRenderTargetChanged();
+ update();
+}
+
+/*!
+ \property QQuickRhiItem::mirrorVertically
+
+ This property controls if texture UVs are flipped when drawing the textured
+ quad. It has no effect on the contents of the offscreen color buffer and
+ the rendering implemented by the QQuickRhiItemRenderer.
+
+ The default value is \c false.
+ */
+
+bool QQuickRhiItem::isMirrorVerticallyEnabled() const
+{
+ Q_D(const QQuickRhiItem);
+ return d->mirrorVertically;
+}
+
+void QQuickRhiItem::setMirrorVertically(bool enable)
+{
+ Q_D(QQuickRhiItem);
+ if (d->mirrorVertically == enable)
+ return;
+
+ d->mirrorVertically = enable;
+ emit mirrorVerticallyChanged();
+ update();
+}
+
+/*!
+ \property QQuickRhiItem::fixedColorBufferWidth
+
+ The fixed width, in pixels, of the item's associated texture or
+ renderbuffer. Relevant when a fixed color buffer size is desired that does
+ not depend on the item's size. This size has no effect on the geometry of
+ the item (its size and placement within the scene), which means the
+ texture's content will appear stretched (scaled up) or scaled down onto the
+ item's area.
+
+ For example, setting a size that is exactly twice the item's (pixel) size
+ effectively performs 2x supersampling (rendering at twice the resolution
+ and then implicitly scaling down when texturing the quad corresponding to
+ the item in the scene).
+
+ By default the value is \c 0. A value of 0 means that texture's size
+ follows the item's size. (\c{texture size} = \c{item size} * \c{device
+ pixel ratio}).
+ */
+int QQuickRhiItem::fixedColorBufferWidth() const
+{
+ Q_D(const QQuickRhiItem);
+ return d->fixedTextureWidth;
+}
+
+void QQuickRhiItem::setFixedColorBufferWidth(int width)
+{
+ Q_D(QQuickRhiItem);
+ if (d->fixedTextureWidth == width)
+ return;
+
+ d->fixedTextureWidth = width;
+ emit fixedColorBufferWidthChanged();
+ update();
+}
+
+/*!
+ \property QQuickRhiItem::fixedColorBufferHeight
+
+ The fixed height, in pixels, of the item's associated texture. Relevant when
+ a fixed texture size is desired that does not depend on the item's size.
+ This size has no effect on the geometry of the item (its size and placement
+ within the scene), which means the texture's content will appear stretched
+ (scaled up) or scaled down onto the item's area.
+
+ For example, setting a size that is exactly twice the item's (pixel) size
+ effectively performs 2x supersampling (rendering at twice the resolution
+ and then implicitly scaling down when texturing the quad corresponding to
+ the item in the scene).
+
+ By default the value is \c 0. A value of 0 means that texture's size
+ follows the item's size. (\c{texture size} = \c{item size} * \c{device
+ pixel ratio}).
+ */
+
+int QQuickRhiItem::fixedColorBufferHeight() const
+{
+ Q_D(const QQuickRhiItem);
+ return d->fixedTextureHeight;
+}
+
+void QQuickRhiItem::setFixedColorBufferHeight(int height)
+{
+ Q_D(QQuickRhiItem);
+ if (d->fixedTextureHeight == height)
+ return;
+
+ d->fixedTextureHeight = height;
+ emit fixedColorBufferHeightChanged();
+ update();
+}
+
+/*!
+ \property QQuickRhiItem::effectiveColorBufferSize
+
+ This property exposes the size, in pixels, of the underlying color buffer
+ (the QRhiTexture or QRhiRenderBuffer). It is provided for use on the GUI
+ (main) thread, in QML bindings or JavaScript.
+
+ \note QQuickRhiItemRenderer implementations, operating on the scene graph
+ render thread, should not use this property. Those should rather query the
+ size from the
+ \l{QQuickRhiItemRenderer::renderTarget()}{render target}.
+
+ \note The value becomes available asynchronously from the main thread's
+ perspective in the sense that the value changes when rendering happens on
+ the render thread. This means that this property is useful mainly in QML
+ bindings. Application code must not assume that the value is up to date
+ already when the QQuickRhiItem object is constructed.
+
+ This is a read-only property.
+ */
+
+QSize QQuickRhiItem::effectiveColorBufferSize() const
+{
+ Q_D(const QQuickRhiItem);
+ return d->effectiveTextureSize;
+}
+
+/*!
+ \property QQuickRhiItem::alphaBlending
+
+ Controls if blending is always enabled when drawing the quad textured with
+ the content generated by the QQuickRhiItem and its renderer.
+
+ The default value is \c false. This is for performance reasons: if
+ semi-transparency is not involved, because the QQuickRhiItemRenderer clears
+ to an opaque color and never renders fragments with alpha smaller than 1,
+ then there is no point in enabling blending.
+
+ If the QQuickRhiItemRenderer subclass renders with semi-transparency involved,
+ set this property to true.
+
+ \note Under certain conditions blending is still going to happen regardless
+ of the value of this property. For example, if the item's
+ \l{QQuickItem::opacity}{opacity} (more precisely, the combined opacity
+ inherited from the parent chain) is smaller than 1, blending will be
+ automatically enabled even when this property is set to false.
+
+ \note The Qt Quick scene graph relies on and expect pre-multiplied alpha.
+ For example, if the intention is to clear the background in the renderer to
+ an alpha value of 0.5, then make sure to multiply the red, green, and blue
+ clear color values with 0.5 as well. Otherwise the blending results will be
+ incorrect.
+ */
+
+bool QQuickRhiItem::alphaBlending() const
+{
+ Q_D(const QQuickRhiItem);
+ return d->blend;
+}
+
+void QQuickRhiItem::setAlphaBlending(bool enable)
+{
+ Q_D(QQuickRhiItem);
+ if (d->blend == enable)
+ return;
+
+ d->blend = enable;
+ emit alphaBlendingChanged();
+ update();
+}
+
+/*!
+ Constructs a new renderer.
+
+ This function is called on the rendering thread during the scene graph sync
+ phase when the GUI thread is blocked.
+
+ \sa QQuickRhiItem::createRenderer()
+ */
+QQuickRhiItemRenderer::QQuickRhiItemRenderer()
+{
+}
+
+/*!
+ The Renderer is automatically deleted when the scene graph resources for
+ the QQuickRhiItem item are cleaned up.
+
+ This function is called on the rendering thread.
+
+ Under certain conditions it is normal and expected that the renderer object
+ is destroyed and then recreated. This is because the renderer's lifetime
+ effectively follows the underlying scene graph node. For example, when
+ changing the parent of a QQuickRhiItem object so that it then belongs to a
+ different \l QQuickWindow, the scene graph nodes are all dropped and
+ recreated due to the window change. This will also involve dropping and
+ creating a new QQuickRhiItemRenderer.
+
+ Unlike \l QRhiWidget, QQuickRhiItemRenderer has no need to implement
+ additional code paths for releasing (or early-relasing) graphics resources
+ created via QRhi. It is sufficient to release everything in the destructor,
+ or rely on smart pointers.
+ */
+QQuickRhiItemRenderer::~QQuickRhiItemRenderer()
+{
+}
+
+/*!
+ Call this function when the content of the offscreen color buffer should be
+ updated. (i.e. to request that render() is called again; the call will
+ happen at a later point, and note that updates are typically throttled to
+ the presentation rate)
+
+ This function can be called from render() to schedule an update.
+
+ \note This function should be used from inside the renderer. To update
+ the item on the GUI thread, use QQuickRhiItem::update().
+ */
+void QQuickRhiItemRenderer::update()
+{
+ if (node)
+ node->scheduleUpdate();
+}
+
+/*!
+ \return the current QRhi object.
+
+ Must only be called from initialize() and render().
+ */
+QRhi *QQuickRhiItemRenderer::rhi() const
+{
+ return node ? node->m_rhi : nullptr;
+}
+
+/*!
+ \return the texture serving as the color buffer for the item.
+
+ Must only be called from initialize() and render().
+
+ Unlike the depth-stencil buffer and the QRhiRenderTarget, this texture is
+ always available and is managed by the QQuickRhiItem, independent of the
+ value of \l {QQuickRhiItem::}{isAutoRenderTargetEnabled}.
+
+ \note When \l {QQuickRhiItem::}{sampleCount} is larger than 1, and so
+ multisample antialiasing is enabled, the return value is \nullptr. Instead,
+ query the \l QRhiRenderBuffer by calling msaaColorBuffer().
+
+ \note The backing texture size and sample count can also be queried via the
+ QRhiRenderTarget returned from renderTarget(). This can be more convenient
+ and compact than querying from the QRhiTexture or QRhiRenderBuffer, because
+ it works regardless of multisampling is in use or not.
+
+ \sa msaaColorBuffer(), depthStencilBuffer(), renderTarget(), resolveTexture()
+ */
+QRhiTexture *QQuickRhiItemRenderer::colorTexture() const
+{
+ return node ? node->m_colorTexture : nullptr;
+}
+
+/*!
+ \return the renderbuffer serving as the multisample color buffer for the item.
+
+ Must only be called from initialize() and render().
+
+ When \l {QQuickRhiItem::}{sampleCount} is larger than 1, and so multisample
+ antialising is enabled, the returned QRhiRenderBuffer has a matching sample
+ count and serves as the color buffer. Graphics pipelines used to render
+ into this buffer must be created with the same sample count, and the
+ depth-stencil buffer's sample count must match as well. The multisample
+ content is expected to be resolved into the texture returned from
+ resolveTexture(). When \l {QQuickRhiItem::}{isAutoRenderTargetEnabled} is
+ \c true, renderTarget() is set up automatically to do this, by setting up
+ msaaColorBuffer() as the
+ \l{QRhiColorAttachment::renderBuffer()}{renderbuffer} of color attachment 0
+ and resolveTexture() as its
+ \l{QRhiColorAttachment::resolveTexture()}{resolveTexture}.
+
+ When MSAA is not in use, the return value is \nullptr. Use colorTexture()
+ instead then.
+
+ Depending on the underlying 3D graphics API, there may be no practical
+ difference between multisample textures and color renderbuffers with a
+ sample count larger than 1 (QRhi may just map both to the same native
+ resource type). Some older APIs however may differentiate between textures
+ and renderbuffers. In order to support OpenGL ES 3.0, where multisample
+ renderbuffers are available, but multisample textures are not, QQuickRhiItem
+ always performs MSAA by using a multisample QRhiRenderBuffer as the color
+ attachment (and never a multisample QRhiTexture).
+
+ \note The backing texture size and sample count can also be queried via the
+ QRhiRenderTarget returned from renderTarget(). This can be more convenient
+ and compact than querying from the QRhiTexture or QRhiRenderBuffer, because
+ it works regardless of multisampling is in use or not.
+
+ \sa colorTexture(), depthStencilBuffer(), renderTarget(), resolveTexture()
+ */
+QRhiRenderBuffer *QQuickRhiItemRenderer::msaaColorBuffer() const
+{
+ return node ? node->m_msaaColorBuffer.get() : nullptr;
+}
+
+/*!
+ \return the non-multisample texture to which the multisample content is resolved.
+
+ The result is \nullptr when multisample antialiasing is not enabled.
+
+ Must only be called from initialize() and render().
+
+ With MSAA enabled, this is the texture that gets used by the item's
+ underlying scene graph node when texturing a quad in the main render pass
+ of Qt Quick. However, the QQuickRhiItemRenderer's rendering must target the
+ (multisample) QRhiRenderBuffer returned from msaaColorBuffer(). When \l
+ {QQuickRhiItem::}{isAutoRenderTargetEnabled} is \c true, this is taken care
+ of by the QRhiRenderTarget returned from renderTarget(). Otherwise, it is
+ up to the subclass code to correctly configure a render target object with
+ both the color buffer and resolve textures.
+
+ \sa colorTexture()
+ */
+QRhiTexture *QQuickRhiItemRenderer::resolveTexture() const
+{
+ return node ? node->m_resolveTexture : nullptr;
+}
+
+/*!
+ \return the depth-stencil buffer used by the item's rendering.
+
+ Must only be called from initialize() and render().
+
+ Available only when \l {QQuickRhiItem::}{isAutoRenderTargetEnabled} is \c
+ true. Otherwise the returned value is \nullptr and it is up the
+ reimplementation of initialize() to create and manage a depth-stencil
+ buffer and a QRhiTextureRenderTarget.
+
+ \sa colorTexture(), renderTarget()
+ */
+QRhiRenderBuffer *QQuickRhiItemRenderer::depthStencilBuffer() const
+{
+ return node ? node->m_depthStencilBuffer.get() : nullptr;
+}
+
+/*!
+ \return the render target object that must be used with
+ \l QRhiCommandBuffer::beginPass() in reimplementations of render().
+
+ Must only be called from initialize() and render().
+
+ Available only when \l {QQuickRhiItem::}{isAutoRenderTargetEnabled} is \c
+ true. Otherwise the returned value is \nullptr and it is up the
+ reimplementation of initialize() to create and manage a depth-stencil
+ buffer and a QRhiTextureRenderTarget.
+
+ When creating \l{QRhiGraphicsPipeline}{graphics pipelines}, a
+ QRhiRenderPassDescriptor is needed. This can be queried from the returned
+ QRhiTextureRenderTarget by calling
+ \l{QRhiTextureRenderTarget::renderPassDescriptor()}{renderPassDescriptor()}.
+
+ \note The returned QRhiTextureRenderTarget always reports a
+ \l{QRhiTextureRenderTarget::}{devicePixelRatio()} of \c 1.
+ This is because only swapchains and the associated window have a concept of
+ device pixel ratio, not textures, and the render target here always refers
+ to a texture. If the on-screen scale factor is relevant for rendering,
+ query and store it via the item's
+ \c{window()->effectiveDevicePixelRatio()} in \l synchronize().
+ When doing so, always prefer using \l{QQuickWindow::}{effectiveDevicePixelRatio()}
+ over the base class' \l{QWindow::}{devicePixelRatio()}.
+
+ \sa colorTexture(), depthStencilBuffer(), QQuickWindow::effectiveDevicePixelRatio()
+ */
+QRhiRenderTarget *QQuickRhiItemRenderer::renderTarget() const
+{
+ return node ? node->m_renderTarget.get() : nullptr;
+}
+
+/*!
+ \fn QQuickRhiItemRenderer *QQuickRhiItem::createRenderer()
+
+ Reimplement this function to create and return a new instance of a
+ QQuickRhiItemRenderer subclass.
+
+ This function will be called on the rendering thread while the GUI thread
+ is blocked.
+ */
+
+/*!
+ \fn void QQuickRhiItemRenderer::initialize(QRhiCommandBuffer *cb)
+
+ Called when the item is initialized for the first time, when the
+ associated texture's size, format, or sample count changes, or when the
+ QRhi or texture change for any reason. The function is expected to
+ maintain (create if not yet created, adjust and rebuild if the size has
+ changed) the graphics resources used by the rendering code in render().
+
+ To query the QRhi, QRhiTexture, and other related objects, call rhi(),
+ colorTexture(), depthStencilBuffer(), and renderTarget().
+
+ When the item size changes, the QRhi object, the color buffer texture,
+ and the depth stencil buffer objects are all the same instances (so the
+ getters return the same pointers) as before, but the color and
+ depth/stencil buffers will likely have been rebuilt, meaning the
+ \l{QRhiTexture::pixelSize()}{size} and the underlying native texture
+ resource may be different than in the last invocation.
+
+ Reimplementations should also be prepared that the QRhi object and the
+ color buffer texture may change between invocations of this function. For
+ example, when the item is reparented so that it belongs to a new
+ QQuickWindow, the the QRhi and all related resources managed by the
+ QQuickRhiItem will be different instances than before in the subsequent
+ call to this function. Is is then important that all existing QRhi
+ resources previously created by the subclass are destroyed because they
+ belong to the previous QRhi that should not be used anymore.
+
+ When \l {QQuickRhiItem::}{isAutoRenderTargetEnabled} is \c true, which is
+ the default, a depth-stencil QRhiRenderBuffer and a QRhiTextureRenderTarget
+ associated with the colorTexture() (or msaaColorBuffer()) and the
+ depth-stencil buffer are created and managed automatically.
+ Reimplementations of initialize() and render() can query those objects via
+ depthStencilBuffer() and renderTarget(). When \l
+ {QQuickRhiItem::}{isAutoRenderTargetEnabled} is set to \c false, these
+ objects are no longer created and managed automatically. Rather, it will be
+ up the the initialize() implementation to create buffers and set up the
+ render target as it sees fit. When manually managing additional color or
+ depth-stencil attachments for the render target, their size and sample
+ count must always follow the size and sample count of colorTexture() (or
+ msaaColorBuffer()), otherwise rendering or 3D API validation errors may
+ occur.
+
+ The subclass-created graphics resources are expected to be released in the
+ destructor implementation of the subclass.
+
+ \a cb is the QRhiCommandBuffer for the current frame. The function is
+ called with a frame being recorded, but without an active render pass. The
+ command buffer is provided primarily to allow enqueuing
+ \l{QRhiCommandBuffer::resourceUpdate()}{resource updates} without deferring
+ to render().
+
+ This function is called on the render thread, if there is one.
+
+ \sa render()
+ */
+
+/*!
+ \fn void QQuickRhiItemRenderer::synchronize(QQuickRhiItem *item)
+
+ This function is called on the render thread, if there is one, while the
+ main/GUI thread is blocked. It is called from
+ \l{QQuickItem::updatePaintNode()}{the \a {item}'s synchronize step},
+ and allows reading and writing data belonging to the main and render
+ threads. Typically property values stored in the QQuickRhiItem are copied
+ into the QQuickRhiItemRenderer, so that they can be safely read afterwards
+ in render() when the render and main threads continue to work in parallel.
+
+ \sa initialize(), render()
+ */
+
+/*!
+ \fn void QQuickRhiItemRenderer::render(QRhiCommandBuffer *cb)
+
+ Called when the backing color buffer's contents needs updating.
+
+ There is always at least one call to initialize() before this function is
+ called.
+
+ To request updates, call \l QQuickItem::update() when calling from QML or
+ from C++ code on the main/GUI thread (e.g. when in a property setter), or
+ \l update() when calling from within a QQuickRhiItemRenderer callback.
+ Calling QQuickRhiItemRenderer's update() from within
+ render() will lead to triggering updates continuously.
+
+ \a cb is the QRhiCommandBuffer for the current frame. The function is
+ called with a frame being recorded, but without an active render pass.
+
+ This function is called on the render thread, if there is one.
+
+ \sa initialize(), synchronize()
+ */
+
+QT_END_NAMESPACE
diff --git a/src/quick/items/qquickrhiitem.h b/src/quick/items/qquickrhiitem.h
new file mode 100644
index 0000000000..78bfb9fe4b
--- /dev/null
+++ b/src/quick/items/qquickrhiitem.h
@@ -0,0 +1,124 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQUICKRHIITEM_H
+#define QQUICKRHIITEM_H
+
+#include <QtQuick/QQuickItem>
+
+QT_BEGIN_NAMESPACE
+
+class QQuickRhiItem;
+class QQuickRhiItemPrivate;
+class QQuickRhiItemNode;
+class QRhi;
+class QRhiCommandBuffer;
+class QRhiTexture;
+class QRhiRenderBuffer;
+class QRhiRenderTarget;
+
+class Q_QUICK_EXPORT QQuickRhiItemRenderer
+{
+public:
+ QQuickRhiItemRenderer();
+ virtual ~QQuickRhiItemRenderer();
+
+protected:
+ virtual void initialize(QRhiCommandBuffer *cb) = 0;
+ virtual void synchronize(QQuickRhiItem *item) = 0;
+ virtual void render(QRhiCommandBuffer *cb) = 0;
+
+ void update();
+
+ QRhi *rhi() const;
+ QRhiTexture *colorTexture() const;
+ QRhiRenderBuffer *msaaColorBuffer() const;
+ QRhiTexture *resolveTexture() const;
+ QRhiRenderBuffer *depthStencilBuffer() const;
+ QRhiRenderTarget *renderTarget() const;
+
+private:
+ QQuickRhiItemNode *node;
+ friend class QQuickRhiItem;
+ friend class QQuickRhiItemNode;
+
+ Q_DISABLE_COPY_MOVE(QQuickRhiItemRenderer)
+};
+
+class Q_QUICK_EXPORT QQuickRhiItem : public QQuickItem
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(QQuickRhiItem)
+
+ Q_PROPERTY(int sampleCount READ sampleCount WRITE setSampleCount NOTIFY sampleCountChanged FINAL)
+ Q_PROPERTY(TextureFormat colorBufferFormat READ colorBufferFormat WRITE setColorBufferFormat NOTIFY colorBufferFormatChanged FINAL)
+ Q_PROPERTY(bool mirrorVertically READ isMirrorVerticallyEnabled WRITE setMirrorVertically NOTIFY mirrorVerticallyChanged FINAL)
+ Q_PROPERTY(bool alphaBlending READ alphaBlending WRITE setAlphaBlending NOTIFY alphaBlendingChanged FINAL)
+ Q_PROPERTY(int fixedColorBufferWidth READ fixedColorBufferWidth WRITE setFixedColorBufferWidth NOTIFY fixedColorBufferWidthChanged FINAL)
+ Q_PROPERTY(int fixedColorBufferHeight READ fixedColorBufferHeight WRITE setFixedColorBufferHeight NOTIFY fixedColorBufferHeightChanged FINAL)
+ Q_PROPERTY(QSize effectiveColorBufferSize READ effectiveColorBufferSize NOTIFY effectiveColorBufferSizeChanged FINAL)
+
+public:
+ enum class TextureFormat {
+ RGBA8,
+ RGBA16F,
+ RGBA32F,
+ RGB10A2
+ };
+ Q_ENUM(TextureFormat)
+
+ explicit QQuickRhiItem(QQuickItem *parent = nullptr);
+ ~QQuickRhiItem() override;
+
+ int sampleCount() const;
+ void setSampleCount(int samples);
+
+ TextureFormat colorBufferFormat() const;
+ void setColorBufferFormat(TextureFormat format);
+
+ bool isMirrorVerticallyEnabled() const;
+ void setMirrorVertically(bool enable);
+
+ bool alphaBlending() const;
+ void setAlphaBlending(bool enable);
+
+ int fixedColorBufferWidth() const;
+ void setFixedColorBufferWidth(int width);
+ int fixedColorBufferHeight() const;
+ void setFixedColorBufferHeight(int height);
+
+ QSize effectiveColorBufferSize() const;
+
+ bool isTextureProvider() const override;
+ QSGTextureProvider *textureProvider() const override;
+
+Q_SIGNALS:
+ void sampleCountChanged();
+ void colorBufferFormatChanged();
+ void autoRenderTargetChanged();
+ void mirrorVerticallyChanged();
+ void alphaBlendingChanged();
+ void fixedColorBufferWidthChanged();
+ void fixedColorBufferHeightChanged();
+ void effectiveColorBufferSizeChanged();
+
+protected:
+ virtual QQuickRhiItemRenderer *createRenderer() = 0;
+
+ bool isAutoRenderTargetEnabled() const;
+ void setAutoRenderTarget(bool enabled);
+
+ QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *) override;
+ bool event(QEvent *) override;
+ void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
+ void releaseResources() override;
+
+private Q_SLOTS:
+ void invalidateSceneGraph();
+
+ friend class QQuickRhiItemNode;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQUICKRHIITEM_H
diff --git a/src/quick/items/qquickrhiitem_p.h b/src/quick/items/qquickrhiitem_p.h
new file mode 100644
index 0000000000..e60de7e55e
--- /dev/null
+++ b/src/quick/items/qquickrhiitem_p.h
@@ -0,0 +1,101 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQUICKRHIITEM_P_H
+#define QQUICKRHIITEM_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qquickrhiitem.h"
+#include <QtQuick/QSGTextureProvider>
+#include <QtQuick/QSGSimpleTextureNode>
+#include <QtQuick/private/qquickitem_p.h>
+#include <rhi/qrhi.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQuickRhiItemNode : public QSGTextureProvider, public QSGSimpleTextureNode
+{
+ Q_OBJECT
+
+public:
+ QQuickRhiItemNode(QQuickRhiItem *item);
+
+ QSGTexture *texture() const override;
+
+ void sync();
+ QRhiCommandBuffer *queryCommandBuffer();
+ void resetColorBufferObjects();
+ void resetRenderTargetObjects();
+
+ bool isValid() const
+ {
+ return m_rhi && m_sgTexture;
+ }
+
+ void scheduleUpdate()
+ {
+ m_renderPending = true;
+ m_window->update(); // ensure getting to beforeRendering() at some point
+ }
+
+ bool hasRenderer() const
+ {
+ return m_renderer != nullptr;
+ }
+
+ void setRenderer(QQuickRhiItemRenderer *r)
+ {
+ m_renderer.reset(r);
+ }
+
+ QQuickRhiItem *m_item;
+ QQuickWindow *m_window;
+ QSize m_pixelSize;
+ qreal m_dpr = 0.0f;
+ QRhi *m_rhi = nullptr;
+ bool m_renderPending = true;
+ std::unique_ptr<QSGTexture> m_sgTexture;
+ std::unique_ptr<QQuickRhiItemRenderer> m_renderer;
+ QRhiTexture *m_colorTexture = nullptr;
+ QRhiTexture *m_resolveTexture = nullptr;
+ std::unique_ptr<QRhiRenderBuffer> m_msaaColorBuffer;
+ std::unique_ptr<QRhiRenderBuffer> m_depthStencilBuffer;
+ std::unique_ptr<QRhiTextureRenderTarget> m_renderTarget;
+ std::unique_ptr<QRhiRenderPassDescriptor> m_renderPassDescriptor;
+
+public slots:
+ void render();
+};
+
+class QQuickRhiItemPrivate : public QQuickItemPrivate
+{
+ Q_DECLARE_PUBLIC(QQuickRhiItem)
+
+public:
+ static QQuickRhiItemPrivate *get(QQuickRhiItem *item) { return item->d_func(); }
+
+ mutable QQuickRhiItemNode *node = nullptr;
+ QQuickRhiItem::TextureFormat itemTextureFormat = QQuickRhiItem::TextureFormat::RGBA8;
+ QRhiTexture::Format rhiTextureFormat = QRhiTexture::RGBA8;
+ int samples = 1;
+ bool autoRenderTarget = true;
+ bool mirrorVertically = false;
+ bool blend = false;
+ int fixedTextureWidth = 0;
+ int fixedTextureHeight = 0;
+ QSize effectiveTextureSize;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/quick/items/qquickscalegrid_p_p.h b/src/quick/items/qquickscalegrid_p_p.h
index e89001ceff..20edb3b0cb 100644
--- a/src/quick/items/qquickscalegrid_p_p.h
+++ b/src/quick/items/qquickscalegrid_p_p.h
@@ -20,19 +20,19 @@
#include <QtQml/qqml.h>
#include <QtCore/qobject.h>
-#include <QtQuick/private/qquickpixmapcache_p.h>
+#include <QtQuick/private/qquickpixmap_p.h>
#include <private/qtquickglobal_p.h>
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QQuickScaleGrid : public QObject
+class Q_QUICK_EXPORT QQuickScaleGrid : public QObject
{
Q_OBJECT
- Q_PROPERTY(int left READ left WRITE setLeft NOTIFY leftBorderChanged)
- Q_PROPERTY(int top READ top WRITE setTop NOTIFY topBorderChanged)
- Q_PROPERTY(int right READ right WRITE setRight NOTIFY rightBorderChanged)
- Q_PROPERTY(int bottom READ bottom WRITE setBottom NOTIFY bottomBorderChanged)
+ Q_PROPERTY(int left READ left WRITE setLeft NOTIFY leftBorderChanged FINAL)
+ Q_PROPERTY(int top READ top WRITE setTop NOTIFY topBorderChanged FINAL)
+ Q_PROPERTY(int right READ right WRITE setRight NOTIFY rightBorderChanged FINAL)
+ Q_PROPERTY(int bottom READ bottom WRITE setBottom NOTIFY bottomBorderChanged FINAL)
QML_ANONYMOUS
QML_ADDED_IN_VERSION(2, 0)
@@ -99,6 +99,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickScaleGrid)
-
#endif // QQUICKSCALEGRID_P_P_H
diff --git a/src/quick/items/qquickscreen.cpp b/src/quick/items/qquickscreen.cpp
index ea3ae58fd0..c8405ca9ad 100644
--- a/src/quick/items/qquickscreen.cpp
+++ b/src/quick/items/qquickscreen.cpp
@@ -39,6 +39,8 @@ QT_BEGIN_NAMESPACE
Note that the Screen type is not valid at Component.onCompleted, because
the Item or Window has not been displayed on a screen by this time.
+
+ \sa {Qt Quick Examples - Window and Screen}
*/
/*!
diff --git a/src/quick/items/qquickscreen_p.h b/src/quick/items/qquickscreen_p.h
index 059c33b0b7..02d33a1523 100644
--- a/src/quick/items/qquickscreen_p.h
+++ b/src/quick/items/qquickscreen_p.h
@@ -18,6 +18,8 @@
#include <QtQml/qqml.h>
#include <QtQuick/private/qtquickglobal_p.h>
+#include <QtCore/qpointer.h>
+
QT_BEGIN_NAMESPACE
@@ -26,25 +28,25 @@ class QQuickWindow;
class QScreen;
-class Q_QUICK_PRIVATE_EXPORT QQuickScreenInfo : public QObject
+class Q_QUICK_EXPORT QQuickScreenInfo : public QObject
{
Q_OBJECT
- Q_PROPERTY(QString name READ name NOTIFY nameChanged)
- Q_PROPERTY(QString manufacturer READ manufacturer NOTIFY manufacturerChanged REVISION(2, 10))
- Q_PROPERTY(QString model READ model NOTIFY modelChanged REVISION(2, 10))
- Q_PROPERTY(QString serialNumber READ serialNumber NOTIFY serialNumberChanged REVISION(2, 10))
- Q_PROPERTY(int width READ width NOTIFY widthChanged)
- Q_PROPERTY(int height READ height NOTIFY heightChanged)
- Q_PROPERTY(int desktopAvailableWidth READ desktopAvailableWidth NOTIFY desktopGeometryChanged)
- Q_PROPERTY(int desktopAvailableHeight READ desktopAvailableHeight NOTIFY desktopGeometryChanged)
- Q_PROPERTY(qreal logicalPixelDensity READ logicalPixelDensity NOTIFY logicalPixelDensityChanged)
- Q_PROPERTY(qreal pixelDensity READ pixelDensity NOTIFY pixelDensityChanged)
- Q_PROPERTY(qreal devicePixelRatio READ devicePixelRatio NOTIFY devicePixelRatioChanged)
- Q_PROPERTY(Qt::ScreenOrientation primaryOrientation READ primaryOrientation NOTIFY primaryOrientationChanged)
- Q_PROPERTY(Qt::ScreenOrientation orientation READ orientation NOTIFY orientationChanged)
-
- Q_PROPERTY(int virtualX READ virtualX NOTIFY virtualXChanged REVISION(2, 3))
- Q_PROPERTY(int virtualY READ virtualY NOTIFY virtualYChanged REVISION(2, 3))
+ Q_PROPERTY(QString name READ name NOTIFY nameChanged FINAL)
+ Q_PROPERTY(QString manufacturer READ manufacturer NOTIFY manufacturerChanged REVISION(2, 10) FINAL)
+ Q_PROPERTY(QString model READ model NOTIFY modelChanged REVISION(2, 10) FINAL)
+ Q_PROPERTY(QString serialNumber READ serialNumber NOTIFY serialNumberChanged REVISION(2, 10) FINAL)
+ Q_PROPERTY(int width READ width NOTIFY widthChanged FINAL)
+ Q_PROPERTY(int height READ height NOTIFY heightChanged FINAL)
+ Q_PROPERTY(int desktopAvailableWidth READ desktopAvailableWidth NOTIFY desktopGeometryChanged FINAL)
+ Q_PROPERTY(int desktopAvailableHeight READ desktopAvailableHeight NOTIFY desktopGeometryChanged FINAL)
+ Q_PROPERTY(qreal logicalPixelDensity READ logicalPixelDensity NOTIFY logicalPixelDensityChanged FINAL)
+ Q_PROPERTY(qreal pixelDensity READ pixelDensity NOTIFY pixelDensityChanged FINAL)
+ Q_PROPERTY(qreal devicePixelRatio READ devicePixelRatio NOTIFY devicePixelRatioChanged FINAL)
+ Q_PROPERTY(Qt::ScreenOrientation primaryOrientation READ primaryOrientation NOTIFY primaryOrientationChanged FINAL)
+ Q_PROPERTY(Qt::ScreenOrientation orientation READ orientation NOTIFY orientationChanged FINAL)
+
+ Q_PROPERTY(int virtualX READ virtualX NOTIFY virtualXChanged REVISION(2, 3) FINAL)
+ Q_PROPERTY(int virtualY READ virtualY NOTIFY virtualYChanged REVISION(2, 3) FINAL)
QML_NAMED_ELEMENT(ScreenInfo)
QML_ADDED_IN_VERSION(2, 3)
QML_UNCREATABLE("ScreenInfo can only be used via the attached property.")
@@ -91,7 +93,7 @@ protected:
QPointer<QScreen> m_screen;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickScreenAttached : public QQuickScreenInfo
+class Q_QUICK_EXPORT QQuickScreenAttached : public QQuickScreenInfo
{
Q_OBJECT
@@ -114,7 +116,7 @@ private:
QQuickItem* m_attachee;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickScreen : public QObject
+class Q_QUICK_EXPORT QQuickScreen : public QObject
{
Q_OBJECT
QML_ATTACHED(QQuickScreenAttached)
@@ -128,6 +130,4 @@ public:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickScreenInfo)
-
#endif
diff --git a/src/quick/items/qquickselectable_p.h b/src/quick/items/qquickselectable_p.h
index 726352719f..65434be490 100644
--- a/src/quick/items/qquickselectable_p.h
+++ b/src/quick/items/qquickselectable_p.h
@@ -20,19 +20,26 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QQuickSelectable
+class Q_QUICK_EXPORT QQuickSelectable
{
public:
+ enum class CallBackFlag {
+ CancelSelection,
+ SelectionRectangleChanged
+ };
+
virtual QQuickItem *selectionPointerHandlerTarget() const = 0;
- virtual bool startSelection(const QPointF &pos) = 0;
+ virtual bool startSelection(const QPointF &pos, Qt::KeyboardModifiers modifiers) = 0;
virtual void setSelectionStartPos(const QPointF &pos) = 0;
virtual void setSelectionEndPos(const QPointF &pos) = 0;
virtual void clearSelection() = 0;
virtual void normalizeSelection() = 0;
virtual QRectF selectionRectangle() const = 0;
- virtual QSizeF scrollTowardsSelectionPoint(const QPointF &pos, const QSizeF &step) = 0;
+ virtual QSizeF scrollTowardsPoint(const QPointF &pos, const QSizeF &step) = 0;
+
+ virtual void setCallback(std::function<void(CallBackFlag)> func) = 0;
};
QT_END_NAMESPACE
diff --git a/src/quick/items/qquickshadereffect.cpp b/src/quick/items/qquickshadereffect.cpp
index d47203057f..7dea334785 100644
--- a/src/quick/items/qquickshadereffect.cpp
+++ b/src/quick/items/qquickshadereffect.cpp
@@ -486,6 +486,15 @@ QT_BEGIN_NAMESPACE
\li Samplers must use binding points starting from 1.
+ \li When Qt Quick is rendering with \c multiview enabled, e.g. because it is
+ part of a 3D scene rendering in a VR/AR environment where the left and right
+ eye content are generated in a single pass, the ShaderEffect's shaders have
+ to be written with this in mind. With a view count of 2 for example, there
+ will be \c 2 matrices (qt_Matrix is an array of mat4 with two elements). The
+ vertex shader is expected to take \c gl_ViewIndex into account. See the \c
+ Multiview section in the \l{QSB Manual} for general information on creating
+ multiview-capable shaders.
+
\endlist
\sa {Item Layers}, {QSB Manual}, {Qt Shader Tools Build System Integration}
@@ -641,11 +650,9 @@ void QQuickShaderEffect::setMesh(const QVariant &mesh)
This property defines which sides of the item should be visible.
- \list
- \li ShaderEffect.NoCulling - Both sides are visible
- \li ShaderEffect.BackFaceCulling - only front side is visible
- \li ShaderEffect.FrontFaceCulling - only back side is visible
- \endlist
+ \value ShaderEffect.NoCulling Both sides are visible
+ \value ShaderEffect.BackFaceCulling only the front side is visible
+ \value ShaderEffect.FrontFaceCulling only the back side is visible
The default is NoCulling.
*/
@@ -700,11 +707,9 @@ void QQuickShaderEffect::setSupportsAtlasTextures(bool supports)
This property tells the current status of the shaders.
- \list
- \li ShaderEffect.Compiled - the shader program was successfully compiled and linked.
- \li ShaderEffect.Uncompiled - the shader program has not yet been compiled.
- \li ShaderEffect.Error - the shader program failed to compile or link.
- \endlist
+ \value ShaderEffect.Compiled the shader program was successfully compiled and linked.
+ \value ShaderEffect.Uncompiled the shader program has not yet been compiled.
+ \value ShaderEffect.Error the shader program failed to compile or link.
When setting the fragment or vertex shader source code, the status will
become Uncompiled. The first time the ShaderEffect is rendered with new
@@ -985,9 +990,11 @@ void QQuickShaderEffectPrivate::handleEvent(QEvent *event)
{
if (event->type() == QEvent::DynamicPropertyChange) {
const auto propertyName = static_cast<QDynamicPropertyChangeEvent *>(event)->propertyName();
- const auto mappedId = findMappedShaderVariableId(propertyName);
- if (mappedId)
- propertyChanged(*mappedId);
+ for (int i = 0; i < NShader; ++i) {
+ const auto mappedId = findMappedShaderVariableId(propertyName, Shader(i));
+ if (mappedId)
+ propertyChanged(*mappedId);
+ }
}
}
@@ -1039,6 +1046,7 @@ QSGNode *QQuickShaderEffectPrivate::handleUpdatePaintNode(QSGNode *oldNode, QQui
sd.fragment.dirtyConstants = &m_dirtyConstants[Fragment];
sd.fragment.dirtyTextures = &m_dirtyTextures[Fragment];
sd.materialTypeCacheKey = q->window();
+ sd.viewCount = QQuickWindowPrivate::get(q->window())->multiViewCount();
node->syncMaterial(&sd);
@@ -1121,6 +1129,7 @@ bool QQuickShaderEffectPrivate::updateUniformValue(const QByteArray &name, const
sd.fragment.dirtyConstants = &dirtyConstants[Fragment];
sd.fragment.dirtyTextures = {};
sd.materialTypeCacheKey = q->window();
+ sd.viewCount = QQuickWindowPrivate::get(q->window())->multiViewCount();
node->syncMaterial(&sd);
@@ -1274,7 +1283,7 @@ bool QQuickShaderEffectPrivate::updateShader(Shader shaderType, const QUrl &file
// provided and monitored like with an application-provided shader.
QSGGuiThreadShaderEffectManager::ShaderInfo::Variable v;
v.name = QByteArrayLiteral("source");
- v.bindPoint = 0; // fake
+ v.bindPoint = 1; // fake, must match the default source bindPoint in qquickshadereffectnode.cpp
v.type = texturesSeparate ? QSGGuiThreadShaderEffectManager::ShaderInfo::Texture
: QSGGuiThreadShaderEffectManager::ShaderInfo::Sampler;
m_shaders[shaderType].shaderInfo.variables.append(v);
@@ -1442,6 +1451,17 @@ std::optional<int> QQuickShaderEffectPrivate::findMappedShaderVariableId(const Q
return {};
}
+std::optional<int> QQuickShaderEffectPrivate::findMappedShaderVariableId(const QByteArray &name, Shader shaderType) const
+{
+ const auto &vars = m_shaders[shaderType].shaderInfo.variables;
+ for (int idx = 0; idx < vars.size(); ++idx) {
+ if (vars[idx].name == name)
+ return indexToMappedId(shaderType, idx);
+ }
+
+ return {};
+}
+
bool QQuickShaderEffectPrivate::sourceIsUnique(QQuickItem *source, Shader typeToSkip, int indexToSkip) const
{
for (int shaderType = 0; shaderType < NShader; ++shaderType) {
diff --git a/src/quick/items/qquickshadereffect_p.h b/src/quick/items/qquickshadereffect_p.h
index 08db23394e..cfd95864de 100644
--- a/src/quick/items/qquickshadereffect_p.h
+++ b/src/quick/items/qquickshadereffect_p.h
@@ -27,7 +27,7 @@ QT_BEGIN_NAMESPACE
class QQuickShaderEffectPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickShaderEffect : public QQuickItem
+class Q_QUICK_EXPORT QQuickShaderEffect : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(QUrl fragmentShader READ fragmentShader WRITE setFragmentShader NOTIFY fragmentShaderChanged)
diff --git a/src/quick/items/qquickshadereffect_p_p.h b/src/quick/items/qquickshadereffect_p_p.h
index 2310b6d366..e7224e50a9 100644
--- a/src/quick/items/qquickshadereffect_p_p.h
+++ b/src/quick/items/qquickshadereffect_p_p.h
@@ -92,6 +92,7 @@ private:
void disconnectSignals(Shader shaderType);
void clearMappers(Shader shaderType);
std::optional<int> findMappedShaderVariableId(const QByteArray &name) const;
+ std::optional<int> findMappedShaderVariableId(const QByteArray &name, Shader shaderType) const;
bool sourceIsUnique(QQuickItem *source, Shader typeToSkip, int indexToSkip) const;
bool inDestructor = false;
diff --git a/src/quick/items/qquickshadereffectmesh_p.h b/src/quick/items/qquickshadereffectmesh_p.h
index e647daef56..f8a8be1b9f 100644
--- a/src/quick/items/qquickshadereffectmesh_p.h
+++ b/src/quick/items/qquickshadereffectmesh_p.h
@@ -31,13 +31,13 @@ QT_REQUIRE_CONFIG(quick_shadereffect);
QT_BEGIN_NAMESPACE
-Q_QUICK_PRIVATE_EXPORT const char *qtPositionAttributeName();
-Q_QUICK_PRIVATE_EXPORT const char *qtTexCoordAttributeName();
+Q_QUICK_EXPORT const char *qtPositionAttributeName();
+Q_QUICK_EXPORT const char *qtTexCoordAttributeName();
class QSGGeometry;
class QRectF;
-class Q_QUICK_PRIVATE_EXPORT QQuickShaderEffectMesh : public QObject
+class Q_QUICK_EXPORT QQuickShaderEffectMesh : public QObject
{
Q_OBJECT
@@ -62,7 +62,7 @@ protected:
QQuickShaderEffectMesh(QObjectPrivate &dd, QObject *parent = nullptr);
};
-class Q_QUICK_PRIVATE_EXPORT QQuickGridMesh : public QQuickShaderEffectMesh
+class Q_QUICK_EXPORT QQuickGridMesh : public QQuickShaderEffectMesh
{
Q_OBJECT
Q_PROPERTY(QSize resolution READ resolution WRITE setResolution NOTIFY resolutionChanged)
diff --git a/src/quick/items/qquickshadereffectsource.cpp b/src/quick/items/qquickshadereffectsource.cpp
index 9df53ff9e9..f9e52a9a6f 100644
--- a/src/quick/items/qquickshadereffectsource.cpp
+++ b/src/quick/items/qquickshadereffectsource.cpp
@@ -231,12 +231,10 @@ QSGTextureProvider *QQuickShaderEffectSource::textureProvider() const
The default value is \c{ShaderEffectSource.ClampToEdge}.
- \list
- \li ShaderEffectSource.ClampToEdge - GL_CLAMP_TO_EDGE both horizontally and vertically
- \li ShaderEffectSource.RepeatHorizontally - GL_REPEAT horizontally, GL_CLAMP_TO_EDGE vertically
- \li ShaderEffectSource.RepeatVertically - GL_CLAMP_TO_EDGE horizontally, GL_REPEAT vertically
- \li ShaderEffectSource.Repeat - GL_REPEAT both horizontally and vertically
- \endlist
+ \value ShaderEffectSource.ClampToEdge GL_CLAMP_TO_EDGE both horizontally and vertically
+ \value ShaderEffectSource.RepeatHorizontally GL_REPEAT horizontally, GL_CLAMP_TO_EDGE vertically
+ \value ShaderEffectSource.RepeatVertically GL_CLAMP_TO_EDGE horizontally, GL_REPEAT vertically
+ \value ShaderEffectSource.Repeat GL_REPEAT both horizontally and vertically
\note Some OpenGL ES 2 implementations do not support the GL_REPEAT
wrap mode with non-power-of-two textures.
@@ -385,14 +383,12 @@ void QQuickShaderEffectSource::setTextureSize(const QSize &size)
Modifying this property makes most sense when the item is used as a
source texture of a \l ShaderEffect.
- \list
- \li ShaderEffectSource.RGBA8
- \li ShaderEffectSource.RGBA16F
- \li ShaderEffectSource.RGBA32F
- \li ShaderEffectSource.Alpha - Starting with Qt 6.0, this value is not in use and has the same effect as RGBA8 in practice.
- \li ShaderEffectSource.RGB - Starting with Qt 6.0, this value is not in use and has the same effect as RGBA8 in practice.
- \li ShaderEffectSource.RGBA - Starting with Qt 6.0, this value is not in use and has the same effect as RGBA8 in practice.
- \endlist
+ \value ShaderEffectSource.RGBA8
+ \value ShaderEffectSource.RGBA16F
+ \value ShaderEffectSource.RGBA32F
+ \value ShaderEffectSource.Alpha Starting with Qt 6.0, this value is not in use and has the same effect as \c RGBA8 in practice.
+ \value ShaderEffectSource.RGB Starting with Qt 6.0, this value is not in use and has the same effect as \c RGBA8 in practice.
+ \value ShaderEffectSource.RGBA Starting with Qt 6.0, this value is not in use and has the same effect as \c RGBA8 in practice.
*/
QQuickShaderEffectSource::Format QQuickShaderEffectSource::format() const
@@ -524,11 +520,9 @@ void QQuickShaderEffectSource::setRecursive(bool enabled)
such as those specified by ShaderEffect. Mirroring has no effect on the UI representation of
the ShaderEffectSource item itself.
- \list
- \li ShaderEffectSource.NoMirroring - No mirroring
- \li ShaderEffectSource.MirrorHorizontally - The generated texture is flipped along X-axis.
- \li ShaderEffectSource.MirrorVertically - The generated texture is flipped along Y-axis.
- \endlist
+ \value ShaderEffectSource.NoMirroring No mirroring
+ \value ShaderEffectSource.MirrorHorizontally The generated texture is flipped along X-axis.
+ \value ShaderEffectSource.MirrorVertically The generated texture is flipped along Y-axis.
*/
QQuickShaderEffectSource::TextureMirroring QQuickShaderEffectSource::textureMirroring() const
diff --git a/src/quick/items/qquickshadereffectsource_p.h b/src/quick/items/qquickshadereffectsource_p.h
index b49df4f075..ea9a6842e1 100644
--- a/src/quick/items/qquickshadereffectsource_p.h
+++ b/src/quick/items/qquickshadereffectsource_p.h
@@ -39,7 +39,7 @@ class QSGSimpleRectNode;
class QQuickShaderEffectSourceTextureProvider;
-class Q_QUICK_PRIVATE_EXPORT QQuickShaderEffectSource : public QQuickItem, public QQuickItemChangeListener
+class Q_QUICK_EXPORT QQuickShaderEffectSource : public QQuickItem, public QQuickItemChangeListener
{
Q_OBJECT
Q_PROPERTY(WrapMode wrapMode READ wrapMode WRITE setWrapMode NOTIFY wrapModeChanged)
diff --git a/src/quick/items/qquicksprite_p.h b/src/quick/items/qquicksprite_p.h
index 3054b8b2a6..004944c628 100644
--- a/src/quick/items/qquicksprite_p.h
+++ b/src/quick/items/qquicksprite_p.h
@@ -23,7 +23,7 @@ QT_REQUIRE_CONFIG(quick_sprite);
#include <QUrl>
#include <QVariantMap>
#include <QQmlListProperty>
-#include <QtQuick/private/qquickpixmapcache_p.h>
+#include <QtQuick/private/qquickpixmap_p.h>
#include "qquickspriteengine_p.h"
#include <QDebug>
diff --git a/src/quick/items/qquickspriteengine_p.h b/src/quick/items/qquickspriteengine_p.h
index ee2fa34f72..3bd129cf98 100644
--- a/src/quick/items/qquickspriteengine_p.h
+++ b/src/quick/items/qquickspriteengine_p.h
@@ -28,13 +28,13 @@ QT_REQUIRE_CONFIG(quick_sprite);
#include <QImage>
#include <QPair>
#include <QRandomGenerator>
-#include <private/qquickpixmapcache_p.h>
+#include <private/qquickpixmap_p.h>
#include <private/qtquickglobal_p.h>
QT_BEGIN_NAMESPACE
class QQuickSprite;
-class Q_QUICK_PRIVATE_EXPORT QQuickStochasticState : public QObject //Currently for internal use only - Sprite and ParticleGroup
+class Q_QUICK_EXPORT QQuickStochasticState : public QObject //Currently for internal use only - Sprite and ParticleGroup
{
Q_OBJECT
Q_PROPERTY(int duration READ duration WRITE setDuration NOTIFY durationChanged)
@@ -147,12 +147,12 @@ private:
bool m_randomStart = false;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickStochasticEngine : public QObject
+class Q_QUICK_EXPORT QQuickStochasticEngine : public QObject
{
Q_OBJECT
//TODO: Optimize single state case?
- Q_PROPERTY(QString globalGoal READ globalGoal WRITE setGlobalGoal NOTIFY globalGoalChanged)
- Q_PROPERTY(QQmlListProperty<QQuickStochasticState> states READ states)
+ Q_PROPERTY(QString globalGoal READ globalGoal WRITE setGlobalGoal NOTIFY globalGoalChanged FINAL)
+ Q_PROPERTY(QQmlListProperty<QQuickStochasticState> states READ states FINAL)
public:
explicit QQuickStochasticEngine(QObject *parent = nullptr);
QQuickStochasticEngine(const QList<QQuickStochasticState*> &states, QObject *parent = nullptr);
@@ -226,10 +226,10 @@ protected:
bool m_addAdvance;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickSpriteEngine : public QQuickStochasticEngine
+class Q_QUICK_EXPORT QQuickSpriteEngine : public QQuickStochasticEngine
{
Q_OBJECT
- Q_PROPERTY(QQmlListProperty<QQuickSprite> sprites READ sprites)
+ Q_PROPERTY(QQmlListProperty<QQuickSprite> sprites READ sprites FINAL)
public:
explicit QQuickSpriteEngine(QObject *parent = nullptr);
QQuickSpriteEngine(const QList<QQuickSprite*> &sprites, QObject *parent = nullptr);
diff --git a/src/quick/items/qquickspritesequence_p.h b/src/quick/items/qquickspritesequence_p.h
index f4fc6def58..eb80401d6e 100644
--- a/src/quick/items/qquickspritesequence_p.h
+++ b/src/quick/items/qquickspritesequence_p.h
@@ -28,7 +28,7 @@ class QQuickSprite;
class QQuickSpriteEngine;
class QQuickSpriteSequencePrivate;
class QSGSpriteNode;
-class Q_QUICK_PRIVATE_EXPORT QQuickSpriteSequence : public QQuickItem
+class Q_QUICK_EXPORT QQuickSpriteSequence : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(bool running READ running WRITE setRunning NOTIFY runningChanged)
diff --git a/src/quick/items/qquickstateoperations.cpp b/src/quick/items/qquickstateoperations.cpp
index a6e5eed56c..35b29b9134 100644
--- a/src/quick/items/qquickstateoperations.cpp
+++ b/src/quick/items/qquickstateoperations.cpp
@@ -7,7 +7,10 @@
#include <private/qquickstate_p_p.h>
#include <QtQml/qqmlinfo.h>
+
#include <QtCore/qmath.h>
+#include <QtCore/qpointer.h>
+
#include <memory>
QT_BEGIN_NAMESPACE
@@ -156,7 +159,7 @@ QQuickParentChange::QQuickParentChange(QObject *parent)
QQmlScriptString QQuickParentChange::x() const
{
Q_D(const QQuickParentChange);
- return d->xString.value;
+ return d->xString.value();
}
void QQuickParentChange::setX(const QQmlScriptString &x)
@@ -174,7 +177,7 @@ bool QQuickParentChange::xIsSet() const
QQmlScriptString QQuickParentChange::y() const
{
Q_D(const QQuickParentChange);
- return d->yString.value;
+ return d->yString.value();
}
void QQuickParentChange::setY(const QQmlScriptString &y)
@@ -192,7 +195,7 @@ bool QQuickParentChange::yIsSet() const
QQmlScriptString QQuickParentChange::width() const
{
Q_D(const QQuickParentChange);
- return d->widthString.value;
+ return d->widthString.value();
}
void QQuickParentChange::setWidth(const QQmlScriptString &width)
@@ -210,7 +213,7 @@ bool QQuickParentChange::widthIsSet() const
QQmlScriptString QQuickParentChange::height() const
{
Q_D(const QQuickParentChange);
- return d->heightString.value;
+ return d->heightString.value();
}
void QQuickParentChange::setHeight(const QQmlScriptString &height)
@@ -228,7 +231,7 @@ bool QQuickParentChange::heightIsSet() const
QQmlScriptString QQuickParentChange::scale() const
{
Q_D(const QQuickParentChange);
- return d->scaleString.value;
+ return d->scaleString.value();
}
void QQuickParentChange::setScale(const QQmlScriptString &scale)
@@ -246,7 +249,7 @@ bool QQuickParentChange::scaleIsSet() const
QQmlScriptString QQuickParentChange::rotation() const
{
Q_D(const QQuickParentChange);
- return d->rotationString.value;
+ return d->rotationString.value();
}
void QQuickParentChange::setRotation(const QQmlScriptString &rotation)
@@ -313,13 +316,14 @@ QQuickStateOperation::ActionList QQuickParentChange::actions()
if (d->xString.isValid()) {
bool ok = false;
- qreal x = d->xString.value.numberLiteral(&ok);
+ qreal x = d->xString.value().numberLiteral(&ok);
if (ok) {
QQuickStateAction xa(d->target, QLatin1String("x"), x);
actions << xa;
} else {
QQmlProperty property(d->target, QLatin1String("x"));
- auto newBinding = QQmlAnyBinding::createFromScriptString(property, d->xString.value, d->target, qmlContext(this));
+ auto newBinding = QQmlAnyBinding::createFromScriptString(
+ property, d->xString.value(), d->target, qmlContext(this));
QQuickStateAction xa;
xa.property = property;
xa.toBinding = newBinding;
@@ -331,13 +335,14 @@ QQuickStateOperation::ActionList QQuickParentChange::actions()
if (d->yString.isValid()) {
bool ok = false;
- qreal y = d->yString.value.numberLiteral(&ok);
+ qreal y = d->yString.value().numberLiteral(&ok);
if (ok) {
QQuickStateAction ya(d->target, QLatin1String("y"), y);
actions << ya;
} else {
QQmlProperty property(d->target, QLatin1String("y"));
- auto newBinding = QQmlAnyBinding::createFromScriptString(property, d->yString.value, d->target, qmlContext(this));
+ auto newBinding = QQmlAnyBinding::createFromScriptString(
+ property, d->yString.value(), d->target, qmlContext(this));
QQuickStateAction ya;
ya.property = property;
ya.toBinding = newBinding;
@@ -349,13 +354,14 @@ QQuickStateOperation::ActionList QQuickParentChange::actions()
if (d->scaleString.isValid()) {
bool ok = false;
- qreal scale = d->scaleString.value.numberLiteral(&ok);
+ qreal scale = d->scaleString.value().numberLiteral(&ok);
if (ok) {
QQuickStateAction sa(d->target, QLatin1String("scale"), scale);
actions << sa;
} else {
QQmlProperty property(d->target, QLatin1String("scale"));
- auto newBinding = QQmlAnyBinding::createFromScriptString(property, d->scaleString.value, d->target, qmlContext(this));
+ auto newBinding = QQmlAnyBinding::createFromScriptString(
+ property, d->scaleString.value(), d->target, qmlContext(this));
QQuickStateAction sa;
sa.property = property;
sa.toBinding = newBinding;
@@ -367,13 +373,14 @@ QQuickStateOperation::ActionList QQuickParentChange::actions()
if (d->rotationString.isValid()) {
bool ok = false;
- qreal rotation = d->rotationString.value.numberLiteral(&ok);
+ qreal rotation = d->rotationString.value().numberLiteral(&ok);
if (ok) {
QQuickStateAction ra(d->target, QLatin1String("rotation"), rotation);
actions << ra;
} else {
QQmlProperty property(d->target, QLatin1String("rotation"));
- auto newBinding = QQmlAnyBinding::createFromScriptString(property, d->rotationString.value, d->target, qmlContext(this));
+ auto newBinding = QQmlAnyBinding::createFromScriptString(
+ property, d->rotationString.value(), d->target, qmlContext(this));
QQuickStateAction ra;
ra.property = property;
ra.toBinding = newBinding;
@@ -385,7 +392,7 @@ QQuickStateOperation::ActionList QQuickParentChange::actions()
if (d->widthString.isValid()) {
bool ok = false;
- qreal width = d->widthString.value.numberLiteral(&ok);
+ qreal width = d->widthString.value().numberLiteral(&ok);
if (ok) {
QQuickStateAction wa(d->target, QLatin1String("width"), width);
actions << wa;
@@ -403,7 +410,7 @@ QQuickStateOperation::ActionList QQuickParentChange::actions()
if (d->heightString.isValid()) {
bool ok = false;
- qreal height = d->heightString.value.numberLiteral(&ok);
+ qreal height = d->heightString.value().numberLiteral(&ok);
if (ok) {
QQuickStateAction ha(d->target, QLatin1String("height"), height);
actions << ha;
@@ -1063,6 +1070,9 @@ void QQuickAnchorChanges::reverse()
stateHAnchors != QQuickAnchors::LeftAnchor &&
stateHAnchors != QQuickAnchors::RightAnchor &&
stateHAnchors != QQuickAnchors::HCenterAnchor);
+ // in case of an additive AnchorChange, we _did_ end up modifying the width
+ stateSetWidth |= ((stateHAnchors & QQuickAnchors::LeftAnchor) && (origHAnchors & QQuickAnchors::RightAnchor)) ||
+ ((stateHAnchors & QQuickAnchors::RightAnchor) && (origHAnchors & QQuickAnchors::LeftAnchor));
bool origSetWidth = (origHAnchors &&
origHAnchors != QQuickAnchors::LeftAnchor &&
origHAnchors != QQuickAnchors::RightAnchor &&
@@ -1078,12 +1088,15 @@ void QQuickAnchorChanges::reverse()
stateVAnchors != QQuickAnchors::BottomAnchor &&
stateVAnchors != QQuickAnchors::VCenterAnchor &&
stateVAnchors != QQuickAnchors::BaselineAnchor);
+ // in case of an additive AnchorChange, we _did_ end up modifying the height
+ stateSetHeight |= ((stateVAnchors & QQuickAnchors::TopAnchor) && (origVAnchors & QQuickAnchors::BottomAnchor)) ||
+ ((stateVAnchors & QQuickAnchors::BottomAnchor) && (origVAnchors & QQuickAnchors::TopAnchor));
bool origSetHeight = (origVAnchors &&
origVAnchors != QQuickAnchors::TopAnchor &&
origVAnchors != QQuickAnchors::BottomAnchor &&
origVAnchors != QQuickAnchors::VCenterAnchor &&
origVAnchors != QQuickAnchors::BaselineAnchor);
- if (d->origHeight.isValid() && stateSetHeight && !origSetHeight && !!qt_is_nan(d->origHeight)) {
+ if (d->origHeight.isValid() && stateSetHeight && !origSetHeight && !qt_is_nan(d->origHeight)) {
targetPrivate->heightValidFlag = true;
if (targetPrivate->height != d->origHeight)
targetPrivate->height.setValueBypassingBindings(d->origHeight);
@@ -1097,7 +1110,12 @@ void QQuickAnchorChanges::reverse()
const QRectF newGeometry(d->target->position(), d->target->size());
if (newGeometry != oldGeometry) {
- targetPrivate->dirty(QQuickItemPrivate::Position);
+ QQuickItemPrivate::DirtyType dirtyFlags {};
+ if (newGeometry.topLeft() != oldGeometry.topLeft())
+ dirtyFlags = QQuickItemPrivate::DirtyType(dirtyFlags | QQuickItemPrivate::Position);
+ if (newGeometry.size() != oldGeometry.size())
+ dirtyFlags = QQuickItemPrivate::DirtyType(dirtyFlags | QQuickItemPrivate::Size);
+ targetPrivate->dirty(dirtyFlags);
d->target->geometryChange(newGeometry, oldGeometry);
}
}
diff --git a/src/quick/items/qquickstateoperations_p.h b/src/quick/items/qquickstateoperations_p.h
index 54b9a8325b..170e5b3b14 100644
--- a/src/quick/items/qquickstateoperations_p.h
+++ b/src/quick/items/qquickstateoperations_p.h
@@ -25,7 +25,7 @@
QT_BEGIN_NAMESPACE
class QQuickParentChangePrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickParentChange : public QQuickStateOperation, public QQuickStateActionEvent
+class Q_QUICK_EXPORT QQuickParentChange : public QQuickStateOperation, public QQuickStateActionEvent
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickParentChange)
@@ -91,17 +91,17 @@ public:
class QQuickAnchorChanges;
class QQuickAnchorSetPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickAnchorSet : public QObject
+class Q_QUICK_EXPORT QQuickAnchorSet : public QObject
{
Q_OBJECT
- Q_PROPERTY(QQmlScriptString left READ left WRITE setLeft RESET resetLeft)
- Q_PROPERTY(QQmlScriptString right READ right WRITE setRight RESET resetRight)
- Q_PROPERTY(QQmlScriptString horizontalCenter READ horizontalCenter WRITE setHorizontalCenter RESET resetHorizontalCenter)
- Q_PROPERTY(QQmlScriptString top READ top WRITE setTop RESET resetTop)
- Q_PROPERTY(QQmlScriptString bottom READ bottom WRITE setBottom RESET resetBottom)
- Q_PROPERTY(QQmlScriptString verticalCenter READ verticalCenter WRITE setVerticalCenter RESET resetVerticalCenter)
- Q_PROPERTY(QQmlScriptString baseline READ baseline WRITE setBaseline RESET resetBaseline)
+ Q_PROPERTY(QQmlScriptString left READ left WRITE setLeft RESET resetLeft FINAL)
+ Q_PROPERTY(QQmlScriptString right READ right WRITE setRight RESET resetRight FINAL)
+ Q_PROPERTY(QQmlScriptString horizontalCenter READ horizontalCenter WRITE setHorizontalCenter RESET resetHorizontalCenter FINAL)
+ Q_PROPERTY(QQmlScriptString top READ top WRITE setTop RESET resetTop FINAL)
+ Q_PROPERTY(QQmlScriptString bottom READ bottom WRITE setBottom RESET resetBottom FINAL)
+ Q_PROPERTY(QQmlScriptString verticalCenter READ verticalCenter WRITE setVerticalCenter RESET resetVerticalCenter FINAL)
+ Q_PROPERTY(QQmlScriptString baseline READ baseline WRITE setBaseline RESET resetBaseline FINAL)
QML_ANONYMOUS
QML_ADDED_IN_VERSION(2, 0)
@@ -146,7 +146,7 @@ private:
};
class QQuickAnchorChangesPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickAnchorChanges : public QQuickStateOperation, public QQuickStateActionEvent
+class Q_QUICK_EXPORT QQuickAnchorChanges : public QQuickStateOperation, public QQuickStateActionEvent
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickAnchorChanges)
@@ -185,9 +185,5 @@ public:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickParentChange)
-QML_DECLARE_TYPE(QQuickAnchorSet)
-QML_DECLARE_TYPE(QQuickAnchorChanges)
-
#endif // QQUICKSTATEOPERATIONS_P_H
diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp
index 81eddb03ce..9499d16a61 100644
--- a/src/quick/items/qquicktableview.cpp
+++ b/src/quick/items/qquicktableview.cpp
@@ -6,11 +6,13 @@
#include <QtCore/qtimer.h>
#include <QtCore/qdir.h>
+#include <QtCore/qmimedata.h>
#include <QtQmlModels/private/qqmldelegatemodel_p.h>
#include <QtQmlModels/private/qqmldelegatemodel_p_p.h>
#include <QtQml/private/qqmlincubator_p.h>
#include <QtQmlModels/private/qqmlchangeset_p.h>
#include <QtQml/qqmlinfo.h>
+#include <QtQuick/qquickitemgrabresult.h>
#include <QtQuick/private/qquickflickable_p_p.h>
#include <QtQuick/private/qquickitemviewfxitem_p_p.h>
@@ -40,6 +42,16 @@
in a TableView. To create models with multiple columns, either use
\l TableModel or a C++ model that inherits QAbstractItemModel.
+ A TableView does not include headers by default. You can add headers
+ using the \l HorizontalHeaderView and \l VerticalHeaderView from
+ Qt Quick Controls.
+
+ \note TableView will only \l {isRowLoaded()}{load} as many delegate items as
+ needed to fill up the view. There is no guarantee that items outside the view
+ will be loaded, although TableView will sometimes pre-load items for
+ optimization reasons. Hence, a TableView with zero width or height might not
+ load any delegate items at all.
+
\section1 Example Usage
\section2 C++ Models
@@ -102,7 +114,7 @@
the width of the column. Otherwise, it will check if an explicit width has
been set with \l setColumnWidth(). If not, \l implicitColumnWidth() will be used.
The implicit width of a column is the same as the largest
- \l {implicit width}{QQuickItem::implicitWidth()} found among the currently loaded
+ \l {Item::implicitWidth}{implicit width} found among the currently loaded
delegate items in that column. Trying to set an explicit \c width directly on
a delegate has no effect, and will be ignored and overwritten. The same logic also
applies to row heights.
@@ -126,7 +138,7 @@
of the view, and is recalculated again if it's flicked back in. This means that if the
width depends on the \l implicitColumnWidth(), the calculation can be different each time,
depending on which row you're at when the column enters (since \l implicitColumnWidth()
- only considers the delegate items that are currently \l {loaded}{isColumnLoaded()}).
+ only considers the delegate items that are currently \l {isColumnLoaded()}{loaded}).
To avoid this, you should use a \l columnWidthProvider, or ensure that all the delegate
items in the same column have the same \c implicitWidth.
@@ -156,13 +168,13 @@
You can let the user edit table cells by providing an edit delegate. The
edit delegate will be instantiated according to the \l editTriggers, which
by default is when the user double taps on a cell, or presses e.g
- \l Qt.Key_Enter or \l Qt.Key_Return. The edit delegate is set using
+ \l Qt::Key_Enter or \l Qt::Key_Return. The edit delegate is set using
\l {TableView::editDelegate}, which is an attached property that you set
on the \l delegate. The following snippet shows how to do that:
\snippet qml/tableview/editdelegate.qml 0
- If the user presses Qt.Key_Enter or Qt.Key_Return while the edit delegate
+ If the user presses Qt::Key_Enter or Qt::Key_Return while the edit delegate
is active, TableView will emit the \l TableView::commit signal to the edit
delegate, so that it can write back the changed data to the model.
@@ -207,7 +219,7 @@
\snippet qml/tableview/overlay.qml 0
You could also parent the overlay directly to the cell instead of the
- \l contentItem. But doing so will be fragile since the cell is unloaded
+ \l {Flickable::}{contentItem}. But doing so will be fragile since the cell is unloaded
or reused whenever it's flicked out of the viewport.
\sa layoutChanged()
@@ -248,7 +260,7 @@
to let the user select cells.
\note By default, a cell will become
- \l {QQuickItemSelectionModel::currentIndex()}{current}, and any selections will
+ \l {ItemSelectionModel::currentIndex}{current}, and any selections will
be removed, when the user taps on it. If such default tap behavior is not wanted
(e.g if you use custom pointer handlers inside your delegate), you can set
\l pointerNavigationEnabled to \c false.
@@ -257,11 +269,17 @@
In order to support keyboard navigation, you need to assign an \l ItemSelectionModel
to the \l selectionModel property. TableView will then use this model to manipulate
- the model's \l {ItemSelectionModel::currentIndex}{currentIndex}. You can
- disable keyboard navigation fully (in case you want to implement your own key
- handlers) by setting \l keyNavigationEnabled to \c false. Below is an
- example that demonstrates how to use keyboard navigation together with
- \c current and \c selected properties:
+ the model's \l {ItemSelectionModel::currentIndex}{currentIndex}.
+
+ It's the responsibility of the delegate to render itself as
+ \l {ItemSelectionModel::currentIndex}{current}. You can do this by adding a
+ property \c {required property bool current} to it, and let the appearance
+ depend on its state. The \c current property's value is set by the TableView.
+ You can also disable keyboard navigation fully (in case you want to implement your
+ own key handlers) by setting \l keyNavigationEnabled to \c false.
+
+ The following example demonstrates how you can use keyboard navigation together
+ with \c current and \c selected properties:
\snippet qml/tableview/keyboard-navigation.qml 0
@@ -324,7 +342,7 @@
}
\endcode
- \sa mimeData(), dropMimeData(), QUndoStack, QUndoCommand, QClipboard
+ \sa QAbstractItemModel::mimeData(), QAbstractItemModel::dropMimeData(), QUndoStack, QUndoCommand, QClipboard
*/
/*!
@@ -442,11 +460,35 @@
\l {Item::}{implicitHeight}. The TableView lays out the items based on that
information. Explicit width or height settings are ignored and overwritten.
+ Inside the delegate, you can optionally add one or more of the following
+ properties. TableView modifies the values of these properties to inform the
+ delegate which state it's in. This can be used by the delegate to render
+ itself differently according on its own state.
+
+ \list
+ \li required property bool current - \c true if the delegate is \l {Keyboard navigation}{current.}
+ \li required property bool selected - \c true if the delegate is \l {Selecting items}{selected.}
+ \li required property bool editing - \c true if the delegate is being \l {Editing cells}{edited.}
+ \li required property bool containsDrag - \c true if a column or row is currently being dragged
+ over this delegate. This property is only supported for HorizontalHeaderView and
+ VerticalHeaderView. (since Qt 6.8)
+ \endlist
+
+ The following example shows how to use these properties:
+ \code
+ delegate: Rectangle {
+ required property bool current
+ required property bool selected
+ border.width: current ? 1 : 0
+ color: selected ? palette.highlight : palette.base
+ }
+ \endcode
+
\note Delegates are instantiated as needed and may be destroyed at any time.
They are also reused if the \l reuseItems property is set to \c true. You
should therefore avoid storing state information in the delegates.
- \sa {Row heights and column widths}, {Reusing items}
+ \sa {Row heights and column widths}, {Reusing items}, {Required Properties}
*/
/*!
@@ -570,7 +612,7 @@
\readonly
This read-only property holds the column in the view that contains the
- item that is current. If no item is current, it will be \c -1.
+ item that is \l {Keyboard navigation}{current.} If no item is current, it will be \c -1.
\note In order for TableView to report what the current column is, you
need to assign an \l ItemSelectionModel to \l selectionModel.
@@ -583,7 +625,7 @@
\readonly
This read-only property holds the row in the view that contains the item
- that is current. If no item is current, it will be \c -1.
+ that is \l {Keyboard navigation}{current.} If no item is current, it will be \c -1.
\note In order for TableView to report what the current row is, you
need to assign an \l ItemSelectionModel to \l selectionModel.
@@ -656,16 +698,47 @@
\qmlproperty enumeration QtQuick::TableView::selectionBehavior
\since 6.4
- This property holds whether the user can select single cells, rows or columns.
+ This property holds whether the user can select cells, rows or columns.
- \list
- \li TableView.SelectionDisabled - the user cannot perform selections.
- \li TableView.SelectCells (default) - the user can select individual cells.
- \li TableView.SelectRows - the user can only select rows.
- \li TableView.SelectColumns - the user can only select columns.
- \endlist
+ \value TableView.SelectionDisabled
+ The user cannot perform selections
+ \value TableView.SelectCells
+ (Default value) The user can select individual cells
+ \value TableView.SelectRows
+ The user can only select rows
+ \value TableView.SelectColumns
+ The user can only select columns
- \sa {Selecting items}, selectionModel, keyNavigationEnabled
+ \sa {Selecting items}, selectionMode, selectionModel, keyNavigationEnabled
+*/
+
+/*!
+ \qmlproperty enumeration QtQuick::TableView::selectionMode
+ \since 6.6
+
+ If \l selectionBehavior is set to \c {TableView.SelectCells}, this property holds
+ whether the user can select one cell at a time, or multiple cells.
+ If \l selectionBehavior is set to \c {TableView.SelectRows}, this property holds
+ whether the user can select one row at a time, or multiple rows.
+ If \l selectionBehavior is set to \c {TableView.SelectColumns}, this property holds
+ whether the user can select one column at a time, or multiple columns.
+
+ The following modes are available:
+
+ \value TableView.SingleSelection
+ The user can select a single cell, row or column.
+ \value TableView.ContiguousSelection
+ The user can select a single contiguous block of cells.
+ An existing selection can be made bigger or smaller by holding down
+ the \c Shift modifier while selecting.
+ \value TableView.ExtendedSelection
+ (Default value) The user can select multiple individual blocks of
+ cells. An existing selection can be made bigger or smaller by
+ holding down the \c Shift modifier while selecting. A new selection
+ block can be started without clearing the current selection by
+ holding down the \c Control modifier while selecting.
+
+ \sa {Selecting items}, selectionBehavior, selectionModel, keyNavigationEnabled
*/
/*!
@@ -698,25 +771,25 @@
But the application can call \l edit() and \l closeEditor() manually.
\value TableView.SingleTapped - the user can edit a cell by single tapping it.
\value TableView.DoubleTapped - the user can edit a cell by double tapping it.
- \value TableView.SelectedTapped - the user can edit the
- \l {QItemSelectionModel::currentIndex()}{current cell} by tapping it.
+ \value TableView.SelectedTapped - the user can edit a
+ \l {QItemSelectionModel::selectedIndexes()}{selected cell} by tapping it.
\value TableView.EditKeyPressed - the user can edit the
- \l {QItemSelectionModel::currentIndex()}{current cell} by pressing one
+ \l {ItemSelectionModel::currentIndex}{current cell} by pressing one
of the edit keys. The edit keys are decided by the OS, but are normally
- \c Qt.Key_Enter and \c Qt.Key_Return.
+ \c Qt::Key_Enter and \c Qt::Key_Return.
\value TableView.AnyKeyPressed - the user can edit the
- \l {TableView::current}{current cell} by pressing any key, other
+ \l {ItemSelectionModel::currentIndex}{current cell} by pressing any key, other
than the cell navigation keys. The pressed key is also sent to the
focus object inside the \l {TableView::editDelegate}{edit delegate}.
For \c TableView.SelectedTapped, \c TableView.EditKeyPressed, and
\c TableView.AnyKeyPressed to have any effect, TableView needs to have a
\l {selectionModel}{selection model} assigned, since they depend on a
- \l {QItemSelectionModel::currentIndex()}{current index} being set. To be
+ \l {ItemSelectionModel::currentIndex}{current index} being set. To be
able to receive any key events at all, TableView will also need to have
\l QQuickItem::activeFocus.
- When editing a cell, the user can press \c Qt.Key_Tab or \c Qt.Key_Backtab
+ When editing a cell, the user can press \c Qt::Key_Tab or \c Qt::Key_Backtab
to \l {TableView::commit}{commit} the data, and move editing to the next
cell. This behavior can be disabled by setting
\l QQuickItem::activeFocusOnTab on TableView to \c false.
@@ -801,6 +874,20 @@
*/
/*!
+ \qmlmethod QtQuick::TableView::positionViewAtIndex(QModelIndex index, PositionMode mode, point offset, rect subRect)
+ \since 6.5
+
+ Positions the view such that \a index is at the position specified
+ by \a mode, \a offset and \a subRect.
+
+ Convenience method for calling
+ \code
+ positionViewAtRow(rowAtIndex(index), mode & Qt.AlignVertical_Mask, offset.y, subRect)
+ positionViewAtColumn(columnAtIndex(index), mode & Qt.AlignVertical_Mask, offset.x, subRect)
+ \endcode
+*/
+
+/*!
\qmlmethod bool QtQuick::TableView::isColumnLoaded(int column)
\since 6.2
@@ -832,14 +919,9 @@
/*!
\qmlmethod QtQuick::TableView::positionViewAtCell(int column, int row, PositionMode mode, point offset, rect subRect)
+ \deprecated
- Positions \l {Flickable::}{contentX} and \l {Flickable::}{contentY} such
- that \a row and \a column is at the position specified by \a mode, \a offset and \a subRect.
-
- Convenience for calling
- \code
- positionViewAtCell(Qt.point(column, row), mode, offset, subRect)
- \endcode
+ Use \l {positionViewAtIndex()}{positionViewAtIndex(index(row, column), ...)} instead.
*/
/*!
@@ -867,6 +949,48 @@
*/
/*!
+ \qmlmethod QtQuick::TableView::moveColumn(int source, int destination)
+ \since 6.8
+
+ Moves a column from the \a source to the \a destination position.
+
+ \note If a syncView is set, the sync view will control the internal index mapping for
+ column reordering. Therefore, in that case, a call to this function will be forwarded to
+ the sync view instead.
+*/
+
+/*!
+ \qmlmethod QtQuick::TableView::clearColumnReordering()
+ \since 6.8
+
+ Resets any previously applied column reordering.
+
+ \note If a syncView is set, a call to this function will be forwarded to
+ corresponding view item and reset the column ordering.
+*/
+
+/*!
+ \qmlmethod QtQuick::TableView::moveRow(int source, int destination)
+ \since 6.8
+
+ Moves a row from the \a source to the \a destination position.
+
+ \note If a syncView is set, the sync view will control the internal index mapping for
+ row reordering. Therefore, in that case, a call to this function will be forwarded to
+ the sync view instead.
+*/
+
+/*!
+ \qmlmethod QtQuick::TableView::clearRowReordering()
+ \since 6.8
+
+ Resets any previously applied row reordering.
+
+ \note If a syncView is set, a call to this function will be forwarded to
+ the corresponding view item and reset the row ordering.
+*/
+
+/*!
\qmlmethod Item QtQuick::TableView::itemAtCell(point cell)
Returns the delegate item at \a cell if loaded, otherwise \c null.
@@ -879,8 +1003,27 @@
/*!
\qmlmethod Item QtQuick::TableView::itemAtCell(int column, int row)
+ \deprecated
+
+ Use \l {itemAtIndex()}{itemAtIndex(index(row, column))} instead.
+*/
+
+/*!
+ \qmlmethod Item QtQuick::TableView::itemAtIndex(QModelIndex index)
+ \since 6.5
+
+ Returns the instantiated delegate item for the cell that represents
+ \a index. If the item is not \l {isRowLoaded()}{loaded}, the value
+ will be \c null.
+
+ \note only the items that are visible in the view are normally loaded.
+ As soon as a cell is flicked out of the view, the item inside will
+ either be unloaded or placed in the recycle pool. As such, the return
+ value should never be stored.
- Convenience for calling \c{itemAtCell(Qt.point(column, row))}.
+ \note If the \l model is not a QAbstractItemModel, you can also use
+ \l {itemAtCell()}{itemAtCell(Qt.point(column, row))}. But be aware
+ that \c {point.x} maps to columns and \c {point.y} maps to rows.
*/
/*!
@@ -929,15 +1072,6 @@
Returns the width of the given \a column. If the column is not
loaded (and therefore not visible), the return value will be \c -1.
- \note It's the applications responsibility to store what the
- column widths are, by using a \l columnWidthProvider. Hence,
- there is no setter function. This getter function is mostly
- useful if the TableView doesn't have a columnWidthProvider set, since
- otherwise you can call that function instead (which will work, even
- for columns that are not currently visible).
- If no columnWidthProvider is set, the width of a column will be
- equal to its \l implicitColumnWidth().
-
\sa columnWidthProvider, implicitColumnWidth(), isColumnLoaded(), {Row heights and column widths}
*/
@@ -948,15 +1082,6 @@
Returns the height of the given \a row. If the row is not
loaded (and therefore not visible), the return value will be \c -1.
- \note It's the applications responsibility to store what the
- row heights are, by using a \l rowHeightProvider. Hence,
- there is no setter function. This getter function is mostly
- useful if the TableView doesn't have a rowHeightProvider set, since
- otherwise you can call that function instead (which will work, even
- for rows that are not currently visible).
- If no rowHeightProvider is set, the height of a row will be
- equal to its \l implicitRowHeight().
-
\sa rowHeightProvider, implicitRowHeight(), isRowLoaded(), {Row heights and column widths}
*/
@@ -1052,7 +1177,7 @@
\qmlmethod qreal QtQuick::TableView::explicitColumnWidth(int column)
Returns the width of the \a column set with \l setColumnWidth(). This width might
- differ from the actual width of the column, if a \l columnWidthProvider()
+ differ from the actual width of the column, if a \l columnWidthProvider
is in use. To get the actual width of a column, use \l columnWidth().
A return value equal to \c 0 means that the column has been told to hide.
@@ -1124,7 +1249,7 @@
\qmlmethod qreal QtQuick::TableView::explicitRowHeight(int row)
Returns the height of the \a row set with \l setRowHeight(). This height might
- differ from the actual height of the column, if a \l rowHeightProvider()
+ differ from the actual height of the column, if a \l rowHeightProvider
is in use. To get the actual height of a row, use \l rowHeight().
A return value equal to \c 0 means that the row has been told to hide.
@@ -1139,18 +1264,14 @@
/*!
\qmlmethod QModelIndex QtQuick::TableView::modelIndex(int row, int column)
\since 6.4
+ \deprecated
- Returns the \l QModelIndex that maps to \a column and \a row in the view.
+ Use \l {QtQuick::TableView::}{index(int row, int column)} instead.
- \a row and \a column should be the row and column in the view (table row and
- table column), and not a row and column in the model.
-
- \note Because of an API incompatible change in Qt 6.4.0 and Qt 6.4.1, the
+ \note Because of an API incompatible change between Qt 6.4.0 and Qt 6.4.2, the
order of \c row and \c column was specified in the opposite order. If you
rely on the order to be \c {modelIndex(column, row)}, you can set the
environment variable \c QT_QUICK_TABLEVIEW_COMPAT_VERSION to \c 6.4
-
- \sa rowAtIndex(), columnAtIndex()
*/
/*!
@@ -1162,19 +1283,35 @@
modelIndex(cell.y, cell.x)
\endcode
- A cell is simply a \l point that combines row and column into
+ A \a cell is simply a \l point that combines row and column into
a single type.
\note \c {point.x} will map to the column, and \c {point.y} will map to the row.
*/
/*!
+ \qmlmethod QModelIndex QtQuick::TableView::index(int row, int column)
+ \since 6.4.3
+
+ Returns the \l QModelIndex that maps to \a row and \a column in the view.
+
+ \a row and \a column should be the row and column in the view (table row and
+ table column), and not a row and column in the model. For a plain
+ TableView, this is equivalent of calling \c {model.index(row, column).}
+ But for a subclass of TableView, like TreeView, where the data model is
+ wrapped inside an internal proxy model that flattens the tree structure
+ into a table, you need to use this function to resolve the model index.
+
+ \sa rowAtIndex(), columnAtIndex()
+*/
+
+/*!
\qmlmethod int QtQuick::TableView::rowAtIndex(QModelIndex modelIndex)
\since 6.4
Returns the row in the view that maps to \a modelIndex in the model.
- \sa columnAtIndex(), modelIndex()
+ \sa columnAtIndex(), index()
*/
/*!
@@ -1183,7 +1320,7 @@
Returns the column in the view that maps to \a modelIndex in the model.
- \sa rowAtIndex(), modelIndex()
+ \sa rowAtIndex(), index()
*/
/*!
@@ -1249,6 +1386,24 @@
*/
/*!
+ \qmlsignal QtQuick::TableView::columnMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex)
+ \since 6.8
+
+ This signal is emitted when a column is moved. The column's logical index is specified by
+ \a logicalIndex, the old index by \a oldVisualIndex, and the new index position by
+ \a newVisualIndex.
+*/
+
+/*!
+ \qmlsignal QtQuick::TableView::rowMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex)
+ \since 6.8
+
+ This signal is emitted when a row is moved. The row's logical index is specified by
+ \a logicalIndex, the old index by \a oldVisualIndex, and the new index position by
+ \a newVisualIndex.
+*/
+
+/*!
\qmlattachedproperty TableView QtQuick::TableView::view
This attached property holds the view that manages the delegate instance.
@@ -1291,13 +1446,13 @@
This signal is emitted by the \l {TableView::editDelegate}{edit delegate}
This attached signal is emitted when the \l {TableView::editDelegate}{edit delegate}
- is active, and the user presses \l Qt.Key_Enter or \l Qt.Key_Return. It will also
+ is active, and the user presses \l Qt::Key_Enter or \l Qt::Key_Return. It will also
be emitted if TableView has \l QQuickItem::activeFocusOnTab set, and the user
- presses Qt.Key_Tab or Qt.Key_Backtab.
+ presses Qt::Key_Tab or Qt::Key_Backtab.
This signal will \e not be emitted if editing ends because of reasons other
than the ones mentioned. This includes e.g if the user presses
- Qt.Key_Escape, taps outside the delegate, the row or column being
+ Qt::Key_Escape, taps outside the delegate, the row or column being
edited is deleted, or if the application calls \l closeEditor().
Upon receiving the signal, the edit delegate should write any modified data
@@ -1315,7 +1470,7 @@
This attached property holds the edit delegate. It's instantiated
when editing begins, and parented to the delegate it edits. It
supports the same required properties as the
- \l {\l delegate}{TableView delegate}, including \c index, \c row and \c column.
+ \l {TableView::delegate}{TableView delegate}, including \c index, \c row and \c column.
Properties of the model, like \c display and \c edit, are also available
(depending on the \l {QAbstractItemModel::roleNames()}{role names} exposed
by the model).
@@ -1330,12 +1485,12 @@
and \l closeEditor(), respectively. The \c Qt::ItemIsEditable flag will
then be ignored.
- Editing ends when the user presses \c Qt.Key_Enter or \c Qt.Key_Return
- (and also \c Qt.Key_Tab or \c Qt.Key_Backtab, if TableView has
+ Editing ends when the user presses \c Qt::Key_Enter or \c Qt::Key_Return
+ (and also \c Qt::Key_Tab or \c Qt::Key_Backtab, if TableView has
\l QQuickItem::activeFocusOnTab set). In that case, the \l TableView::commit
signal will be emitted, so that the edit delegate can respond by writing any
modified data back to the model. If editing ends because of other reasons
- (e.g if the user presses Qt.Key_Escape), the signal will not be emitted.
+ (e.g if the user presses Qt::Key_Escape), the signal will not be emitted.
In any case will \l {Component::destruction}{destruction()} be emitted in the end.
While the edit delegate is showing, the cell underneath will still be visible, and
@@ -1344,7 +1499,7 @@
of the edit delegate be a solid \l Rectangle, or hide some of the items
inside the \l {TableView::delegate}{TableView delegate.}. The latter can be done
by defining a property \c {required property bool editing} inside it, that you
- bind to the \l visible property of some of the child items.
+ bind to the \l {QQuickItem::}{visible} property of some of the child items.
The following snippet shows how to do that:
\snippet qml/tableview/editdelegate.qml 1
@@ -1369,6 +1524,52 @@ static const char* kRequiredProperties = "_qt_tableview_requiredpropertymask";
static const char* kRequiredProperty_selected = "selected";
static const char* kRequiredProperty_current = "current";
static const char* kRequiredProperty_editing = "editing";
+static const char* kRequiredProperty_containsDrag = "containsDrag";
+
+QDebug operator<<(QDebug dbg, QQuickTableViewPrivate::RebuildState state)
+{
+#define TV_REBUILDSTATE(STATE) \
+ case QQuickTableViewPrivate::RebuildState::STATE: \
+ dbg << QStringLiteral(#STATE); break;
+
+ switch (state) {
+ TV_REBUILDSTATE(Begin);
+ TV_REBUILDSTATE(LoadInitalTable);
+ TV_REBUILDSTATE(VerifyTable);
+ TV_REBUILDSTATE(LayoutTable);
+ TV_REBUILDSTATE(CancelOvershoot);
+ TV_REBUILDSTATE(UpdateContentSize);
+ TV_REBUILDSTATE(PreloadColumns);
+ TV_REBUILDSTATE(PreloadRows);
+ TV_REBUILDSTATE(MovePreloadedItemsToPool);
+ TV_REBUILDSTATE(Done);
+ }
+
+ return dbg;
+}
+
+QDebug operator<<(QDebug dbg, QQuickTableViewPrivate::RebuildOptions options)
+{
+#define TV_REBUILDOPTION(OPTION) \
+ if (options & QQuickTableViewPrivate::RebuildOption::OPTION) \
+ dbg << QStringLiteral(#OPTION)
+
+ if (options == QQuickTableViewPrivate::RebuildOption::None) {
+ dbg << QStringLiteral("None");
+ } else {
+ TV_REBUILDOPTION(All);
+ TV_REBUILDOPTION(LayoutOnly);
+ TV_REBUILDOPTION(ViewportOnly);
+ TV_REBUILDOPTION(CalculateNewTopLeftRow);
+ TV_REBUILDOPTION(CalculateNewTopLeftColumn);
+ TV_REBUILDOPTION(CalculateNewContentWidth);
+ TV_REBUILDOPTION(CalculateNewContentHeight);
+ TV_REBUILDOPTION(PositionViewAtRow);
+ TV_REBUILDOPTION(PositionViewAtColumn);
+ }
+
+ return dbg;
+}
QQuickTableViewPrivate::EdgeRange::EdgeRange()
: startIndex(kEdgeIndexNotSet)
@@ -1528,10 +1729,15 @@ QQuickItem *QQuickTableViewPrivate::selectionPointerHandlerTarget() const
return const_cast<QQuickTableView *>(q_func())->contentItem();
}
-bool QQuickTableViewPrivate::startSelection(const QPointF &pos)
+bool QQuickTableViewPrivate::startSelection(const QPointF &pos, Qt::KeyboardModifiers modifiers)
{
Q_Q(QQuickTableView);
- Q_UNUSED(pos);
+ if (!selectionModel) {
+ if (warnNoSelectionModel)
+ qmlWarning(q_func()) << "Cannot start selection: no SelectionModel assigned!";
+ warnNoSelectionModel = false;
+ return false;
+ }
if (selectionBehavior == QQuickTableView::SelectionDisabled) {
qmlWarning(q) << "Cannot start selection: TableView.selectionBehavior == TableView.SelectionDisabled";
@@ -1542,6 +1748,28 @@ bool QQuickTableViewPrivate::startSelection(const QPointF &pos)
if (resizeHandler->state() != QQuickTableViewResizeHandler::Listening)
return false;
+ // For SingleSelection and ContiguousSelection, we should only allow one
+ // selection at a time. We also clear the current selection if the mode
+ // is ExtendedSelection, but no modifier is being held.
+ if (selectionMode == QQuickTableView::SingleSelection
+ || selectionMode == QQuickTableView::ContiguousSelection
+ || modifiers == Qt::NoModifier)
+ clearSelection();
+ else if (selectionModel)
+ existingSelection = selectionModel->selection();
+
+ // If pos is on top of an unselected cell, we start a session where the user selects which
+ // cells to become selected. Otherwise, if pos is on top of an already selected cell and
+ // ctrl is being held, we start a session where the user selects which selected cells to
+ // become unselected.
+ selectionFlag = QItemSelectionModel::Select;
+ if (modifiers & Qt::ControlModifier) {
+ QPoint startCell = clampedCellAtPos(pos);
+ const QModelIndex startIndex = q->index(startCell.y(), startCell.x());
+ if (selectionModel->isSelected(startIndex))
+ selectionFlag = QItemSelectionModel::Deselect;
+ }
+
selectionStartCell = QPoint(-1, -1);
selectionEndCell = QPoint(-1, -1);
q->closeEditor();
@@ -1550,6 +1778,8 @@ bool QQuickTableViewPrivate::startSelection(const QPointF &pos)
void QQuickTableViewPrivate::setSelectionStartPos(const QPointF &pos)
{
+ Q_Q(QQuickTableView);
+ Q_ASSERT(selectionFlag != QItemSelectionModel::NoUpdate);
if (loadedItems.isEmpty())
return;
if (!selectionModel) {
@@ -1562,13 +1792,26 @@ void QQuickTableViewPrivate::setSelectionStartPos(const QPointF &pos)
if (!qaim)
return;
+ if (selectionMode == QQuickTableView::SingleSelection
+ && cellIsValid(selectionStartCell)) {
+ return;
+ }
+
const QRect prevSelection = selection();
- const QPoint clampedCell = clampedCellAtPos(pos);
+
+ QPoint clampedCell;
+ if (pos.x() == -1) {
+ // Special case: use current cell as start cell
+ clampedCell = q->cellAtIndex(selectionModel->currentIndex());
+ } else {
+ clampedCell = clampedCellAtPos(pos);
+ if (cellIsValid(clampedCell))
+ setCurrentIndex(clampedCell);
+ }
+
if (!cellIsValid(clampedCell))
return;
- setCurrentIndex(clampedCell);
-
switch (selectionBehavior) {
case QQuickTableView::SelectCells:
selectionStartCell = clampedCell;
@@ -1587,11 +1830,13 @@ void QQuickTableViewPrivate::setSelectionStartPos(const QPointF &pos)
return;
// Update selection model
+ QScopedValueRollback callbackGuard(inSelectionModelUpdate, true);
updateSelection(prevSelection, selection());
}
void QQuickTableViewPrivate::setSelectionEndPos(const QPointF &pos)
{
+ Q_ASSERT(selectionFlag != QItemSelectionModel::NoUpdate);
if (loadedItems.isEmpty())
return;
if (!selectionModel) {
@@ -1605,9 +1850,15 @@ void QQuickTableViewPrivate::setSelectionEndPos(const QPointF &pos)
return;
const QRect prevSelection = selection();
- const QPoint clampedCell = clampedCellAtPos(pos);
- if (!cellIsValid(clampedCell))
- return;
+
+ QPoint clampedCell;
+ if (selectionMode == QQuickTableView::SingleSelection) {
+ clampedCell = selectionStartCell;
+ } else {
+ clampedCell = clampedCellAtPos(pos);
+ if (!cellIsValid(clampedCell))
+ return;
+ }
setCurrentIndex(clampedCell);
@@ -1629,6 +1880,7 @@ void QQuickTableViewPrivate::setSelectionEndPos(const QPointF &pos)
return;
// Update selection model
+ QScopedValueRollback callbackGuard(inSelectionModelUpdate, true);
updateSelection(prevSelection, selection());
}
@@ -1659,42 +1911,84 @@ void QQuickTableViewPrivate::updateSelection(const QRect &oldSelection, const QR
const QRect oldRect = oldSelection.normalized();
const QRect newRect = newSelection.normalized();
+ QItemSelection select;
+ QItemSelection deselect;
+
// Select cells inside the new selection rect
{
const QModelIndex startIndex = qaim->index(newRect.y(), newRect.x());
const QModelIndex endIndex = qaim->index(newRect.y() + newRect.height(), newRect.x() + newRect.width());
- selectionModel->select(QItemSelection(startIndex, endIndex), QItemSelectionModel::Select);
+ for (const auto &modelIndex : QItemSelection(startIndex, endIndex).indexes()) {
+ const QModelIndex &logicalModelIndex = qaim->index(logicalRowIndex(modelIndex.row()), logicalColumnIndex(modelIndex.column()));
+ select.append(QItemSelection(logicalModelIndex, logicalModelIndex));
+ }
}
// Unselect cells in the new minus old rects
if (oldRect.x() < newRect.x()) {
const QModelIndex startIndex = qaim->index(oldRect.y(), oldRect.x());
const QModelIndex endIndex = qaim->index(oldRect.y() + oldRect.height(), newRect.x() - 1);
- selectionModel->select(QItemSelection(startIndex, endIndex), QItemSelectionModel::Deselect);
+ for (const auto &modelIndex : QItemSelection(startIndex, endIndex).indexes()) {
+ const QModelIndex &logicalModelIndex = qaim->index(logicalRowIndex(modelIndex.row()), logicalColumnIndex(modelIndex.column()));
+ deselect.merge(QItemSelection(logicalModelIndex, logicalModelIndex), QItemSelectionModel::Select);
+ }
} else if (oldRect.x() + oldRect.width() > newRect.x() + newRect.width()) {
const QModelIndex startIndex = qaim->index(oldRect.y(), newRect.x() + newRect.width() + 1);
const QModelIndex endIndex = qaim->index(oldRect.y() + oldRect.height(), oldRect.x() + oldRect.width());
- selectionModel->select(QItemSelection(startIndex, endIndex), QItemSelectionModel::Deselect);
+ for (auto &modelIndex : QItemSelection(startIndex, endIndex).indexes()) {
+ const QModelIndex &logicalModelIndex = qaim->index(logicalRowIndex(modelIndex.row()), logicalColumnIndex(modelIndex.column()));
+ deselect.merge(QItemSelection(logicalModelIndex, logicalModelIndex), QItemSelectionModel::Select);
+ }
}
if (oldRect.y() < newRect.y()) {
const QModelIndex startIndex = qaim->index(oldRect.y(), oldRect.x());
const QModelIndex endIndex = qaim->index(newRect.y() - 1, oldRect.x() + oldRect.width());
- selectionModel->select(QItemSelection(startIndex, endIndex), QItemSelectionModel::Deselect);
+ for (const auto &modelIndex : QItemSelection(startIndex, endIndex).indexes()) {
+ const QModelIndex &logicalModelIndex = qaim->index(logicalRowIndex(modelIndex.row()), logicalColumnIndex(modelIndex.column()));
+ deselect.merge(QItemSelection(logicalModelIndex, logicalModelIndex), QItemSelectionModel::Select);
+ }
} else if (oldRect.y() + oldRect.height() > newRect.y() + newRect.height()) {
const QModelIndex startIndex = qaim->index(newRect.y() + newRect.height() + 1, oldRect.x());
const QModelIndex endIndex = qaim->index(oldRect.y() + oldRect.height(), oldRect.x() + oldRect.width());
- selectionModel->select(QItemSelection(startIndex, endIndex), QItemSelectionModel::Deselect);
+ for (const auto &modelIndex : QItemSelection(startIndex, endIndex).indexes()) {
+ const QModelIndex &logicalModelIndex = qaim->index(logicalRowIndex(modelIndex.row()), logicalColumnIndex(modelIndex.column()));
+ deselect.merge(QItemSelection(logicalModelIndex, logicalModelIndex), QItemSelectionModel::Select);
+ }
+ }
+
+ if (selectionFlag == QItemSelectionModel::Select) {
+ // Don't clear the selection that existed before the user started a new selection block
+ deselect.merge(existingSelection, QItemSelectionModel::Deselect);
+ selectionModel->select(deselect, QItemSelectionModel::Deselect);
+ selectionModel->select(select, QItemSelectionModel::Select);
+ } else if (selectionFlag == QItemSelectionModel::Deselect){
+ QItemSelection oldSelection = existingSelection;
+ oldSelection.merge(select, QItemSelectionModel::Deselect);
+ selectionModel->select(oldSelection, QItemSelectionModel::Select);
+ selectionModel->select(select, QItemSelectionModel::Deselect);
+ } else {
+ Q_UNREACHABLE();
}
}
-void QQuickTableViewPrivate::clearSelection()
+void QQuickTableViewPrivate::cancelSelectionTracking()
{
+ // Cancel any ongoing key/mouse aided selection tracking
selectionStartCell = QPoint(-1, -1);
selectionEndCell = QPoint(-1, -1);
+ existingSelection.clear();
+ selectionFlag = QItemSelectionModel::NoUpdate;
+ if (selectableCallbackFunction)
+ selectableCallbackFunction(QQuickSelectable::CallBackFlag::CancelSelection);
+}
- if (selectionModel)
- selectionModel->clearSelection();
+void QQuickTableViewPrivate::clearSelection()
+{
+ if (!selectionModel)
+ return;
+ QScopedValueRollback callbackGuard(inSelectionModelUpdate, true);
+ selectionModel->clearSelection();
}
void QQuickTableViewPrivate::normalizeSelection()
@@ -1728,7 +2022,7 @@ QRectF QQuickTableViewPrivate::selectionRectangle() const
// If the corner cells of the selection are loaded, we can position the
// selection rectangle at its exact location. Otherwise we extend it out
// to the edges of the content item. This is not ideal, but the best we
- // can do while the location of the the corner cells are unknown.
+ // can do while the location of the corner cells are unknown.
// This will at least move the selection handles (and other overlay) out
// of the viewport until the affected cells are eventually loaded.
int left = 0;
@@ -1766,7 +2060,7 @@ QRect QQuickTableViewPrivate::selection() const
return QRect(selectionStartCell.x(), selectionStartCell.y(), w, h);
}
-QSizeF QQuickTableViewPrivate::scrollTowardsSelectionPoint(const QPointF &pos, const QSizeF &step)
+QSizeF QQuickTableViewPrivate::scrollTowardsPoint(const QPointF &pos, const QSizeF &step)
{
Q_Q(QQuickTableView);
@@ -1832,6 +2126,11 @@ QSizeF QQuickTableViewPrivate::scrollTowardsSelectionPoint(const QPointF &pos, c
return dist;
}
+void QQuickTableViewPrivate::setCallback(std::function<void (CallBackFlag)> func)
+{
+ selectableCallbackFunction = func;
+}
+
QQuickTableViewAttached *QQuickTableViewPrivate::getAttachedObject(const QObject *object) const
{
QObject *attachedObject = qmlAttachedPropertiesObject<QQuickTableView>(object);
@@ -1870,14 +2169,14 @@ QPoint QQuickTableViewPrivate::cellAtModelIndex(int modelIndex) const
}
}
-int QQuickTableViewPrivate::modelIndexToCellIndex(const QModelIndex &modelIndex) const
+int QQuickTableViewPrivate::modelIndexToCellIndex(const QModelIndex &modelIndex, bool visualIndex) const
{
// Convert QModelIndex to cell index. A cell index is just an
// integer representation of a cell instead of using a QPoint.
const QPoint cell = q_func()->cellAtIndex(modelIndex);
if (!cellIsValid(cell))
return -1;
- return modelIndexAtCell(cell);
+ return modelIndexAtCell(visualIndex ? cell : QPoint(modelIndex.column(), modelIndex.row()));
}
int QQuickTableViewPrivate::edgeToArrayIndex(Qt::Edge edge) const
@@ -2019,7 +2318,10 @@ void QQuickTableViewPrivate::updateContentWidth()
if (loadedItems.isEmpty()) {
QBoolBlocker fixupGuard(inUpdateContentSize, true);
- q->QQuickFlickable::setContentWidth(0);
+ if (model && model->count() > 0 && tableModel && tableModel->delegate())
+ q->QQuickFlickable::setContentWidth(kDefaultColumnWidth);
+ else
+ q->QQuickFlickable::setContentWidth(0);
return;
}
@@ -2052,7 +2354,10 @@ void QQuickTableViewPrivate::updateContentHeight()
if (loadedItems.isEmpty()) {
QBoolBlocker fixupGuard(inUpdateContentSize, true);
- q->QQuickFlickable::setContentHeight(0);
+ if (model && model->count() > 0 && tableModel && tableModel->delegate())
+ q->QQuickFlickable::setContentHeight(kDefaultRowHeight);
+ else
+ q->QQuickFlickable::setContentHeight(0);
return;
}
@@ -2365,21 +2670,22 @@ void QQuickTableViewPrivate::forceLayout(bool immediate)
const QSize actualTableSize = calculateTableSize();
if (tableSize != actualTableSize) {
- // This can happen if the app is calling forceLayout while
- // the model is updated, but before we're notified about it.
- rebuildOptions = RebuildOption::All;
- } else {
- // Resizing a column (or row) can result in the table going from being
- // e.g completely inside the viewport to go outside. And in the latter
- // case, the user needs to be able to scroll the viewport, also if
- // flags such as Flickable.StopAtBounds is in use. So we need to
- // update contentWidth/Height to support that case.
- rebuildOptions = RebuildOption::LayoutOnly
- | RebuildOption::CalculateNewContentWidth
- | RebuildOption::CalculateNewContentHeight
- | checkForVisibilityChanges();
+ // The table size will have changed if forceLayout is called after
+ // the row count in the model has changed, but before we received
+ // a rowsInsertedCallback about it (and vice versa for columns).
+ rebuildOptions |= RebuildOption::ViewportOnly;
}
+ // Resizing a column (or row) can result in the table going from being
+ // e.g completely inside the viewport to go outside. And in the latter
+ // case, the user needs to be able to scroll the viewport, also if
+ // flags such as Flickable.StopAtBounds is in use. So we need to
+ // update contentWidth/Height to support that case.
+ rebuildOptions |= RebuildOption::LayoutOnly
+ | RebuildOption::CalculateNewContentWidth
+ | RebuildOption::CalculateNewContentHeight
+ | checkForVisibilityChanges();
+
scheduleRebuildTable(rebuildOptions);
if (immediate) {
@@ -2425,7 +2731,9 @@ FxTableItem *QQuickTableViewPrivate::createFxTableItem(const QPoint &cell, QQmlI
Q_Q(QQuickTableView);
bool ownItem = false;
- int modelIndex = modelIndexAtCell(cell);
+
+ int modelIndex = modelIndexAtCell(isTransposed ? QPoint(logicalRowIndex(cell.x()), logicalColumnIndex(cell.y())) :
+ QPoint(logicalColumnIndex(cell.x()), logicalRowIndex(cell.y())));
QObject* object = model->object(modelIndex, incubationMode);
if (!object) {
@@ -2661,21 +2969,6 @@ qreal QQuickTableViewPrivate::sizeHintForRow(int row) const
return rowHeight;
}
-void QQuickTableViewPrivate::updateTableSize()
-{
- // tableSize is the same as row and column count, and will always
- // be the same as the number of rows and columns in the model.
- Q_Q(QQuickTableView);
-
- const QSize prevTableSize = tableSize;
- tableSize = calculateTableSize();
-
- if (prevTableSize.width() != tableSize.width())
- emit q->columnsChanged();
- if (prevTableSize.height() != tableSize.height())
- emit q->rowsChanged();
-}
-
QSize QQuickTableViewPrivate::calculateTableSize()
{
QSize size(0, 0);
@@ -2793,7 +3086,7 @@ qreal QQuickTableViewPrivate::getColumnWidth(int column) const
const int noExplicitColumnWidth = -1;
- if (cachedColumnWidth.startIndex == column)
+ if (cachedColumnWidth.startIndex == logicalColumnIndex(column))
return cachedColumnWidth.size;
if (syncHorizontally)
@@ -2812,7 +3105,7 @@ qreal QQuickTableViewPrivate::getColumnWidth(int column) const
qreal columnWidth = noExplicitColumnWidth;
if (columnWidthProvider.isCallable()) {
- auto const columnAsArgument = QJSValueList() << QJSValue(column);
+ auto const columnAsArgument = QJSValueList() << QJSValue(logicalColumnIndex(column));
columnWidth = columnWidthProvider.call(columnAsArgument).toNumber();
if (qIsNaN(columnWidth) || columnWidth < 0)
columnWidth = noExplicitColumnWidth;
@@ -2824,7 +3117,7 @@ qreal QQuickTableViewPrivate::getColumnWidth(int column) const
columnWidth = noExplicitColumnWidth;
}
- cachedColumnWidth.startIndex = column;
+ cachedColumnWidth.startIndex = logicalColumnIndex(column);
cachedColumnWidth.size = columnWidth;
return columnWidth;
}
@@ -2839,7 +3132,7 @@ qreal QQuickTableViewPrivate::getRowHeight(int row) const
const int noExplicitRowHeight = -1;
- if (cachedRowHeight.startIndex == row)
+ if (cachedRowHeight.startIndex == logicalRowIndex(row))
return cachedRowHeight.size;
if (syncVertically)
@@ -2858,7 +3151,7 @@ qreal QQuickTableViewPrivate::getRowHeight(int row) const
qreal rowHeight = noExplicitRowHeight;
if (rowHeightProvider.isCallable()) {
- auto const rowAsArgument = QJSValueList() << QJSValue(row);
+ auto const rowAsArgument = QJSValueList() << QJSValue(logicalRowIndex(row));
rowHeight = rowHeightProvider.call(rowAsArgument).toNumber();
if (qIsNaN(rowHeight) || rowHeight < 0)
rowHeight = noExplicitRowHeight;
@@ -2870,7 +3163,7 @@ qreal QQuickTableViewPrivate::getRowHeight(int row) const
rowHeight = noExplicitRowHeight;
}
- cachedRowHeight.startIndex = row;
+ cachedRowHeight.startIndex = logicalRowIndex(row);
cachedRowHeight.size = rowHeight;
return rowHeight;
}
@@ -3206,18 +3499,8 @@ void QQuickTableViewPrivate::processRebuildTable()
Q_Q(QQuickTableView);
if (rebuildState == RebuildState::Begin) {
- if (Q_UNLIKELY(lcTableViewDelegateLifecycle().isDebugEnabled())) {
- qCDebug(lcTableViewDelegateLifecycle()) << "begin rebuild:" << q;
- if (rebuildOptions & RebuildOption::All)
- qCDebug(lcTableViewDelegateLifecycle()) << "RebuildOption::All, options:" << rebuildOptions;
- else if (rebuildOptions & RebuildOption::ViewportOnly)
- qCDebug(lcTableViewDelegateLifecycle()) << "RebuildOption::ViewportOnly, options:" << rebuildOptions;
- else if (rebuildOptions & RebuildOption::LayoutOnly)
- qCDebug(lcTableViewDelegateLifecycle()) << "RebuildOption::LayoutOnly, options:" << rebuildOptions;
- else
- Q_TABLEVIEW_UNREACHABLE(rebuildOptions);
- }
-
+ qCDebug(lcTableViewDelegateLifecycle()) << "begin rebuild:" << q << "options:" << rebuildOptions;
+ tableSizeBeforeRebuild = tableSize;
edgesBeforeRebuild = loadedItems.isEmpty() ? QMargins()
: QMargins(q->leftColumn(), q->topRow(), q->rightColumn(), q->bottomRow());
}
@@ -3286,6 +3569,10 @@ void QQuickTableViewPrivate::processRebuildTable()
}
if (rebuildState == RebuildState::Done) {
+ if (tableSizeBeforeRebuild.width() != tableSize.width())
+ emit q->columnsChanged();
+ if (tableSizeBeforeRebuild.height() != tableSize.height())
+ emit q->rowsChanged();
if (edgesBeforeRebuild.left() != q->leftColumn())
emit q->leftColumnChanged();
if (edgesBeforeRebuild.right() != q->rightColumn())
@@ -3324,7 +3611,7 @@ bool QQuickTableViewPrivate::moveToNextRebuildState()
else
rebuildState = RebuildState(int(rebuildState) + 1);
- qCDebug(lcTableViewDelegateLifecycle()) << int(rebuildState);
+ qCDebug(lcTableViewDelegateLifecycle()) << rebuildState;
return true;
}
@@ -3435,7 +3722,7 @@ void QQuickTableViewPrivate::calculateTopLeft(QPoint &topLeftCell, QPointF &topL
void QQuickTableViewPrivate::loadInitialTable()
{
- updateTableSize();
+ tableSize = calculateTableSize();
if (positionXAnimation.isRunning()) {
positionXAnimation.stop();
@@ -3941,10 +4228,11 @@ bool QQuickTableViewPrivate::currentInSelectionModel(const QPoint &cell) const
void QQuickTableViewPrivate::selectionChangedInSelectionModel(const QItemSelection &selected, const QItemSelection &deselected)
{
- if (!selectionModel->hasSelection()) {
- // Ensure that we cancel any ongoing key/mouse-based selections
- // if selectionModel.clearSelection() is called.
- clearSelection();
+ if (!inSelectionModelUpdate) {
+ // The selection model was manipulated outside of TableView
+ // and SelectionRectangle. In that case we cancel any ongoing
+ // selection tracking.
+ cancelSelectionTracking();
}
const auto &selectedIndexes = selected.indexes();
@@ -3957,6 +4245,13 @@ void QQuickTableViewPrivate::selectionChangedInSelectionModel(const QItemSelecti
void QQuickTableViewPrivate::setSelectedOnDelegateItem(const QModelIndex &modelIndex, bool select)
{
+ if (modelIndex.isValid() && modelIndex.model() != selectionSourceModel()) {
+ qmlWarning(q_func())
+ << "Cannot select cells: TableView.selectionModel.model is not "
+ << "compatible with the model displayed in the view";
+ return;
+ }
+
const int cellIndex = modelIndexToCellIndex(modelIndex);
if (!loadedItems.contains(cellIndex))
return;
@@ -3965,6 +4260,24 @@ void QQuickTableViewPrivate::setSelectedOnDelegateItem(const QModelIndex &modelI
setRequiredProperty(kRequiredProperty_selected, QVariant::fromValue(select), cellIndex, item, false);
}
+QAbstractItemModel *QQuickTableViewPrivate::selectionSourceModel()
+{
+ // TableView.selectionModel.model should always be the same as TableView.model.
+ // After all, when the user selects an index in the view, the same index should
+ // be selected in the selection model. We therefore set the model in
+ // selectionModel.model automatically.
+ // But it's not always the case that the model shown in the view is the same
+ // as TableView.model. Subclasses with a proxy model will instead show the
+ // proxy model (e.g TreeView and HeaderView). And then it's no longer clear if
+ // we should use the proxy model or the TableView.model as source model in
+ // TableView.selectionModel. It's up to the subclass. But in short, if the proxy
+ // model shares the same model items as TableView.model (just with e.g a filter
+ // applied, or sorted etc), then TableView.model should be used. If the proxy
+ // model is a completely different model that shares no model items with
+ // TableView.model, then the proxy model should be used (e.g HeaderView).
+ return qaim(modelImpl());
+}
+
QAbstractItemModel *QQuickTableViewPrivate::qaim(QVariant modelAsVariant) const
{
// If modelAsVariant wraps a qaim, return it
@@ -3992,11 +4305,12 @@ void QQuickTableViewPrivate::updateSelectedOnAllDelegateItems()
void QQuickTableViewPrivate::currentChangedInSelectionModel(const QModelIndex &current, const QModelIndex &previous)
{
- // Warn if the source models are not the same
- const QAbstractItemModel *qaimInSelection = selectionModel ? selectionModel->model() : nullptr;
- const QAbstractItemModel *qaimInTableView = qaim(modelImpl());
- if (qaimInSelection && qaimInSelection != qaimInTableView)
- qmlWarning(q_func()) << "TableView.selectionModel.model differs from TableView.model";
+ if (current.isValid() && current.model() != selectionSourceModel()) {
+ qmlWarning(q_func())
+ << "Cannot change current index: TableView.selectionModel.model is not "
+ << "compatible with the model displayed in the view";
+ return;
+ }
updateCurrentRowAndColumn();
setCurrentOnDelegateItem(previous, false);
@@ -4060,11 +4374,13 @@ void QQuickTableViewPrivate::initItemCallback(int modelIndex, QObject *object)
item->setZ(1);
const QPoint cell = cellAtModelIndex(modelIndex);
- const bool current = currentInSelectionModel(cell);
- const bool selected = selectedInSelectionModel(cell);
+ const QPoint visualCell = QPoint(visualColumnIndex(cell.x()), visualRowIndex(cell.y()));
+ const bool current = currentInSelectionModel(visualCell);
+ const bool selected = selectedInSelectionModel(visualCell);
setRequiredProperty(kRequiredProperty_current, QVariant::fromValue(current), modelIndex, object, true);
setRequiredProperty(kRequiredProperty_selected, QVariant::fromValue(selected), modelIndex, object, true);
setRequiredProperty(kRequiredProperty_editing, QVariant::fromValue(false), modelIndex, item, true);
+ setRequiredProperty(kRequiredProperty_containsDrag, QVariant::fromValue(false), modelIndex, item, true);
if (auto attached = getAttachedObject(object))
attached->setView(q);
@@ -4081,11 +4397,13 @@ void QQuickTableViewPrivate::itemPooledCallback(int modelIndex, QObject *object)
void QQuickTableViewPrivate::itemReusedCallback(int modelIndex, QObject *object)
{
const QPoint cell = cellAtModelIndex(modelIndex);
- const bool current = currentInSelectionModel(cell);
- const bool selected = selectedInSelectionModel(cell);
+ const QPoint visualCell = QPoint(visualColumnIndex(cell.x()), visualRowIndex(cell.y()));
+ const bool current = currentInSelectionModel(visualCell);
+ const bool selected = selectedInSelectionModel(visualCell);
setRequiredProperty(kRequiredProperty_current, QVariant::fromValue(current), modelIndex, object, false);
setRequiredProperty(kRequiredProperty_selected, QVariant::fromValue(selected), modelIndex, object, false);
// Note: the edit item will never be reused, so no reason to set kRequiredProperty_editing
+ setRequiredProperty(kRequiredProperty_containsDrag, QVariant::fromValue(false), modelIndex, object, false);
if (auto item = qobject_cast<QQuickItem*>(object))
QQuickItemPrivate::get(item)->setCulled(false);
@@ -4159,9 +4477,6 @@ QVariant QQuickTableViewPrivate::modelImpl() const
void QQuickTableViewPrivate::setModelImpl(const QVariant &newModel)
{
- if (newModel == assignedModel)
- return;
-
assignedModel = newModel;
scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::All);
emit q_func()->modelChanged();
@@ -4169,7 +4484,7 @@ void QQuickTableViewPrivate::setModelImpl(const QVariant &newModel)
void QQuickTableViewPrivate::syncModel()
{
- if (modelVariant == assignedModel)
+ if (compareModel(modelVariant, assignedModel))
return;
if (model) {
@@ -4445,6 +4760,13 @@ void QQuickTableViewPrivate::modelResetCallback()
scheduleRebuildTable(RebuildOption::All);
}
+bool QQuickTableViewPrivate::compareModel(const QVariant& model1, const QVariant& model2) const
+{
+ return (model1 == model2 ||
+ (model1.userType() == qMetaTypeId<QJSValue>() && model2.userType() == qMetaTypeId<QJSValue>() &&
+ model1.value<QJSValue>().strictlyEquals(model2.value<QJSValue>())));
+}
+
void QQuickTableViewPrivate::positionViewAtRow(int row, Qt::Alignment alignment, qreal offset, const QRectF subRect)
{
Qt::Alignment verticalAlignment = alignment & (Qt::AlignTop | Qt::AlignVCenter | Qt::AlignBottom);
@@ -4660,10 +4982,11 @@ void QQuickTableViewPrivate::init()
positionYAnimation.setProperty(QStringLiteral("contentY"));
positionYAnimation.setEasing(QEasingCurve::OutQuart);
- auto tapHandler = new QQuickTapHandler(q->contentItem());
+ auto tapHandler = new QQuickTableViewTapHandler(q);
hoverHandler = new QQuickTableViewHoverHandler(q);
resizeHandler = new QQuickTableViewResizeHandler(q);
+
hoverHandler->setEnabled(resizableRows || resizableColumns);
resizeHandler->setEnabled(resizableRows || resizableColumns);
@@ -4724,24 +5047,33 @@ void QQuickTableViewPrivate::handleTap(const QQuickHandlerPoint &point)
if (resizeHandler->state() != QQuickTableViewResizeHandler::Listening)
return;
- QModelIndex prevIndex;
- if (selectionModel) {
- prevIndex = selectionModel->currentIndex();
- if (pointerNavigationEnabled) {
- clearSelection();
- setCurrentIndexFromTap(point.position());
- }
- }
+ const QModelIndex tappedIndex = q->modelIndex(q->cellAtPosition(point.position()));
+ bool tappedCellIsSelected = false;
- if (editTriggers != QQuickTableView::NoEditTriggers)
- q->closeEditor();
+ if (selectionModel)
+ tappedCellIsSelected = selectionModel->isSelected(tappedIndex);
- const QModelIndex tappedIndex = q->modelIndex(q->cellAtPosition(point.position()));
if (canEdit(tappedIndex, false)) {
- if (editTriggers & QQuickTableView::SingleTapped)
+ if (editTriggers & QQuickTableView::SingleTapped) {
+ if (selectionBehavior != QQuickTableView::SelectionDisabled)
+ clearSelection();
q->edit(tappedIndex);
- else if ((editTriggers & QQuickTableView::SelectedTapped) && tappedIndex == prevIndex)
+ return;
+ } else if (editTriggers & QQuickTableView::SelectedTapped && tappedCellIsSelected) {
q->edit(tappedIndex);
+ return;
+ }
+ }
+
+ // Since the tap didn't result in selecting or editing cells, we clear
+ // the current selection and move the current index instead.
+ if (pointerNavigationEnabled) {
+ q->closeEditor();
+ if (selectionBehavior != QQuickTableView::SelectionDisabled) {
+ clearSelection();
+ cancelSelectionTracking();
+ }
+ setCurrentIndexFromTap(point.position());
}
}
@@ -4840,7 +5172,6 @@ bool QQuickTableViewPrivate::setCurrentIndexFromKeyEvent(QKeyEvent *e)
const QModelIndex currentIndex = selectionModel->currentIndex();
const QPoint currentCell = q->cellAtIndex(currentIndex);
- const bool select = (e->modifiers() & Qt::ShiftModifier) && (e->key() != Qt::Key_Backtab);
if (!q->activeFocusOnTab()) {
switch (e->key()) {
@@ -4864,7 +5195,7 @@ bool QQuickTableViewPrivate::setCurrentIndexFromKeyEvent(QKeyEvent *e)
case Qt::Key_Backtab:
// Special case: the current index doesn't map to a cell in the view (perhaps
// because it isn't set yet). In that case, we set it to be the top-left cell.
- const QModelIndex topLeftIndex = q->modelIndex(topRow(), leftColumn());
+ const QModelIndex topLeftIndex = q->index(topRow(), leftColumn());
selectionModel->setCurrentIndex(topLeftIndex, QItemSelectionModel::NoUpdate);
return true;
}
@@ -4872,25 +5203,38 @@ bool QQuickTableViewPrivate::setCurrentIndexFromKeyEvent(QKeyEvent *e)
}
auto beginMoveCurrentIndex = [&](){
- if (!select) {
+ const bool shouldSelect = (e->modifiers() & Qt::ShiftModifier) && (e->key() != Qt::Key_Backtab);
+ const bool startNewSelection = selectionRectangle().isEmpty();
+ if (!shouldSelect) {
clearSelection();
- } else if (selectionRectangle().isEmpty()) {
+ cancelSelectionTracking();
+ } else if (startNewSelection) {
+ // Try to start a new selection if no selection exists from before.
+ // The startSelection() call is theoretically allowed to refuse, although this
+ // is less likely when starting a selection using the keyboard.
const int serializedStartIndex = modelIndexToCellIndex(selectionModel->currentIndex());
if (loadedItems.contains(serializedStartIndex)) {
const QRectF startGeometry = loadedItems.value(serializedStartIndex)->geometry();
- setSelectionStartPos(startGeometry.center());
+ if (startSelection(startGeometry.center(), Qt::ShiftModifier)) {
+ setSelectionStartPos(startGeometry.center());
+ if (selectableCallbackFunction)
+ selectableCallbackFunction(QQuickSelectable::CallBackFlag::SelectionRectangleChanged);
+ }
}
}
};
auto endMoveCurrentIndex = [&](const QPoint &cell){
- if (select) {
+ const bool isSelecting = selectionFlag != QItemSelectionModel::NoUpdate;
+ if (isSelecting) {
if (polishScheduled)
forceLayout(true);
const int serializedEndIndex = modelIndexAtCell(cell);
if (loadedItems.contains(serializedEndIndex)) {
const QRectF endGeometry = loadedItems.value(serializedEndIndex)->geometry();
setSelectionEndPos(endGeometry.center());
+ if (selectableCallbackFunction)
+ selectableCallbackFunction(QQuickSelectable::CallBackFlag::SelectionRectangleChanged);
}
}
selectionModel->setCurrentIndex(q->modelIndex(cell), QItemSelectionModel::NoUpdate);
@@ -5170,6 +5514,14 @@ QQuickTableView::QQuickTableView(QQuickTableViewPrivate &dd, QQuickItem *parent)
QQuickTableView::~QQuickTableView()
{
+ Q_D(QQuickTableView);
+
+ if (d->syncView) {
+ // Remove this TableView as a sync child from the syncView
+ auto syncView_d = d->syncView->d_func();
+ syncView_d->syncChildren.removeOne(this);
+ syncView_d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::ViewportOnly);
+ }
}
void QQuickTableView::componentFinalized()
@@ -5301,12 +5653,13 @@ QVariant QQuickTableView::model() const
void QQuickTableView::setModel(const QVariant &newModel)
{
Q_D(QQuickTableView);
+ if (d->compareModel(newModel, d->assignedModel))
+ return;
closeEditor();
d->setModelImpl(newModel);
-
if (d->selectionModel)
- d->selectionModel->setModel(d->qaim(newModel));
+ d->selectionModel->setModel(d->selectionSourceModel());
}
QQmlComponent *QQuickTableView::delegate() const
@@ -5406,6 +5759,10 @@ void QQuickTableView::setSyncView(QQuickTableView *view)
if (d->assignedSyncView == view)
return;
+ // Clear existing index mapping information maintained
+ // in the current view
+ d->clearIndexMapping();
+
d->assignedSyncView = view;
d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::ViewportOnly);
@@ -5473,7 +5830,7 @@ void QQuickTableView::setSelectionModel(QItemSelectionModel *selectionModel)
d->selectionModel = selectionModel;
if (d->selectionModel) {
- d->selectionModel->setModel(d->qaim(d->modelImpl()));
+ d->selectionModel->setModel(d->selectionSourceModel());
QQuickTableViewPrivate::connect(d->selectionModel, &QItemSelectionModel::selectionChanged,
d, &QQuickTableViewPrivate::selectionChangedInSelectionModel);
QQuickTableViewPrivate::connect(d->selectionModel, &QItemSelectionModel::currentChanged,
@@ -5574,7 +5931,7 @@ int QQuickTableView::currentColumn() const
void QQuickTableView::positionViewAtRow(int row, PositionMode mode, qreal offset, const QRectF &subRect)
{
Q_D(QQuickTableView);
- if (row < 0 || row >= rows())
+ if (row < 0 || row >= rows() || d->loadedRows.isEmpty())
return;
// Note: PositionMode::Contain is from here on translated to (Qt::AlignTop | Qt::AlignBottom).
@@ -5640,7 +5997,7 @@ void QQuickTableView::positionViewAtRow(int row, PositionMode mode, qreal offset
void QQuickTableView::positionViewAtColumn(int column, PositionMode mode, qreal offset, const QRectF &subRect)
{
Q_D(QQuickTableView);
- if (column < 0 || column >= columns())
+ if (column < 0 || column >= columns() || d->loadedColumns.isEmpty())
return;
// Note: PositionMode::Contain is from here on translated to (Qt::AlignLeft | Qt::AlignRight).
@@ -5705,9 +6062,35 @@ void QQuickTableView::positionViewAtColumn(int column, PositionMode mode, qreal
void QQuickTableView::positionViewAtCell(const QPoint &cell, PositionMode mode, const QPointF &offset, const QRectF &subRect)
{
- positionViewAtCell(cell.x(), cell.y(), mode, offset, subRect);
+ PositionMode horizontalMode = mode & ~(AlignTop | AlignBottom | AlignVCenter);
+ PositionMode verticalMode = mode & ~(AlignLeft | AlignRight | AlignHCenter);
+ if (!horizontalMode && !verticalMode) {
+ qmlWarning(this) << "Unsupported mode:" << int(mode);
+ return;
+ }
+
+ if (horizontalMode)
+ positionViewAtColumn(cell.x(), horizontalMode, offset.x(), subRect);
+ if (verticalMode)
+ positionViewAtRow(cell.y(), verticalMode, offset.y(), subRect);
+}
+
+void QQuickTableView::positionViewAtIndex(const QModelIndex &index, PositionMode mode, const QPointF &offset, const QRectF &subRect)
+{
+ PositionMode horizontalMode = mode & ~(AlignTop | AlignBottom | AlignVCenter);
+ PositionMode verticalMode = mode & ~(AlignLeft | AlignRight | AlignHCenter);
+ if (!horizontalMode && !verticalMode) {
+ qmlWarning(this) << "Unsupported mode:" << int(mode);
+ return;
+ }
+
+ if (horizontalMode)
+ positionViewAtColumn(columnAtIndex(index), horizontalMode, offset.x(), subRect);
+ if (verticalMode)
+ positionViewAtRow(rowAtIndex(index), verticalMode, offset.y(), subRect);
}
+#if QT_DEPRECATED_SINCE(6, 5)
void QQuickTableView::positionViewAtCell(int column, int row, PositionMode mode, const QPointF &offset, const QRectF &subRect)
{
PositionMode horizontalMode = mode & ~(AlignTop | AlignBottom | AlignVCenter);
@@ -5722,6 +6105,170 @@ void QQuickTableView::positionViewAtCell(int column, int row, PositionMode mode,
if (verticalMode)
positionViewAtRow(row, verticalMode, offset.y(), subRect);
}
+#endif
+
+void QQuickTableView::moveColumn(int source, int destination)
+{
+ Q_D(QQuickTableView);
+ d->moveSection(source, destination, Qt::Horizontal);
+}
+
+void QQuickTableView::moveRow(int source, int destination)
+{
+ Q_D(QQuickTableView);
+ d->moveSection(source, destination, Qt::Vertical);
+}
+
+void QQuickTableViewPrivate::moveSection(int source, int destination, Qt::Orientations orientation)
+{
+ Q_Q(QQuickTableView);
+
+ if (source < 0 || destination < 0 ||
+ (orientation == Qt::Horizontal &&
+ (source >= tableSize.width() || destination >= tableSize.width())) ||
+ (orientation == Qt::Vertical &&
+ (source >= tableSize.height() || destination >= tableSize.height())))
+ return;
+
+ if (source == destination)
+ return;
+
+ if (m_sectionState != SectionState::Moving) {
+ m_sectionState = SectionState::Moving;
+ if (syncView)
+ syncView->d_func()->moveSection(source, destination, orientation);
+ else {
+ // Initialize the visual and logical index mapping
+ initializeIndexMapping();
+
+ // Set current index mapping according to moving rows or columns
+ SectionData *visualIndex = nullptr;
+ SectionData *logicalIndex = nullptr;
+
+ if (orientation == Qt::Horizontal) {
+ visualIndex = visualIndices[0].data();
+ logicalIndex = logicalIndices[0].data();
+ } else if (orientation == Qt::Vertical) {
+ visualIndex = visualIndices[1].data();
+ logicalIndex = logicalIndices[1].data();
+ }
+
+ const int logical = logicalIndex[source].index;
+ int visual = source;
+
+ if (destination > source) {
+ while (visual < destination) {
+ SectionData &visualData = visualIndex[logicalIndex[visual + 1].index];
+ SectionData &logicalData = logicalIndex[visual];
+ visualData.prevIndex = visualData.index;
+ visualData.index = visual;
+ logicalData.prevIndex = logicalData.index;
+ logicalData.index = logicalIndex[visual + 1].index;
+ ++visual;
+ }
+ } else {
+ while (visual > destination) {
+ SectionData &visualData = visualIndex[logicalIndex[visual - 1].index];
+ SectionData &logicalData = logicalIndex[visual];
+ visualData.prevIndex = visualData.index;
+ visualData.index = visual;
+ logicalData.prevIndex = logicalData.index;
+ logicalData.index = logicalIndex[visual - 1].index;
+ --visual;
+ }
+ }
+
+ visualIndex[logical].prevIndex = visualIndex[logical].index;
+ visualIndex[logical].index = destination;
+ logicalIndex[destination].prevIndex = logicalIndex[destination].index;
+ logicalIndex[destination].index = logical;
+
+ // Trigger section move for horizontal and vertical child views
+ // Used in a case where moveSection() triggered for table view
+ for (auto syncChild : std::as_const(syncChildren)) {
+ auto syncChild_d = syncChild->d_func();
+ if (syncChild_d->m_sectionState != SectionState::Moving &&
+ ((syncChild_d->syncHorizontally && orientation == Qt::Horizontal) ||
+ (syncChild_d->syncVertically && orientation == Qt::Vertical)))
+ syncChild_d->moveSection(source, destination, orientation);
+ }
+ }
+
+ // Rebuild the view to reflect the section order
+ scheduleRebuildTable(RebuildOption::ViewportOnly);
+ m_sectionState = SectionState::Idle;
+
+ // Emit section moved signal for the sections moved in the view
+ const int startIndex = (source > destination) ? destination : source;
+ const int endIndex = (source > destination) ? source : destination;
+ const int mapIndex = static_cast<int>(orientation) - 1;
+ for (int index = startIndex; index <= endIndex; index++) {
+ const SectionData *logicalDataIndices = (syncView ? syncView->d_func()->logicalIndices[mapIndex].constData() : logicalIndices[mapIndex].constData());
+ const SectionData *visualDataIndices = syncView ? syncView->d_func()->visualIndices[mapIndex].constData() : visualIndices[mapIndex].constData();
+ const int prevLogicalIndex = logicalDataIndices[index].prevIndex;
+ if (orientation == Qt::Horizontal)
+ emit q->columnMoved(prevLogicalIndex, visualDataIndices[prevLogicalIndex].prevIndex, visualDataIndices[prevLogicalIndex].index);
+ else
+ emit q->rowMoved(prevLogicalIndex, visualDataIndices[prevLogicalIndex].prevIndex, visualDataIndices[prevLogicalIndex].index);
+ }
+ }
+}
+
+void QQuickTableView::clearColumnReordering()
+{
+ Q_D(QQuickTableView);
+ d->clearSection(Qt::Horizontal);
+}
+
+void QQuickTableView::clearRowReordering()
+{
+ Q_D(QQuickTableView);
+ d->clearSection(Qt::Vertical);
+}
+
+void QQuickTableViewPrivate::clearSection(Qt::Orientations orientation)
+{
+ Q_Q(QQuickTableView);
+
+ const int mapIndex = static_cast<int>(orientation) - 1;
+ const QList<SectionData> oldLogicalIndices = syncView ? syncView->d_func()->logicalIndices[mapIndex] : logicalIndices[mapIndex];
+ const QList<SectionData> oldVisualIndices = syncView ? syncView->d_func()->visualIndices[mapIndex] : visualIndices[mapIndex];;
+
+ if (syncView)
+ syncView->d_func()->clearSection(orientation);
+ else {
+ // Clear the index mapping and rebuild the table
+ logicalIndices[mapIndex].clear();
+ visualIndices[mapIndex].clear();
+ scheduleRebuildTable(RebuildOption::ViewportOnly);
+ }
+
+ // Emit section moved signal for the sections moved in the view
+ for (int index = 0; index < oldLogicalIndices.size(); index++) {
+ const SectionData *logicalDataIndices = oldLogicalIndices.constData();
+ const SectionData *visualDataIndices = oldVisualIndices.constData();
+ if (logicalDataIndices[index].index != index) {
+ const int currentIndex = logicalDataIndices[index].index;
+ if (orientation == Qt::Horizontal)
+ emit q->columnMoved(currentIndex, visualDataIndices[currentIndex].index, index);
+ else
+ emit q->rowMoved(currentIndex, visualDataIndices[currentIndex].index, index);
+ }
+ }
+}
+
+void QQuickTableViewPrivate::setContainsDragOnDelegateItem(const QModelIndex &modelIndex, bool overlay)
+{
+ if (!modelIndex.isValid())
+ return;
+
+ const int cellIndex = modelIndexToCellIndex(modelIndex);
+ if (!loadedItems.contains(cellIndex))
+ return;
+ const QPoint cell = cellAtModelIndex(cellIndex);
+ QQuickItem *item = loadedTableItem(cell)->item;
+ setRequiredProperty(kRequiredProperty_containsDrag, QVariant::fromValue(overlay), cellIndex, item, false);
+}
QQuickItem *QQuickTableView::itemAtCell(const QPoint &cell) const
{
@@ -5732,10 +6279,21 @@ QQuickItem *QQuickTableView::itemAtCell(const QPoint &cell) const
return d->loadedItems.value(modelIndex)->item;
}
+#if QT_DEPRECATED_SINCE(6, 5)
QQuickItem *QQuickTableView::itemAtCell(int column, int row) const
{
return itemAtCell(QPoint(column, row));
}
+#endif
+
+QQuickItem *QQuickTableView::itemAtIndex(const QModelIndex &index) const
+{
+ Q_D(const QQuickTableView);
+ const int serializedIndex = d->modelIndexToCellIndex(index);
+ if (!d->loadedItems.contains(serializedIndex))
+ return nullptr;
+ return d->loadedItems.value(serializedIndex)->item;
+}
#if QT_DEPRECATED_SINCE(6, 4)
QPoint QQuickTableView::cellAtPos(qreal x, qreal y, bool includeSpacing) const
@@ -5890,9 +6448,9 @@ void QQuickTableView::setColumnWidth(int column, qreal size)
return;
if (size < 0)
- d->explicitColumnWidths.remove(column);
+ d->explicitColumnWidths.remove(d->logicalColumnIndex(column));
else
- d->explicitColumnWidths.insert(column, size);
+ d->explicitColumnWidths.insert(d->logicalColumnIndex(column), size);
if (d->loadedItems.isEmpty())
return;
@@ -5925,7 +6483,7 @@ qreal QQuickTableView::explicitColumnWidth(int column) const
if (d->syncHorizontally)
return d->syncView->explicitColumnWidth(column);
- const auto it = d->explicitColumnWidths.constFind(column);
+ const auto it = d->explicitColumnWidths.constFind(d->logicalColumnIndex(column));
if (it != d->explicitColumnWidths.constEnd())
return *it;
return -1;
@@ -5948,9 +6506,9 @@ void QQuickTableView::setRowHeight(int row, qreal size)
return;
if (size < 0)
- d->explicitRowHeights.remove(row);
+ d->explicitRowHeights.remove(d->logicalRowIndex(row));
else
- d->explicitRowHeights.insert(row, size);
+ d->explicitRowHeights.insert(d->logicalRowIndex(row), size);
if (d->loadedItems.isEmpty())
return;
@@ -5983,7 +6541,7 @@ qreal QQuickTableView::explicitRowHeight(int row) const
if (d->syncVertically)
return d->syncView->explicitRowHeight(row);
- const auto it = d->explicitRowHeights.constFind(row);
+ const auto it = d->explicitRowHeights.constFind(d->logicalRowIndex(row));
if (it != d->explicitRowHeights.constEnd())
return *it;
return -1;
@@ -5999,16 +6557,18 @@ QModelIndex QQuickTableView::modelIndex(const QPoint &cell) const
if (!qaim)
return {};
- return qaim->index(cell.y(), cell.x());
+ return qaim->index(d->logicalRowIndex(cell.y()), d->logicalColumnIndex(cell.x()));
}
QPoint QQuickTableView::cellAtIndex(const QModelIndex &index) const
{
if (!index.isValid() || index.parent().isValid())
return {-1, -1};
- return {index.column(), index.row()};
+ Q_D(const QQuickTableView);
+ return {d->visualColumnIndex(index.column()), d->visualRowIndex(index.row())};
}
+#if QT_DEPRECATED_SINCE(6, 4)
QModelIndex QQuickTableView::modelIndex(int row, int column) const
{
static bool compat6_4 = qEnvironmentVariable("QT_QUICK_TABLEVIEW_COMPAT_VERSION") == QStringLiteral("6.4");
@@ -6019,9 +6579,18 @@ QModelIndex QQuickTableView::modelIndex(int row, int column) const
// to continue accepting calls to modelIndex(column, row).
return modelIndex({row, column});
} else {
+ qmlWarning(this) << "modelIndex(row, column) is deprecated. "
+ "Use index(row, column) instead. For more information, see "
+ "https://doc.qt.io/qt-6/qml-qtquick-tableview-obsolete.html";
return modelIndex({column, row});
}
}
+#endif
+
+QModelIndex QQuickTableView::index(int row, int column) const
+{
+ return modelIndex({column, row});
+}
int QQuickTableView::rowAtIndex(const QModelIndex &index) const
{
@@ -6061,7 +6630,8 @@ void QQuickTableView::edit(const QModelIndex &index)
// is currently dependent of the QQmlTableInstanceModel that was used to create the object
// in order to initialize required properties, so we need to set the editItem variable
// early on, so that we can use it in setRequiredProperty.
- d->editIndex = modelIndex(d->cellAtModelIndex(serializedModelIndex));
+ const QPoint cell = d->cellAtModelIndex(serializedModelIndex);
+ d->editIndex = modelIndex({d->visualColumnIndex(cell.x()), d->visualRowIndex(cell.y())});
d->editItem = qmlobject_cast<QQuickItem*>(object);
if (!d->editItem)
return;
@@ -6090,7 +6660,7 @@ void QQuickTableView::edit(const QModelIndex &index)
d->editModel->setModel(d->tableModel->model());
d->editModel->setDelegate(attached->editDelegate());
- const int cellIndex = d->modelIndexToCellIndex(index);
+ const int cellIndex = d->modelIndexToCellIndex(index, false);
QObject* object = d->editModel->object(cellIndex, QQmlIncubator::Synchronous);
if (!object) {
d->editIndex = QModelIndex();
@@ -6141,7 +6711,7 @@ void QQuickTableView::closeEditor()
d->editItem = nullptr;
cellItem->setZ(1);
- const int cellIndex = d->modelIndexToCellIndex(d->editIndex);
+ const int cellIndex = d->modelIndexToCellIndex(d->editIndex, false);
d->setRequiredProperty(kRequiredProperty_editing, QVariant::fromValue(false), cellIndex, cellItem, false);
// Remove the extra reference we sat on the cell item from edit()
d->model->release(cellItem, QQmlInstanceModel::NotReusable);
@@ -6307,6 +6877,21 @@ void QQuickTableView::setSelectionBehavior(SelectionBehavior selectionBehavior)
emit selectionBehaviorChanged();
}
+QQuickTableView::SelectionMode QQuickTableView::selectionMode() const
+{
+ return d_func()->selectionMode;
+}
+
+void QQuickTableView::setSelectionMode(SelectionMode selectionMode)
+{
+ Q_D(QQuickTableView);
+ if (d->selectionMode == selectionMode)
+ return;
+
+ d->selectionMode = selectionMode;
+ emit selectionModeChanged();
+}
+
bool QQuickTableView::resizableColumns() const
{
return d_func()->resizableColumns;
@@ -6344,7 +6929,6 @@ void QQuickTableView::setResizableRows(bool enabled)
}
// ----------------------------------------------
-
QQuickTableViewHoverHandler::QQuickTableViewHoverHandler(QQuickTableView *view)
: QQuickHoverHandler(view->contentItem())
{
@@ -6395,15 +6979,38 @@ void QQuickTableViewHoverHandler::handleEventPoint(QPointerEvent *event, QEventP
// ----------------------------------------------
-QQuickTableViewResizeHandler::QQuickTableViewResizeHandler(QQuickTableView *view)
+QQuickTableViewPointerHandler::QQuickTableViewPointerHandler(QQuickTableView *view)
: QQuickSinglePointHandler(view->contentItem())
{
- setMargin(5);
// Set a grab permission that stops the flickable, as well as
// any drag handler inside the delegate, from stealing the drag.
setGrabPermissions(QQuickPointerHandler::CanTakeOverFromAnything);
}
+bool QQuickTableViewPointerHandler::wantsEventPoint(const QPointerEvent *event, const QEventPoint &point)
+{
+ if (!QQuickSinglePointHandler::wantsEventPoint(event, point))
+ return false;
+
+ // If we have a mouse wheel event then we do not want to do anything related to resizing.
+ if (event->type() == QEvent::Type::Wheel)
+ return false;
+
+ // When the user is flicking, we disable resizing, so that
+ // he doesn't start to resize by accident.
+ const auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent());
+ return !tableView->isMoving();
+}
+
+// ----------------------------------------------
+
+QQuickTableViewResizeHandler::QQuickTableViewResizeHandler(QQuickTableView *view)
+ : QQuickTableViewPointerHandler(view)
+{
+ setMargin(5);
+ setObjectName("tableViewResizeHandler");
+}
+
void QQuickTableViewResizeHandler::onGrabChanged(QQuickPointerHandler *grabber
, QPointingDevice::GrabTransition transition
, QPointerEvent *ev
@@ -6428,18 +7035,14 @@ void QQuickTableViewResizeHandler::onGrabChanged(QQuickPointerHandler *grabber
}
}
-bool QQuickTableViewResizeHandler::wantsEventPoint(const QPointerEvent *event, const QEventPoint &point)
-{
- Q_UNUSED(event);
- Q_UNUSED(point);
- // When the user is flicking, we disable resizing, so that
- // he doesn't start to resize by accident.
- auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
- return !tableView->isMoving();
-}
-
void QQuickTableViewResizeHandler::handleEventPoint(QPointerEvent *event, QEventPoint &point)
{
+ auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent());
+ auto *tableViewPrivate = QQuickTableViewPrivate::get(tableView);
+ const auto *activeHandler = tableViewPrivate->activePointerHandler();
+ if (activeHandler && !qobject_cast<const QQuickTableViewResizeHandler *>(activeHandler))
+ return;
+
// Resolve which state we're in first...
updateState(point);
// ...and act on it next
@@ -6504,6 +7107,7 @@ void QQuickTableViewResizeHandler::updateDrag(QPointerEvent *event, QEventPoint
// pointer handlers to do flicking, so setting an exclusive grab (together
// with grab permissions) doens't work ATM.
tableView->setFiltersChildMouseEvents(false);
+ tableViewPrivate->setActivePointerHandler(this);
break;
case DraggingStarted:
setExclusiveGrab(event, point, true);
@@ -6514,7 +7118,7 @@ void QQuickTableViewResizeHandler::updateDrag(QPointerEvent *event, QEventPoint
#if QT_CONFIG(cursor)
tableViewPrivate->updateCursor();
#endif
- // fallthrough
+ Q_FALLTHROUGH();
case Dragging: {
const qreal distX = point.position().x() - m_columnStartX;
const qreal distY = point.position().y() - m_rowStartY;
@@ -6525,6 +7129,7 @@ void QQuickTableViewResizeHandler::updateDrag(QPointerEvent *event, QEventPoint
break; }
case DraggingFinished: {
tableView->setFiltersChildMouseEvents(true);
+ tableViewPrivate->setActivePointerHandler(nullptr);
#if QT_CONFIG(cursor)
tableViewPrivate->updateCursor();
#endif
@@ -6532,6 +7137,331 @@ void QQuickTableViewResizeHandler::updateDrag(QPointerEvent *event, QEventPoint
}
}
+// ----------------------------------------------
+QQuickTableViewSectionDragHandler::QQuickTableViewSectionDragHandler(QQuickTableView *view)
+ : QQuickTableViewPointerHandler(view)
+{
+ setObjectName("tableViewDragHandler");
+}
+
+QQuickTableViewSectionDragHandler::~QQuickTableViewSectionDragHandler()
+{
+ resetDragData();
+}
+
+void QQuickTableViewSectionDragHandler::resetDragData()
+{
+ if (m_state != Listening) {
+ m_state = Listening;
+ resetSectionOverlay();
+ m_source = -1;
+ m_destination = -1;
+ if (m_grabResult.data())
+ m_grabResult.data()->disconnect();
+ if (!m_drag.isNull()) {
+ m_drag->disconnect();
+ delete m_drag;
+ }
+ if (!m_dropArea.isNull()) {
+ m_dropArea->disconnect();
+ delete m_dropArea;
+ }
+ auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent());
+ tableView->setFiltersChildMouseEvents(true);
+ }
+}
+
+void QQuickTableViewSectionDragHandler::resetSectionOverlay()
+{
+ if (m_destination != -1) {
+ auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent());
+ auto *tableViewPrivate = QQuickTableViewPrivate::get(tableView);
+ const int row = (m_sectionOrientation == Qt::Horizontal) ? 0 : m_destination;
+ const int column = (m_sectionOrientation == Qt::Horizontal) ? m_destination : 0;
+ tableViewPrivate->setContainsDragOnDelegateItem(tableView->index(row, column), false);
+ m_destination = -1;
+ }
+}
+
+void QQuickTableViewSectionDragHandler::grabSection()
+{
+ // Generate the transparent section image in pixmap
+ QPixmap pixmap(m_grabResult->image().size());
+ pixmap.fill(Qt::transparent);
+ QPainter painter(&pixmap);
+ painter.setOpacity(0.6);
+ painter.drawImage(0, 0, m_grabResult->image());
+ painter.end();
+
+ // Specify the pixmap and mime data to be as drag object
+ auto *mimeData = new QMimeData();
+ mimeData->setImageData(pixmap);
+ m_drag->setMimeData(mimeData);
+ m_drag->setPixmap(pixmap);
+}
+
+void QQuickTableViewSectionDragHandler::handleDrop(QQuickDragEvent *event)
+{
+ Q_UNUSED(event);
+
+ if (m_state == Dragging) {
+ auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent());
+ auto *tableViewPrivate = QQuickTableViewPrivate::get(tableView);
+ tableViewPrivate->moveSection(m_source, m_destination, m_sectionOrientation);
+ m_state = DraggingFinished;
+ resetSectionOverlay();
+ if (m_scrollTimer.isActive())
+ m_scrollTimer.stop();
+ }
+}
+
+void QQuickTableViewSectionDragHandler::handleDrag(QQuickDragEvent *event)
+{
+ Q_UNUSED(event);
+
+ if (m_state == Dragging) {
+ auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent());
+ const QPoint dragItemPosition(tableView->contentX() + event->x(), tableView->contentY() + event->y());
+ const auto *sourceItem = qobject_cast<QQuickItem *>(m_drag->source());
+ const QPoint targetCell = tableView->cellAtPosition(dragItemPosition, true);
+
+ auto *tableViewPrivate = QQuickTableViewPrivate::get(tableView);
+ const int newDestination = (m_sectionOrientation == Qt::Horizontal) ? targetCell.x() : targetCell.y();
+ if (newDestination != m_destination) {
+ // Reset the overlay property in the existing model delegate item
+ resetSectionOverlay();
+ // Set the overlay property in the new model delegate item
+ const int row = (m_sectionOrientation == Qt::Horizontal) ? 0 : newDestination;
+ const int column = (m_sectionOrientation == Qt::Horizontal) ? newDestination : 0;
+ tableViewPrivate->setContainsDragOnDelegateItem(tableView->index(row, column), true);
+ m_destination = newDestination;
+ }
+
+ // Scroll header view while section item moves out of the table boundary
+ const QPoint dragItemStartPos = (m_sectionOrientation == Qt::Horizontal) ? QPoint(dragItemPosition.x() - sourceItem->width() / 2, dragItemPosition.y()) :
+ QPoint(dragItemPosition.x(), dragItemPosition.y() - sourceItem->height() / 2);
+ const QPoint dragItemEndPos = (m_sectionOrientation == Qt::Horizontal) ? QPoint(dragItemPosition.x() + sourceItem->width() / 2, dragItemPosition.y()) :
+ QPoint(dragItemPosition.x(), dragItemPosition.y() + sourceItem->height() / 2);
+ const bool useStartPos = (m_sectionOrientation == Qt::Horizontal) ? (dragItemStartPos.x() <= tableView->contentX()) : (dragItemStartPos.y() <= tableView->contentY());
+ const bool useEndPos = (m_sectionOrientation == Qt::Horizontal) ? (dragItemEndPos.x() >= tableView->width()) : (dragItemEndPos.y() >= tableView->height());
+ if (useStartPos || useEndPos) {
+ if (!m_scrollTimer.isActive()) {
+ m_dragPoint = (m_sectionOrientation == Qt::Horizontal) ? QPoint(useStartPos ? dragItemStartPos.x() : dragItemEndPos.x(), 0) :
+ QPoint(0, useStartPos ? dragItemStartPos.y() : dragItemEndPos.y());
+ m_scrollTimer.start(1);
+ }
+ } else {
+ if (m_scrollTimer.isActive())
+ m_scrollTimer.stop();
+ }
+ }
+}
+
+void QQuickTableViewSectionDragHandler::handleDragDropAction(Qt::DropAction action)
+{
+ // Reset the overlay property in the model delegate item when drag or drop
+ // happens outside specified drop area (i.e. during ignore action)
+ if (action == Qt::IgnoreAction) {
+ resetSectionOverlay();
+ if (m_scrollTimer.isActive())
+ m_scrollTimer.stop();
+ }
+}
+
+void QQuickTableViewSectionDragHandler::handleEventPoint(QPointerEvent *event, QEventPoint &point)
+{
+ QQuickSinglePointHandler::handleEventPoint(event, point);
+
+ auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent());
+ auto *tableViewPrivate = QQuickTableViewPrivate::get(tableView);
+ const auto *activeHandler = tableViewPrivate->activePointerHandler();
+ if (activeHandler && !qobject_cast<const QQuickTableViewSectionDragHandler *>(activeHandler))
+ return;
+
+ if (m_state == DraggingFinished) {
+ if (m_scrollTimer.isActive())
+ m_scrollTimer.stop();
+ resetDragData();
+ }
+
+ if (point.state() == QEventPoint::Pressed) {
+ // Reset the information in the drag handler
+ resetDragData();
+ // Activate the passive grab to get further move updates
+ setPassiveGrab(event, point, true);
+ // Disable flicking while dragging. TableView uses filtering instead of
+ // pointer handlers to do flicking, so setting an exclusive grab (together
+ // with grab permissions) doens't work ATM.
+ auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent());
+ tableView->setFiltersChildMouseEvents(false);
+ m_state = Tracking;
+ } else if (point.state() == QEventPoint::Released) {
+ // Reset the information in the drag handler
+ if (m_scrollTimer.isActive())
+ m_scrollTimer.stop();
+ resetDragData();
+ } else if (point.state() == QEventPoint::Updated) {
+ // Check to see that the movement can be considered as dragging
+ const qreal distX = point.position().x() - point.pressPosition().x();
+ const qreal distY = point.position().y() - point.pressPosition().y();
+ const qreal dragDist = qSqrt(distX * distX + distY * distY);
+ if (dragDist > qApp->styleHints()->startDragDistance()) {
+ switch (m_state) {
+ case Tracking: {
+ // Grab the image for dragging header
+ const QPoint cell = tableView->cellAtPosition(point.position(), true);
+ auto *item = tableView->itemAtCell(cell);
+ if (m_drag.isNull()) {
+ m_drag = new QDrag(item);
+ connect(m_drag.data(), &QDrag::actionChanged, this,
+ &QQuickTableViewSectionDragHandler::handleDragDropAction);
+ }
+ // Connect the timer for scroling
+ QObject::connect(&m_scrollTimer, &QTimer::timeout, [&]{
+ const QSizeF dist = tableViewPrivate->scrollTowardsPoint(m_dragPoint, m_step);
+ m_dragPoint.rx() += dist.width() > 0 ? m_step.width() : -m_step.width();
+ m_dragPoint.ry() += dist.height() > 0 ? m_step.height() : -m_step.height();
+ m_step = QSizeF(qAbs(dist.width() * 0.010), qAbs(dist.height() * 0.010));
+ });
+ // Set the drop area
+ if (m_dropArea.isNull()) {
+ m_dropArea = new QQuickDropArea(tableView);
+ m_dropArea->setSize(tableView->size());
+ connect(m_dropArea, &QQuickDropArea::positionChanged, this,
+ &QQuickTableViewSectionDragHandler::handleDrag);
+ connect(m_dropArea, &QQuickDropArea::dropped, this,
+ &QQuickTableViewSectionDragHandler::handleDrop);
+ }
+ // Grab the image of the section
+ m_grabResult = item->grabToImage();
+ connect(m_grabResult.data(), &QQuickItemGrabResult::ready, this,
+ &QQuickTableViewSectionDragHandler::grabSection);
+ // Update source depending on the type of orientation
+ m_source = (m_sectionOrientation == Qt::Horizontal) ? cell.x() : cell.y();
+ m_state = DraggingStarted;
+ // Set drag handler as active and it further handles section pointer events
+ tableViewPrivate->setActivePointerHandler(this);
+ }
+ break;
+
+ case DraggingStarted: {
+ if (m_drag && m_drag->mimeData()) {
+ if (auto *item = qobject_cast<QQuickItem *>(m_drag->source())) {
+ m_state = Dragging;
+ const QPointF itemPos = item->mapFromItem(tableView->contentItem(), point.position());
+ Q_UNUSED(itemPos);
+ m_drag->setHotSpot(m_sectionOrientation == Qt::Horizontal ? QPoint(item->width()/2, itemPos.y()) : QPoint(itemPos.x(), item->height()/2));
+ m_drag->exec();
+ // Reset the active handler
+ tableViewPrivate->setActivePointerHandler(nullptr);
+ }
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+}
+
+// ----------------------------------------------
+void QQuickTableViewPrivate::initSectionDragHandler(Qt::Orientation orientation)
+{
+ if (!sectionDragHandler) {
+ Q_Q(QQuickTableView);
+ sectionDragHandler = new QQuickTableViewSectionDragHandler(q);
+ sectionDragHandler->setSectionOrientation(orientation);
+ }
+}
+
+void QQuickTableViewPrivate::destroySectionDragHandler()
+{
+ if (sectionDragHandler)
+ delete sectionDragHandler;
+}
+
+void QQuickTableViewPrivate::initializeIndexMapping()
+{
+ auto initIndices = [](auto& visualIndex, auto& logicalIndex, int size) {
+ visualIndex.resize(size);
+ logicalIndex.resize(size);
+ for (int index = 0; index < size; ++index)
+ visualIndex[index].index = logicalIndex[index].index = index;
+ };
+
+ if (!tableSize.isEmpty()) {
+ if (visualIndices[0].size() != tableSize.width()
+ || logicalIndices[0].size() != tableSize.width())
+ initIndices(visualIndices[0], logicalIndices[0], tableSize.width());
+
+ if (visualIndices[1].size() != tableSize.height()
+ || logicalIndices[1].size() != tableSize.height())
+ initIndices(visualIndices[1], logicalIndices[1], tableSize.height());
+ }
+}
+
+void QQuickTableViewPrivate::clearIndexMapping()
+{
+ logicalIndices[0].clear();
+ visualIndices[0].clear();
+
+ logicalIndices[1].clear();
+ visualIndices[1].clear();
+}
+
+int QQuickTableViewPrivate::logicalRowIndex(const int visualIndex) const
+{
+ if (syncView)
+ return syncView->d_func()->logicalRowIndex(visualIndex);
+ if (logicalIndices[1].isEmpty() || visualIndex < 0)
+ return visualIndex;
+ return logicalIndices[1].constData()[visualIndex].index;
+}
+
+int QQuickTableViewPrivate::logicalColumnIndex(const int visualIndex) const
+{
+ if (syncView)
+ return syncView->d_func()->logicalColumnIndex(visualIndex);
+ if (logicalIndices[0].isEmpty() || visualIndex < 0)
+ return visualIndex;
+ return logicalIndices[0].constData()[visualIndex].index;
+}
+
+int QQuickTableViewPrivate::visualRowIndex(const int logicalIndex) const
+{
+ if (syncView)
+ return syncView->d_func()->visualRowIndex(logicalIndex);
+ if (visualIndices[1].isEmpty() || logicalIndex < 0)
+ return logicalIndex;
+ return visualIndices[1].constData()[logicalIndex].index;
+}
+
+int QQuickTableViewPrivate::visualColumnIndex(const int logicalIndex) const
+{
+ if (syncView)
+ return syncView->d_func()->visualColumnIndex(logicalIndex);
+ if (visualIndices[0].isEmpty() || logicalIndex < 0)
+ return logicalIndex;
+ return visualIndices[0].constData()[logicalIndex].index;
+}
+
+// ----------------------------------------------
+
+QQuickTableViewTapHandler::QQuickTableViewTapHandler(QQuickTableView *view)
+ : QQuickTapHandler(view->contentItem())
+{
+ setObjectName("tableViewTapHandler");
+}
+
+bool QQuickTableViewTapHandler::wantsEventPoint(const QPointerEvent *event, const QEventPoint &point)
+{
+ auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
+ auto tableViewPrivate = QQuickTableViewPrivate::get(tableView);
+ return tableViewPrivate->pointerNavigationEnabled && QQuickTapHandler::wantsEventPoint(event, point);
+}
+
QT_END_NAMESPACE
#include "moc_qquicktableview_p.cpp"
diff --git a/src/quick/items/qquicktableview_p.h b/src/quick/items/qquicktableview_p.h
index 162d576320..10cc53274d 100644
--- a/src/quick/items/qquicktableview_p.h
+++ b/src/quick/items/qquicktableview_p.h
@@ -31,7 +31,7 @@ class QQuickTableViewAttached;
class QQuickTableViewPrivate;
class QItemSelectionModel;
-class Q_QUICK_PRIVATE_EXPORT QQuickTableView : public QQuickFlickable, public QQmlFinalizerHook
+class Q_QUICK_EXPORT QQuickTableView : public QQuickFlickable, public QQmlFinalizerHook
{
Q_OBJECT
Q_INTERFACES(QQmlFinalizerHook)
@@ -64,6 +64,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickTableView : public QQuickFlickable, public QQ
Q_PROPERTY(bool resizableColumns READ resizableColumns WRITE setResizableColumns NOTIFY resizableColumnsChanged REVISION(6, 5) FINAL)
Q_PROPERTY(bool resizableRows READ resizableRows WRITE setResizableRows NOTIFY resizableRowsChanged REVISION(6, 5) FINAL)
Q_PROPERTY(EditTriggers editTriggers READ editTriggers WRITE setEditTriggers NOTIFY editTriggersChanged REVISION(6, 5) FINAL)
+ Q_PROPERTY(SelectionMode selectionMode READ selectionMode WRITE setSelectionMode NOTIFY selectionModeChanged REVISION(6, 6) FINAL)
QML_NAMED_ELEMENT(TableView)
QML_ADDED_IN_VERSION(2, 12)
@@ -92,6 +93,13 @@ public:
};
Q_ENUM(SelectionBehavior)
+ enum SelectionMode {
+ SingleSelection,
+ ContiguousSelection,
+ ExtendedSelection
+ };
+ Q_ENUM(SelectionMode)
+
enum EditTrigger {
NoEditTriggers = 0x0,
SingleTapped = 0x1,
@@ -162,6 +170,8 @@ public:
SelectionBehavior selectionBehavior() const;
void setSelectionBehavior(SelectionBehavior selectionBehavior);
+ SelectionMode selectionMode() const;
+ void setSelectionMode(SelectionMode selectionMode);
bool resizableColumns() const;
void setResizableColumns(bool enabled);
@@ -174,15 +184,16 @@ public:
Q_INVOKABLE void forceLayout();
Q_INVOKABLE void positionViewAtCell(const QPoint &cell, PositionMode mode, const QPointF &offset = QPointF(), const QRectF &subRect = QRectF());
- Q_INVOKABLE void positionViewAtCell(int column, int row, PositionMode mode, const QPointF &offset = QPointF(), const QRectF &subRect = QRectF());
+ Q_INVOKABLE void positionViewAtIndex(const QModelIndex &index, PositionMode mode, const QPointF &offset = QPointF(), const QRectF &subRect = QRectF());
Q_INVOKABLE void positionViewAtRow(int row, PositionMode mode, qreal offset = 0, const QRectF &subRect = QRectF());
Q_INVOKABLE void positionViewAtColumn(int column, PositionMode mode, qreal offset = 0, const QRectF &subRect = QRectF());
Q_INVOKABLE QQuickItem *itemAtCell(const QPoint &cell) const;
- Q_INVOKABLE QQuickItem *itemAtCell(int column, int row) const;
Q_REVISION(6, 4) Q_INVOKABLE QPoint cellAtPosition(const QPointF &position, bool includeSpacing = false) const;
Q_REVISION(6, 4) Q_INVOKABLE QPoint cellAtPosition(qreal x, qreal y, bool includeSpacing = false) const;
#if QT_DEPRECATED_SINCE(6, 4)
+ QT_DEPRECATED_VERSION_X_6_4("Use index(row, column) instead")
+ Q_REVISION(6, 4) Q_INVOKABLE virtual QModelIndex modelIndex(int row, int column) const;
QT_DEPRECATED_VERSION_X_6_4("Use cellAtPosition() instead")
Q_INVOKABLE QPoint cellAtPos(const QPointF &position, bool includeSpacing = false) const;
Q_INVOKABLE QPoint cellAtPos(qreal x, qreal y, bool includeSpacing = false) const;
@@ -196,8 +207,8 @@ public:
Q_REVISION(6, 2) Q_INVOKABLE qreal implicitColumnWidth(int column) const;
Q_REVISION(6, 2) Q_INVOKABLE qreal implicitRowHeight(int row) const;
+ Q_REVISION(6, 4) Q_INVOKABLE QModelIndex index(int row, int column) const;
Q_REVISION(6, 4) Q_INVOKABLE virtual QModelIndex modelIndex(const QPoint &cell) const;
- Q_REVISION(6, 4) Q_INVOKABLE virtual QModelIndex modelIndex(int row, int column) const;
Q_REVISION(6, 4) Q_INVOKABLE virtual QPoint cellAtIndex(const QModelIndex &index) const;
Q_REVISION(6, 4) Q_INVOKABLE int rowAtIndex(const QModelIndex &index) const;
Q_REVISION(6, 4) Q_INVOKABLE int columnAtIndex(const QModelIndex &index) const;
@@ -213,6 +224,20 @@ public:
Q_REVISION(6, 5) Q_INVOKABLE void edit(const QModelIndex &index);
Q_REVISION(6, 5) Q_INVOKABLE void closeEditor();
+ Q_REVISION(6, 5) Q_INVOKABLE QQuickItem *itemAtIndex(const QModelIndex &index) const;
+
+#if QT_DEPRECATED_SINCE(6, 5)
+ QT_DEPRECATED_VERSION_X_6_5("Use itemAtIndex(index(row, column)) instead")
+ Q_INVOKABLE QQuickItem *itemAtCell(int column, int row) const;
+ QT_DEPRECATED_VERSION_X_6_5("Use positionViewAtIndex(index(row, column)) instead")
+ Q_INVOKABLE void positionViewAtCell(int column, int row, PositionMode mode, const QPointF &offset = QPointF(), const QRectF &subRect = QRectF());
+#endif
+
+ Q_REVISION(6, 8) Q_INVOKABLE void moveColumn(int source, int destination);
+ Q_REVISION(6, 8) Q_INVOKABLE void moveRow(int source, int destination);
+ Q_REVISION(6, 8) Q_INVOKABLE void clearColumnReordering();
+ Q_REVISION(6, 8) Q_INVOKABLE void clearRowReordering();
+
static QQuickTableViewAttached *qmlAttachedProperties(QObject *);
Q_SIGNALS:
@@ -243,6 +268,10 @@ Q_SIGNALS:
Q_REVISION(6, 5) void resizableRowsChanged();
Q_REVISION(6, 5) void editTriggersChanged();
Q_REVISION(6, 5) void layoutChanged();
+ Q_REVISION(6, 6) void selectionModeChanged();
+ Q_REVISION(6, 8) void rowMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex);
+ Q_REVISION(6, 8) void columnMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex);
+
protected:
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
@@ -265,11 +294,11 @@ private:
qreal maxYExtent() const override;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickTableViewAttached : public QObject
+class Q_QUICK_EXPORT QQuickTableViewAttached : public QObject
{
Q_OBJECT
- Q_PROPERTY(QQuickTableView *view READ view NOTIFY viewChanged)
- Q_PROPERTY(QQmlComponent *editDelegate READ editDelegate WRITE setEditDelegate NOTIFY editDelegateChanged)
+ Q_PROPERTY(QQuickTableView *view READ view NOTIFY viewChanged FINAL)
+ Q_PROPERTY(QQmlComponent *editDelegate READ editDelegate WRITE setEditDelegate NOTIFY editDelegateChanged FINAL)
public:
QQuickTableViewAttached(QObject *parent)
@@ -311,6 +340,4 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickTableView::EditTriggers)
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickTableView)
-
#endif // QQUICKTABLEVIEW_P_H
diff --git a/src/quick/items/qquicktableview_p_p.h b/src/quick/items/qquicktableview_p_p.h
index d8c2b036ac..ea49a3309b 100644
--- a/src/quick/items/qquicktableview_p_p.h
+++ b/src/quick/items/qquicktableview_p_p.h
@@ -23,14 +23,18 @@
#include <QtQml/private/qqmlincubator_p.h>
#include <QtQmlModels/private/qqmlchangeset_p.h>
#include <QtQml/qqmlinfo.h>
+#include <QtGui/qdrag.h>
-#include <QtQuick/private/qminimalflatset_p.h>
#include <QtQuick/private/qquickflickable_p_p.h>
#include <QtQuick/private/qquickitemviewfxitem_p_p.h>
#include <QtQuick/private/qquickanimation_p.h>
#include <QtQuick/private/qquickselectable_p.h>
#include <QtQuick/private/qquicksinglepointhandler_p.h>
#include <QtQuick/private/qquickhoverhandler_p.h>
+#include <QtQuick/private/qquicktaphandler_p.h>
+#include <QtQuick/private/qquickdroparea_p.h>
+
+#include <QtCore/private/qminimalflatset_p.h>
QT_BEGIN_NAMESPACE
@@ -66,15 +70,10 @@ protected:
void handleEventPoint(QPointerEvent *event, QEventPoint &point) override;
};
-/*! \internal
- * TableView uses QQuickTableViewResizeHandler to enable the user to resize
- * rows and columns. By using a custom pointer handler, we can get away with
- * using a single pointer handler for the whole content item, rather than
- * e.g having to split it up into multiple items with drag handlers placed
- * between the cells.
- */
-class QQuickTableViewResizeHandler : public QQuickSinglePointHandler
+class QQuickTableViewPointerHandler : public QQuickSinglePointHandler
{
+ Q_OBJECT
+
public:
enum State {
Listening, // the pointer is not being pressed between the cells
@@ -84,12 +83,28 @@ public:
DraggingFinished // dragging was finished
};
- QQuickTableViewResizeHandler(QQuickTableView *view);
- State state() { return m_state; }
- void updateState(QEventPoint &point);
- void updateDrag(QPointerEvent *event, QEventPoint &point);
+ QQuickTableViewPointerHandler(QQuickTableView *view);
State m_state = Listening;
+ State state() { return m_state; }
+
+protected:
+ bool wantsEventPoint(const QPointerEvent *event, const QEventPoint &point) override;
+};
+
+/*! \internal
+ * TableView uses QQuickTableViewResizeHandler to enable the user to resize
+ * rows and columns. By using a custom pointer handler, we can get away with
+ * using a single pointer handler for the whole content item, rather than
+ * e.g having to split it up into multiple items with drag handlers placed
+ * between the cells.
+ */
+class QQuickTableViewResizeHandler : public QQuickTableViewPointerHandler
+{
+ Q_OBJECT
+
+public:
+ QQuickTableViewResizeHandler(QQuickTableView *view);
int m_row = -1;
qreal m_rowStartY = -1;
@@ -99,16 +114,69 @@ public:
qreal m_columnStartX = -1;
qreal m_columnStartWidth = -1;
+ void updateState(QEventPoint &point);
+ void updateDrag(QPointerEvent *event, QEventPoint &point);
+
friend class QQuickTableViewPrivate;
protected:
- bool wantsEventPoint(const QPointerEvent *event, const QEventPoint &point) override;
void handleEventPoint(QPointerEvent *event, QEventPoint &point) override;
void onGrabChanged(QQuickPointerHandler *grabber, QPointingDevice::GrabTransition transition,
QPointerEvent *ev, QEventPoint &point) override;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickTableViewPrivate : public QQuickFlickablePrivate, public QQuickSelectable
+class QQuickTableViewSectionDragHandler : public QQuickTableViewPointerHandler
+{
+ Q_OBJECT
+
+public:
+ QQuickTableViewSectionDragHandler(QQuickTableView *view);
+ ~QQuickTableViewSectionDragHandler();
+
+ void grabSection();
+
+ void handleDrag(QQuickDragEvent *event);
+ void handleDrop(QQuickDragEvent *event);
+ void handleDragDropAction(Qt::DropAction action);
+
+ void setSectionOrientation(Qt::Orientation orientation) { m_sectionOrientation = orientation; }
+
+ friend class QQuickTableViewPrivate;
+
+protected:
+ void handleEventPoint(QPointerEvent *event, QEventPoint &point) override;
+
+private:
+ void resetDragData();
+ void resetSectionOverlay();
+
+ QSharedPointer<QQuickItemGrabResult> m_grabResult;
+ QPointer<QDrag> m_drag;
+ int m_source = -1;
+ int m_destination = -1;
+ QPointer<QQuickDropArea> m_dropArea;
+ Qt::Orientation m_sectionOrientation;
+
+ QPointF m_dragPoint;
+ QSizeF m_step = QSizeF(1, 1);
+ QTimer m_scrollTimer;
+};
+
+/*! \internal
+ * QQuickTableViewTapHandler used to handle tap events explicitly for table view
+ */
+class QQuickTableViewTapHandler : public QQuickTapHandler
+{
+ Q_OBJECT
+
+public:
+ explicit QQuickTableViewTapHandler(QQuickTableView *view);
+ bool wantsEventPoint(const QPointerEvent *event, const QEventPoint &point) override;
+
+ friend class QQuickTableViewPrivate;
+};
+
+class Q_QUICK_EXPORT QQuickTableViewPrivate : public QQuickFlickablePrivate, public QQuickSelectable
{
public:
Q_DECLARE_PUBLIC(QQuickTableView)
@@ -224,6 +292,11 @@ public:
Done
};
+ enum class SectionState {
+ Idle = 0,
+ Moving
+ };
+
enum class RebuildOption {
None = 0,
All = 0x1,
@@ -343,6 +416,10 @@ public:
QPointer<QItemSelectionModel> selectionModel;
QQuickTableView::SelectionBehavior selectionBehavior = QQuickTableView::SelectCells;
+ QQuickTableView::SelectionMode selectionMode = QQuickTableView::ExtendedSelection;
+ QItemSelectionModel::SelectionFlag selectionFlag = QItemSelectionModel::NoUpdate;
+ std::function<void(CallBackFlag)> selectableCallbackFunction;
+ bool inSelectionModelUpdate = false;
int assignedPositionViewAtRowAfterRebuild = 0;
int assignedPositionViewAtColumnAfterRebuild = 0;
@@ -360,8 +437,10 @@ public:
QPoint selectionStartCell = {-1, -1};
QPoint selectionEndCell = {-1, -1};
+ QItemSelection existingSelection;
QMargins edgesBeforeRebuild;
+ QSize tableSizeBeforeRebuild;
int currentRow = -1;
int currentColumn = -1;
@@ -371,6 +450,8 @@ public:
QQuickTableViewHoverHandler *hoverHandler = nullptr;
QQuickTableViewResizeHandler *resizeHandler = nullptr;
+ QQuickTableViewSectionDragHandler *sectionDragHandler = nullptr;
+ QQuickTableViewPointerHandler *activePtrHandler = nullptr;
QQmlTableInstanceModel *editModel = nullptr;
QQuickItem *editItem = nullptr;
@@ -381,6 +462,16 @@ public:
QString forcedIncubationMode = qEnvironmentVariable("QT_TABLEVIEW_INCUBATION_MODE");
#endif
+ struct SectionData {
+ int index = -1;
+ int prevIndex = -1;
+ };
+
+ QList<SectionData> visualIndices[Qt::Vertical];
+ QList<SectionData> logicalIndices[Qt::Vertical];
+
+ SectionState m_sectionState = SectionState::Idle;
+
public:
void init();
@@ -388,7 +479,7 @@ public:
int modelIndexAtCell(const QPoint &cell) const;
QPoint cellAtModelIndex(int modelIndex) const;
- int modelIndexToCellIndex(const QModelIndex &modelIndex) const;
+ int modelIndexToCellIndex(const QModelIndex &modelIndex, bool visualIndex = true) const;
inline bool cellIsValid(const QPoint &cell) const { return cell.x() != -1 && cell.y() != -1; }
qreal sizeHintForColumn(int column) const;
@@ -502,6 +593,7 @@ public:
virtual void syncModel();
virtual void syncSyncView();
virtual void syncPositionView();
+ virtual QAbstractItemModel *selectionSourceModel();
inline void syncRebuildOptions();
void connectToModel();
@@ -515,6 +607,7 @@ public:
void columnsRemovedCallback(const QModelIndex &parent, int begin, int end);
void layoutChangedCallback(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint);
void modelResetCallback();
+ bool compareModel(const QVariant& model1, const QVariant& model2) const;
void positionViewAtRow(int row, Qt::Alignment alignment, qreal offset, const QRectF subRect = QRectF());
void positionViewAtColumn(int column, Qt::Alignment alignment, qreal offset, const QRectF subRect = QRectF());
@@ -531,7 +624,6 @@ public:
void selectionChangedInSelectionModel(const QItemSelection &selected, const QItemSelection &deselected);
void updateSelectedOnAllDelegateItems();
void setSelectedOnDelegateItem(const QModelIndex &modelIndex, bool select);
- void syncSourceModelInSelectionModel();
bool currentInSelectionModel(const QPoint &cell) const;
void currentChangedInSelectionModel(const QModelIndex &current, const QModelIndex &previous);
@@ -560,18 +652,36 @@ public:
// QQuickSelectable
QQuickItem *selectionPointerHandlerTarget() const override;
- bool startSelection(const QPointF &pos) override;
+ bool startSelection(const QPointF &pos, Qt::KeyboardModifiers modifiers) override;
void setSelectionStartPos(const QPointF &pos) override;
void setSelectionEndPos(const QPointF &pos) override;
void clearSelection() override;
void normalizeSelection() override;
QRectF selectionRectangle() const override;
- QSizeF scrollTowardsSelectionPoint(const QPointF &pos, const QSizeF &step) override;
+ QSizeF scrollTowardsPoint(const QPointF &pos, const QSizeF &step) override;
+ void setCallback(std::function<void(CallBackFlag)> func) override;
+ void cancelSelectionTracking();
QPoint clampedCellAtPos(const QPointF &pos) const;
virtual void updateSelection(const QRect &oldSelection, const QRect &newSelection);
QRect selection() const;
// ----------------
+
+ // Section drag handler
+ void initSectionDragHandler(Qt::Orientation orientation);
+ void destroySectionDragHandler();
+ inline void setActivePointerHandler(QQuickTableViewPointerHandler *handler) { activePtrHandler = handler; }
+ inline QQuickTableViewPointerHandler* activePointerHandler() const { return activePtrHandler; }
+ // Row/Column reordering
+ void moveSection(int source , int destination, Qt::Orientations orientation);
+ void initializeIndexMapping();
+ void clearIndexMapping();
+ void clearSection(Qt::Orientations orientation);
+ virtual int logicalRowIndex(const int visualIndex) const;
+ virtual int logicalColumnIndex(const int visualIndex) const;
+ virtual int visualRowIndex(const int logicalIndex) const;
+ virtual int visualColumnIndex(const int logicalIndex) const;
+ void setContainsDragOnDelegateItem(const QModelIndex &modelIndex, bool overlay);
};
class FxTableItem : public QQuickItemViewFxItem
diff --git a/src/quick/items/qquicktext.cpp b/src/quick/items/qquicktext.cpp
index 517ccba845..e29495f451 100644
--- a/src/quick/items/qquicktext.cpp
+++ b/src/quick/items/qquicktext.cpp
@@ -10,10 +10,8 @@
#include <QtQuick/private/qsgcontext_p.h>
#include <private/qqmlglobal_p.h>
#include <private/qsgadaptationlayer_p.h>
-#include "qquicktextnode_p.h"
-#include "qquickimage_p_p.h"
+#include "qsginternaltextnode_p.h"
#include "qquicktextutil_p.h"
-#include "qquicktextdocument_p.h"
#include <QtQuick/private/qsgtexture_p.h>
@@ -29,14 +27,16 @@
#include <private/qtextengine_p.h>
#include <private/qquickstyledtext_p.h>
-#include <QtQuick/private/qquickpixmapcache_p.h>
+#include <QtQuick/private/qquickpixmap_p.h>
#include <qmath.h>
#include <limits.h>
QT_BEGIN_NAMESPACE
-Q_DECLARE_LOGGING_CATEGORY(lcHoverTrace)
+Q_STATIC_LOGGING_CATEGORY(lcText, "qt.quick.text")
+
+using namespace Qt::StringLiterals;
const QChar QQuickTextPrivate::elideChar = QChar(0x2026);
@@ -47,7 +47,7 @@ const QChar QQuickTextPrivate::elideChar = QChar(0x2026);
const int QQuickTextPrivate::largeTextSizeThreshold = QQUICKTEXT_LARGETEXT_THRESHOLD;
QQuickTextPrivate::QQuickTextPrivate()
- : fontInfo(font), elideLayout(nullptr), textLine(nullptr), lineWidth(0)
+ : fontInfo(font), lineWidth(0)
, color(0xFF000000), linkColor(0xFF0000FF), styleColor(0xFF000000)
, lineCount(1), multilengthEos(-1)
, elideMode(QQuickText::ElideNone), hAlign(QQuickText::AlignLeft), vAlign(QQuickText::AlignTop)
@@ -80,7 +80,6 @@ QQuickTextPrivate::ExtraData::ExtraData()
, doc(nullptr)
, minimumPixelSize(12)
, minimumPointSize(12)
- , nbActiveDownloads(0)
, maximumLineCount(INT_MAX)
, renderTypeQuality(QQuickText::DefaultRenderTypeQuality)
, lineHeightValid(false)
@@ -99,9 +98,6 @@ void QQuickTextPrivate::init()
QQuickTextPrivate::~QQuickTextPrivate()
{
- delete elideLayout;
- delete textLine; textLine = nullptr;
-
if (extra.isAllocated()) {
qDeleteAll(extra->imgTags);
extra->imgTags.clear();
@@ -204,7 +200,7 @@ void QQuickTextPrivate::setBottomPadding(qreal value, bool reset)
Used to decide if the Text should use antialiasing or not. Only Text
with renderType of Text.NativeRendering can disable antialiasing.
- The default is true.
+ The default is \c true.
*/
void QQuickText::q_updateLayout()
@@ -273,32 +269,115 @@ void QQuickTextPrivate::updateLayout()
q->polish();
}
+/*! \internal
+ QTextDocument::loadResource() calls this to load inline images etc.
+ But if it's a local file, don't do it: let QTextDocument::loadResource()
+ load it in the default way. QQuickPixmap is for QtQuick-specific uses.
+*/
+QVariant QQuickText::loadResource(int type, const QUrl &source)
+{
+ Q_D(QQuickText);
+ const QUrl url = d->extra->doc->baseUrl().resolved(source);
+ if (url.isLocalFile()) {
+ // qmlWarning if the file doesn't exist (because QTextDocument::loadResource() can't do that)
+ const QFileInfo fi(QQmlFile::urlToLocalFileOrQrc(url));
+ if (!fi.exists())
+ qmlWarning(this) << "Cannot open: " << url.toString();
+ // let QTextDocument::loadResource() handle local file loading
+ return {};
+ }
+ // see if we already started a load job
+ for (auto it = d->extra->pixmapsInProgress.cbegin(); it != d->extra->pixmapsInProgress.cend();) {
+ auto *job = *it;
+ if (job->url() == url) {
+ if (job->isError()) {
+ qmlWarning(this) << job->error();
+ delete *it;
+ it = d->extra->pixmapsInProgress.erase(it);
+ return QImage();
+ }
+ qCDebug(lcText) << "already downloading" << url;
+ // existing job: return a null variant if it's not done yet
+ return job->isReady() ? job->image() : QVariant();
+ }
+ ++it;
+ }
+ qCDebug(lcText) << "loading" << source << "resolved" << url
+ << "type" << static_cast<QTextDocument::ResourceType>(type);
+ QQmlContext *context = qmlContext(this);
+ Q_ASSERT(context);
+ // don't cache it in QQuickPixmapCache, because it's cached in QTextDocumentPrivate::cachedResources
+ QQuickPixmap *p = new QQuickPixmap(context->engine(), url, QQuickPixmap::Options{});
+ p->connectFinished(this, SLOT(resourceRequestFinished()));
+ d->extra->pixmapsInProgress.append(p);
+ // the new job is probably not done; return a null variant if the caller should poll again
+ return p->isReady() ? p->image() : QVariant();
+}
+
+/*! \internal
+ Handle completion of a download that QQuickText::loadResource() started.
+*/
+void QQuickText::resourceRequestFinished()
+{
+ Q_D(QQuickText);
+ bool allDone = true;
+ for (auto it = d->extra->pixmapsInProgress.cbegin(); it != d->extra->pixmapsInProgress.cend();) {
+ auto *job = *it;
+ if (job->isError()) {
+ // get QTextDocument::loadResource() to call QQuickText::loadResource() again, to return the placeholder
+ qCDebug(lcText) << "failed to load" << job->url();
+ d->extra->doc->resource(QTextDocument::ImageResource, job->url());
+ } else if (job->isReady()) {
+ // get QTextDocument::loadResource() to call QQuickText::loadResource() again, and cache the result
+ auto res = d->extra->doc->resource(QTextDocument::ImageResource, job->url());
+ // If QTextDocument::resource() returned a valid variant, it's been cached too. Either way, the job is done.
+ qCDebug(lcText) << (res.isValid() ? "done downloading" : "failed to load") << job->url();
+ delete *it;
+ it = d->extra->pixmapsInProgress.erase(it);
+ } else {
+ allDone = false;
+ ++it;
+ }
+ }
+ if (allDone) {
+ Q_ASSERT(d->extra->pixmapsInProgress.isEmpty());
+ d->updateLayout();
+ }
+}
+
+/*! \internal
+ Handle completion of StyledText image downloads (there's no QTextDocument instance in that case).
+*/
void QQuickText::imageDownloadFinished()
{
Q_D(QQuickText);
+ if (!d->extra.isAllocated())
+ return;
- (d->extra->nbActiveDownloads)--;
+ if (std::any_of(d->extra->imgTags.cbegin(), d->extra->imgTags.cend(),
+ [] (auto *image) { return image->pix && image->pix->isLoading(); })) {
+ // return if we still have any active download
+ return;
+ }
// when all the remote images have been downloaded,
// if one of the sizes was not specified at parsing time
// we use the implicit size from pixmapcache and re-layout.
- if (d->extra.isAllocated() && d->extra->nbActiveDownloads == 0) {
- bool needToUpdateLayout = false;
- for (QQuickStyledTextImgTag *img : std::as_const(d->extra->visibleImgTags)) {
- if (!img->size.isValid()) {
- img->size = img->pix->implicitSize();
- needToUpdateLayout = true;
- }
+ bool needToUpdateLayout = false;
+ for (QQuickStyledTextImgTag *img : std::as_const(d->extra->visibleImgTags)) {
+ if (!img->size.isValid()) {
+ img->size = img->pix->implicitSize();
+ needToUpdateLayout = true;
}
+ }
- if (needToUpdateLayout) {
- d->textHasChanged = true;
- d->updateLayout();
- } else {
- d->updateType = QQuickTextPrivate::UpdatePaintNode;
- update();
- }
+ if (needToUpdateLayout) {
+ d->textHasChanged = true;
+ d->updateLayout();
+ } else {
+ d->updateType = QQuickTextPrivate::UpdatePaintNode;
+ update();
}
}
@@ -430,8 +509,13 @@ void QQuickTextPrivate::updateSize()
layedOutTextRect = QRectF(QPointF(0,0), dsize);
size = QSizeF(extra->doc->idealWidth(),dsize.height());
- QFontMetricsF fm(font);
- updateBaseline(fm.ascent(), q->height() - size.height() - vPadding);
+
+ qreal baseline = QFontMetricsF(font).ascent();
+ QTextBlock firstBlock = extra->doc->firstBlock();
+ if (firstBlock.isValid() && firstBlock.layout() != nullptr && firstBlock.lineCount() > 0)
+ baseline = firstBlock.layout()->lineAt(0).ascent();
+
+ updateBaseline(baseline, q->height() - size.height() - vPadding);
//### need to confirm cost of always setting these for richText
internalWidthUpdate = true;
@@ -468,7 +552,7 @@ void QQuickTextPrivate::updateSize()
QTextLine firstLine = firstBlock.layout()->lineAt(0);
QTextLine lastLine = lastBlock.layout()->lineAt(lastBlock.layout()->lineCount() - 1);
advance = QSizeF(lastLine.horizontalAdvance(),
- (lastLine.y() + lastBlock.layout()->position().y()) - (firstLine.y() + firstBlock.layout()->position().y()));
+ (lastLine.y() + lastBlock.layout()->position().y() + lastLine.ascent()) - (firstLine.y() + firstBlock.layout()->position().y() + firstLine.ascent()));
} else {
advance = QSizeF();
}
@@ -591,7 +675,7 @@ void QQuickTextPrivate::setupCustomLineGeometry(QTextLine &line, qreal &height,
Q_Q(QQuickText);
if (!textLine)
- textLine = new QQuickTextLine;
+ textLine.reset(new QQuickTextLine);
textLine->setFullLayoutTextLength(fullLayoutTextLength);
textLine->setLine(&line);
textLine->setY(height);
@@ -607,7 +691,7 @@ void QQuickTextPrivate::setupCustomLineGeometry(QTextLine &line, qreal &height,
if (lineHeight() != 1.0)
textLine->setHeight((lineHeightMode() == QQuickText::FixedHeight) ? lineHeight() : line.height() * lineHeight());
- emit q->lineLaidOut(textLine);
+ emit q->lineLaidOut(textLine.get());
height += textLine->height();
}
@@ -1103,7 +1187,7 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline)
if (elide) {
if (!elideLayout) {
- elideLayout = new QTextLayout;
+ elideLayout.reset(new QTextLayout);
elideLayout->setCacheEnabled(true);
}
QTextEngine *engine = layout.engine();
@@ -1153,8 +1237,7 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline)
if (visibleCount == 1)
layout.clearLayout();
} else {
- delete elideLayout;
- elideLayout = nullptr;
+ elideLayout.reset();
}
QTextLine firstLine = visibleCount == 1 && elideLayout
@@ -1203,12 +1286,10 @@ void QQuickTextPrivate::setLineGeometry(QTextLine &line, qreal lineWidth, qreal
if (!image->pix) {
const QQmlContext *context = qmlContext(q);
const QUrl url = context->resolvedUrl(q->baseUrl()).resolved(image->url);
- image->pix = new QQuickPixmap(context->engine(), url, QRect(), image->size);
+ image->pix.reset(new QQuickPixmap(context->engine(), url, QRect(), image->size * devicePixelRatio()));
+
if (image->pix->isLoading()) {
image->pix->connectFinished(q, SLOT(imageDownloadFinished()));
- if (!extra.isAllocated() || !extra->nbActiveDownloads)
- extra.value().nbActiveDownloads = 0;
- extra->nbActiveDownloads++;
} else if (image->pix->isReady()) {
if (!image->size.isValid()) {
image->size = image->pix->implicitSize();
@@ -1266,13 +1347,14 @@ void QQuickTextPrivate::ensureDoc()
{
if (!extra.isAllocated() || !extra->doc) {
Q_Q(QQuickText);
- extra.value().doc = new QQuickTextDocumentWithImageResources(q);
- extra->doc->setPageSize(QSizeF(0, 0));
- extra->doc->setDocumentMargin(0);
+ extra.value().doc = new QTextDocument(q);
+ auto *doc = extra->doc;
+ extra->imageHandler = new QQuickTextImageHandler(doc);
+ doc->documentLayout()->registerHandler(QTextFormat::ImageObject, extra->imageHandler);
+ doc->setPageSize(QSizeF(0, 0));
+ doc->setDocumentMargin(0);
const QQmlContext *context = qmlContext(q);
- extra->doc->setBaseUrl(context ? context->resolvedUrl(q->baseUrl()) : q->baseUrl());
- qmlobject_connect(extra->doc, QQuickTextDocumentWithImageResources, SIGNAL(imagesLoaded()),
- q, QQuickText, SLOT(q_updateLayout()));
+ doc->setBaseUrl(context ? context->resolvedUrl(q->baseUrl()) : q->baseUrl());
}
}
@@ -1289,10 +1371,14 @@ void QQuickTextPrivate::updateDocumentText()
#else
extra->doc->setPlainText(text);
#endif
- extra->doc->clearResources();
rightToLeftText = extra->doc->toPlainText().isRightToLeft();
}
+qreal QQuickTextPrivate::devicePixelRatio() const
+{
+ return (window ? window->effectiveDevicePixelRatio() : qApp->devicePixelRatio());
+}
+
/*!
\qmltype Text
\instantiates QQuickText
@@ -1301,8 +1387,8 @@ void QQuickTextPrivate::updateDocumentText()
\inherits Item
\brief Specifies how to add formatted text to a scene.
- Text items can display both plain and rich text. For example, red text with
- a specific font and size can be defined like this:
+ Text items can display both plain and rich text. For example, you can define
+ red text with a specific font and size like this:
\qml
Text {
@@ -1313,25 +1399,47 @@ void QQuickTextPrivate::updateDocumentText()
}
\endqml
- Rich text is defined using HTML-style markup:
+ Use HTML-style markup or Markdown to define rich text:
+ \if defined(onlinedocs)
+ \tab {build-qt-app}{tab-html}{HTML-style}{checked}
+ \tab {build-qt-app}{tab-md}{Markdown}{}
+ \tabcontent {tab-html}
+ \else
+ \section1 Using HTML-style
+ \endif
\qml
Text {
text: "<b>Hello</b> <i>World!</i>"
}
\endqml
+ \if defined(onlinedocs)
+ \endtabcontent
+ \tabcontent {tab-md}
+ \else
+ \section1 Using Markdown
+ \endif
+ \qml
+ Text {
+ text: "**Hello** *World!*"
+ }
+ \endqml
+ \if defined(onlinedocs)
+ \endtabcontent
+ \endif
\image declarative-text.png
- If height and width are not explicitly set, Text will attempt to determine how
- much room is needed and set it accordingly. Unless \l wrapMode is set, it will always
- prefer width to height (all text will be placed on a single line).
+ If height and width are not explicitly set, Text will try to determine how
+ much room is needed and set it accordingly. Unless \l wrapMode is set, it
+ will always prefer width to height (all text will be placed on a single
+ line).
- The \l elide property can alternatively be used to fit a single line of
- plain text to a set width.
+ To fit a single line of plain text to a set width, you can use the \l elide
+ property.
- Note that the \l{Supported HTML Subset} is limited. Also, if the text contains
- HTML img tags that load remote images, the text is reloaded.
+ Note that the \l{Supported HTML Subset} is limited. Also, if the text
+ contains HTML img tags that load remote images, the text is reloaded.
Text provides read-only text. For editable text, see \l TextEdit.
@@ -1353,13 +1461,18 @@ QQuickText::QQuickText(QQuickTextPrivate &dd, QQuickItem *parent)
QQuickText::~QQuickText()
{
+ Q_D(QQuickText);
+ if (d->extra.isAllocated()) {
+ qDeleteAll(d->extra->pixmapsInProgress);
+ d->extra->pixmapsInProgress.clear();
+ }
}
/*!
\qmlproperty bool QtQuick::Text::clip
This property holds whether the text is clipped.
- Note that if the text does not fit in the bounding rectangle it will be abruptly chopped.
+ Note that if the text does not fit in the bounding rectangle, it will be abruptly chopped.
If you want to display potentially long text in a limited space, you probably want to use \c elide instead.
*/
@@ -1448,7 +1561,8 @@ QQuickText::~QQuickText()
Sets the family name of the font.
- The family name is case insensitive and may optionally include a foundry name, e.g. "Helvetica [Cronyx]".
+ The family name is case insensitive and may optionally include a foundry
+ name, for example "Helvetica [Cronyx]".
If the family is available from more than one foundry and the foundry isn't specified, an arbitrary foundry is chosen.
If the family isn't available a family will be set using the font matching algorithm.
*/
@@ -1474,17 +1588,16 @@ QQuickText::~QQuickText()
The requested weight of the font. The weight requested must be an integer
between 1 and 1000, or one of the predefined values:
- \list
- \li Font.Thin
- \li Font.Light
- \li Font.ExtraLight
- \li Font.Normal - the default
- \li Font.Medium
- \li Font.DemiBold
- \li Font.Bold
- \li Font.ExtraBold
- \li Font.Black
- \endlist
+
+ \value Font.Thin 100
+ \value Font.ExtraLight 200
+ \value Font.Light 300
+ \value Font.Normal 400 (default)
+ \value Font.Medium 500
+ \value Font.DemiBold 600
+ \value Font.Bold 700
+ \value Font.ExtraBold 800
+ \value Font.Black 900
\qml
Text { text: "Hello"; font.weight: Font.DemiBold }
@@ -1548,13 +1661,12 @@ QQuickText::~QQuickText()
Sets the capitalization for the text.
- \list
- \li Font.MixedCase - This is the normal text rendering option where no capitalization change is applied.
- \li Font.AllUppercase - This alters the text to be rendered in all uppercase type.
- \li Font.AllLowercase - This alters the text to be rendered in all lowercase type.
- \li Font.SmallCaps - This alters the text to be rendered in small-caps type.
- \li Font.Capitalize - This alters the text to be rendered with the first character of each word as an uppercase character.
- \endlist
+ \value Font.MixedCase the normal case: no capitalization change is applied
+ \value Font.AllUppercase alters the text to be rendered in all uppercase type
+ \value Font.AllLowercase alters the text to be rendered in all lowercase type
+ \value Font.SmallCaps alters the text to be rendered in small-caps type
+ \value Font.Capitalize alters the text to be rendered with the first character of
+ each word as an uppercase character
\qml
Text { text: "Hello"; font.capitalization: Font.AllLowercase }
@@ -1571,23 +1683,21 @@ QQuickText::~QQuickText()
\note This property only has an effect when used together with render type Text.NativeRendering.
- \list
- \value Font.PreferDefaultHinting - Use the default hinting level for the target platform.
- \value Font.PreferNoHinting - If possible, render text without hinting the outlines
+ \value Font.PreferDefaultHinting Use the default hinting level for the target platform.
+ \value Font.PreferNoHinting If possible, render text without hinting the outlines
of the glyphs. The text layout will be typographically accurate, using the same metrics
- as are used e.g. when printing.
- \value Font.PreferVerticalHinting - If possible, render text with no horizontal hinting,
+ as are used, for example, when printing.
+ \value Font.PreferVerticalHinting If possible, render text with no horizontal hinting,
but align glyphs to the pixel grid in the vertical direction. The text will appear
crisper on displays where the density is too low to give an accurate rendering
of the glyphs. But since the horizontal metrics of the glyphs are unhinted, the text's
layout will be scalable to higher density devices (such as printers) without impacting
details such as line breaks.
- \value Font.PreferFullHinting - If possible, render text with hinting in both horizontal and
+ \value Font.PreferFullHinting If possible, render text with hinting in both horizontal and
vertical directions. The text will be altered to optimize legibility on the target
device, but since the metrics will depend on the target size of the text, the positions
of glyphs, line breaks, and other typographical detail will not scale, meaning that a
text layout may look different on devices with different pixel densities.
- \endlist
\qml
Text { text: "Hello"; renderType: Text.NativeRendering; font.hintingPreference: Font.PreferVerticalHinting }
@@ -1613,7 +1723,7 @@ QQuickText::~QQuickText()
Sometimes, a font will apply complex rules to a set of characters in order to
display them correctly. In some writing systems, such as Brahmic scripts, this is
- required in order for the text to be legible, but in e.g. Latin script, it is merely
+ required in order for the text to be legible, but in for example Latin script, it is merely
a cosmetic feature. Setting the \c preferShaping property to false will disable all
such features when they are not required, which will improve performance in most cases.
@@ -1623,6 +1733,167 @@ QQuickText::~QQuickText()
Text { text: "Some text"; font.preferShaping: false }
\endqml
*/
+
+/*!
+ \qmlproperty object QtQuick::Text::font.variableAxes
+ \since 6.7
+
+//! [qml-font-variable-axes]
+ Applies floating point values to variable axes in variable fonts.
+
+ Variable fonts provide a way to store multiple variations (with different weights, widths
+ or styles) in the same font file. The variations are given as floating point values for
+ a pre-defined set of parameters, called "variable axes". Specific instances are typically
+ given names by the font designer, and, in Qt, these can be selected using setStyleName()
+ just like traditional sub-families.
+
+ In some cases, it is also useful to provide arbitrary values for the different axes. For
+ instance, if a font has a Regular and Bold sub-family, you may want a weight in-between these.
+ You could then manually request this by supplying a custom value for the "wght" axis in the
+ font.
+
+ \qml
+ Text {
+ text: "Foobar"
+ font.family: "MyVariableFont"
+ font.variableAxes: { "wght": (Font.Normal + Font.Bold) / 2.0 }
+ }
+ \endqml
+
+ If the "wght" axis is supported by the font and the given value is within its defined range,
+ a font corresponding to the weight 550.0 will be provided.
+
+ There are a few standard axes than many fonts provide, such as "wght" (weight), "wdth" (width),
+ "ital" (italic) and "opsz" (optical size). They each have indivdual ranges defined in the font
+ itself. For instance, "wght" may span from 100 to 900 (QFont::Thin to QFont::Black) whereas
+ "ital" can span from 0 to 1 (from not italic to fully italic).
+
+ A font may also choose to define custom axes; the only limitation is that the name has to
+ meet the requirements for a QFont::Tag (sequence of four latin-1 characters.)
+
+ By default, no variable axes are set.
+
+ \note On Windows, variable axes are not supported if the optional GDI font backend is in use.
+
+ \sa QFont::setVariableAxis()
+//! [qml-font-variable-axes]
+*/
+
+
+/*!
+ \qmlproperty object QtQuick::Text::font.features
+ \since 6.6
+
+//! [qml-font-features]
+ Applies integer values to specific OpenType features when shaping the text based on the contents
+ in \a features. This provides advanced access to the font shaping process, and can be used
+ to support font features that are otherwise not covered in the API.
+
+ The font features are represented by a map from four-letter tags to integer values. This integer
+ value passed along with the tag in most cases represents a boolean value: A zero value means the
+ feature is disabled, and a non-zero value means it is enabled. For certain font features,
+ however, it may have other interpretations. For example, when applied to the \c salt feature, the
+ value is an index that specifies the stylistic alternative to use.
+
+ For example, the \c frac font feature will convert diagonal fractions separated with a slash
+ (such as \c 1/2) with a different representation. Typically this will involve baking the full
+ fraction into a single character width (such as \c ½).
+
+ If a font supports the \c frac feature, then it can be enabled in the shaper as in the following
+ code:
+
+ \qml
+ Text {
+ text: "One divided by two is 1/2"
+ font.family: "MyFractionFont"
+ font.features: { "frac": 1 }
+ }
+ \endqml
+
+ Multiple features can be assigned values in the same mapping. For instance,
+ if you would like to also disable kerning for the font, you can explicitly
+ disable this as follows:
+
+ \qml
+ Text {
+ text: "One divided by two is 1/2"
+ font.family: "MyFractionFont"
+ font.features: { "frac": 1, "kern": 0 }
+ }
+ \endqml
+
+ You can also collect the font properties in an object:
+
+ \qml
+ Text {
+ text: "One divided by two is 1/2"
+ font: {
+ family: "MyFractionFont"
+ features: { "frac": 1, "kern": 0 }
+ }
+ }
+ \endqml
+
+ \note By default, Qt will enable and disable certain font features based on other font
+ properties. In particular, the \c kern feature will be enabled/disabled depending on the
+ \l font.kerning property of the QFont. In addition, all ligature features (\c liga, \c clig,
+ \c dlig, \c hlig) will be disabled if a \l font.letterSpacing is set, but only for writing
+ systems where the use of ligature is cosmetic. For writing systems where ligatures are required,
+ the features will remain in their default state. The values set using \c font.features will
+ override the default behavior. If, for instance, \c{"kern"} is set to 1, then kerning will
+ always be enabled, regardless of whether the \l font.kerning property is set to false. Similarly,
+ if it is set to \c 0, it will always be disabled.
+
+ \sa QFont::setFeature()
+//! [qml-font-features]
+*/
+
+/*!
+ \qmlproperty bool QtQuick::Text::font.contextFontMerging
+ \since 6.8
+
+//! [qml-font-context-font-merging]
+ If the selected font does not contain a certain character, Qt automatically chooses a
+ similar-looking fallback font that contains the character. By default this is done on a
+ character-by-character basis.
+
+ This means that in certain uncommon cases, many different fonts may be used to represent one
+ string of text even if it's in the same script. Setting \c contextFontMerging to true will try
+ finding the fallback font that matches the largest subset of the input string instead. This
+ will be more expensive for strings where missing glyphs occur, but may give more consistent
+ results. By default, \c contextFontMerging is \c{false}.
+
+ \sa QFont::StyleStrategy
+//! [qml-font-context-font-merging]
+*/
+
+/*!
+ \qmlproperty bool QtQuick::Text::font.preferTypoLineMetrics
+ \since 6.8
+
+//! [qml-font-prefer-typo-line-metrics] For compatibility reasons, OpenType fonts contain two
+ competing sets of the vertical line metrics that provide the \l{QFontMetricsF::ascent()}{ascent},
+ \l{QFontMetricsF::descent()}{descent} and \l{QFontMetricsF::leading()}{leading} of the font. These
+ are often referred to as the
+ \l{https://learn.microsoft.com/en-us/typography/opentype/spec/os2#uswinascent}{win} (Windows)
+ metrics and the \l{https://learn.microsoft.com/en-us/typography/opentype/spec/os2#sta}{typo}
+ (typographical) metrics. While the specification recommends using the \c typo metrics for line
+ spacing, many applications prefer the \c win metrics unless the \c{USE_TYPO_METRICS} flag is set in
+ the \l{https://learn.microsoft.com/en-us/typography/opentype/spec/os2#fsselection}{fsSelection}
+ field of the font. For backwards-compatibility reasons, this is also the case for Qt applications.
+ This is not an issue for fonts that set the \c{USE_TYPO_METRICS} flag to indicate that the \c{typo}
+ metrics are valid, nor for fonts where the \c{win} metrics and \c{typo} metrics match up. However,
+ for certain fonts the \c{win} metrics may be larger than the preferable line spacing and the
+ \c{USE_TYPO_METRICS} flag may be unset by mistake. For such fonts, setting
+ \c{font.preferTypoLineMetrics} may give superior results.
+
+ By default, \c preferTypoLineMetrics is \c{false}.
+
+ \sa QFont::StyleStrategy
+//! [qml-font-prefer-typo-line-metrics]
+*/
+
+
QFont QQuickText::font() const
{
Q_D(const QQuickText);
@@ -1677,14 +1948,30 @@ void QQuickText::itemChange(ItemChange change, const ItemChangeData &value)
break;
case ItemDevicePixelRatioHasChanged:
- if (d->renderType == NativeRendering) {
- // Native rendering optimizes for a given pixel grid, so its results must not be scaled.
- // Text layout code respects the current device pixel ratio automatically, we only need
- // to rerun layout after the ratio changed.
- // Changes of implicit size should be minimal; they are hard to avoid.
- d->implicitWidthValid = false;
- d->implicitHeightValid = false;
- d->updateLayout();
+ {
+ bool needUpdateLayout = false;
+ if (d->renderType == NativeRendering) {
+ // Native rendering optimizes for a given pixel grid, so its results must not be scaled.
+ // Text layout code respects the current device pixel ratio automatically, we only need
+ // to rerun layout after the ratio changed.
+ // Changes of implicit size should be minimal; they are hard to avoid.
+ d->implicitWidthValid = false;
+ d->implicitHeightValid = false;
+ needUpdateLayout = true;
+ }
+
+ if (d->extra.isAllocated()) {
+ // check if we have scalable inline images with explicit size set, which should be reloaded
+ for (QQuickStyledTextImgTag *image : std::as_const(d->extra->visibleImgTags)) {
+ if (image->size.isValid() && QQuickPixmap::isScalableImageFormat(image->url)) {
+ image->pix.reset();
+ needUpdateLayout = true;
+ }
+ }
+ }
+
+ if (needUpdateLayout)
+ d->updateLayout();
}
break;
@@ -1823,12 +2110,11 @@ void QQuickText::setLinkColor(const QColor &color)
Set an additional text style.
Supported text styles are:
- \list
- \li Text.Normal - the default
- \li Text.Outline
- \li Text.Raised
- \li Text.Sunken
- \endlist
+
+ \value Text.Normal - the default
+ \value Text.Outline
+ \value Text.Raised
+ \value Text.Sunken
\qml
Row {
@@ -2028,12 +2314,17 @@ void QQuickText::setVAlign(VAlignment align)
Set this property to wrap the text to the Text item's width. The text will only
wrap if an explicit width has been set. wrapMode can be one of:
- \list
- \li Text.NoWrap (default) - no wrapping will be performed. If the text contains insufficient newlines, then \l contentWidth will exceed a set width.
- \li Text.WordWrap - wrapping is done on word boundaries only. If a word is too long, \l contentWidth will exceed a set width.
- \li Text.WrapAnywhere - wrapping is done at any point on a line, even if it occurs in the middle of a word.
- \li Text.Wrap - if possible, wrapping occurs at a word boundary; otherwise it will occur at the appropriate point on the line, even in the middle of a word.
- \endlist
+ \value Text.NoWrap
+ (default) no wrapping will be performed. If the text contains
+ insufficient newlines, then \l contentWidth will exceed a set width.
+ \value Text.WordWrap
+ wrapping is done on word boundaries only. If a word is too long,
+ \l contentWidth will exceed a set width.
+ \value Text.WrapAnywhere
+ wrapping is done at any point on a line, even if it occurs in the middle of a word.
+ \value Text.Wrap
+ if possible, wrapping occurs at a word boundary; otherwise it will occur
+ at the appropriate point on the line, even in the middle of a word.
*/
QQuickText::WrapMode QQuickText::wrapMode() const
{
@@ -2139,7 +2430,7 @@ void QQuickText::resetMaximumLineCount()
\l {https://guides.github.com/features/mastering-markdown/}{GitHub}
extensions for tables and task lists (since 5.14)
- If the text format is \c Text.AutoText the Text item
+ If the text format is \c Text.AutoText, the Text item
will automatically determine whether the text should be treated as
styled text. This determination is made using Qt::mightBeRichText(),
which can detect the presence of an HTML tag on the first line of text,
@@ -2184,7 +2475,6 @@ void QQuickText::resetMaximumLineCount()
\list
\li code blocks use the \l {QFontDatabase::FixedFont}{default monospace font} but without a surrounding highlight box
\li block quotes are indented, but there is no vertical line alongside the quote
- \li horizontal rules are not rendered
\endlist
*/
QQuickText::TextFormat QQuickText::textFormat() const
@@ -2230,12 +2520,11 @@ void QQuickText::setTextFormat(TextFormat format)
This property cannot be used with rich text.
Eliding can be:
- \list
- \li Text.ElideNone - the default
- \li Text.ElideLeft
- \li Text.ElideMiddle
- \li Text.ElideRight
- \endlist
+
+ \value Text.ElideNone - the default
+ \value Text.ElideLeft
+ \value Text.ElideMiddle
+ \value Text.ElideRight
If this property is set to Text.ElideRight, it can be used with \l {wrapMode}{wrapped}
text. The text will only elide if \c maximumLineCount, or \c height has been set.
@@ -2269,7 +2558,7 @@ void QQuickText::setElideMode(QQuickText::TextElideMode mode)
/*!
\qmlproperty url QtQuick::Text::baseUrl
- This property specifies a base URL which is used to resolve relative URLs
+ This property specifies a base URL that is used to resolve relative URLs
within the text.
Urls are resolved to be within the same directory as the target of the base
@@ -2439,8 +2728,10 @@ void QQuickText::geometryChange(const QRectF &newGeometry, const QRectF &oldGeom
}
}
} else if (!heightChanged && widthMaximum) {
- if (!qFuzzyIsNull(oldGeometry.width())) {
+ if (oldGeometry.width() > 0) {
// no change to height, width is adequate and wasn't 0 before
+ // (old width could also be negative if it was 0 and the margins
+ // were set)
goto geomChangeDone;
}
}
@@ -2485,46 +2776,48 @@ QSGNode *QQuickText::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data
const qreal dy = QQuickTextUtil::alignedY(d->layedOutTextRect.height() + d->lineHeightOffset(), d->availableHeight(), d->vAlign) + topPadding();
- QQuickTextNode *node = nullptr;
+ QSGInternalTextNode *node = nullptr;
if (!oldNode)
- node = new QQuickTextNode(this);
+ node = d->sceneGraphContext()->createInternalTextNode(d->sceneGraphRenderContext());
else
- node = static_cast<QQuickTextNode *>(oldNode);
+ node = static_cast<QSGInternalTextNode *>(oldNode);
+
+ node->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
- node->setUseNativeRenderer(d->renderType == NativeRendering);
+ node->setTextStyle(QSGTextNode::TextStyle(d->style));
+ node->setRenderType(QSGTextNode::RenderType(d->renderType));
node->setRenderTypeQuality(d->renderTypeQuality());
- node->deleteContent();
+ node->clear();
node->setMatrix(QMatrix4x4());
- const QColor color = QColor::fromRgba(d->color);
- const QColor styleColor = QColor::fromRgba(d->styleColor);
- const QColor linkColor = QColor::fromRgba(d->linkColor);
+ node->setColor(QColor::fromRgba(d->color));
+ node->setStyleColor(QColor::fromRgba(d->styleColor));
+ node->setLinkColor(QColor::fromRgba(d->linkColor));
if (d->richText) {
+ node->setViewport(clipRect());
const qreal dx = QQuickTextUtil::alignedX(d->layedOutTextRect.width(), d->availableWidth(), effectiveHAlign()) + leftPadding();
d->ensureDoc();
- node->addTextDocument(QPointF(dx, dy), d->extra->doc, color, d->style, styleColor, linkColor);
+ node->addTextDocument(QPointF(dx, dy), d->extra->doc);
} else if (d->layedOutTextRect.width() > 0) {
+ if (flags().testFlag(ItemObservesViewport))
+ node->setViewport(clipRect());
+ else
+ node->setViewport(QRectF{});
const qreal dx = QQuickTextUtil::alignedX(d->lineWidth, d->availableWidth(), effectiveHAlign()) + leftPadding();
int unelidedLineCount = d->lineCount;
if (d->elideLayout)
unelidedLineCount -= 1;
- if (unelidedLineCount > 0) {
- node->addTextLayout(
- QPointF(dx, dy),
- &d->layout,
- color, d->style, styleColor, linkColor,
- QColor(), QColor(), -1, -1,
- 0, unelidedLineCount);
- }
+ if (unelidedLineCount > 0)
+ node->addTextLayout(QPointF(dx, dy), &d->layout, -1, -1,0, unelidedLineCount);
+
if (d->elideLayout)
- node->addTextLayout(QPointF(dx, dy), d->elideLayout, color, d->style, styleColor, linkColor);
+ node->addTextLayout(QPointF(dx, dy), d->elideLayout.get());
if (d->extra.isAllocated()) {
for (QQuickStyledTextImgTag *img : std::as_const(d->extra->visibleImgTags)) {
- QQuickPixmap *pix = img->pix;
- if (pix && pix->isReady())
- node->addImage(QRectF(img->pos.x() + dx, img->pos.y() + dy, pix->width(), pix->height()), pix->image());
+ if (img->pix && img->pix->isReady())
+ node->addImage(QRectF(img->pos.x() + dx, img->pos.y() + dy, img->size.width(), img->size.height()), img->pix->image());
}
}
}
@@ -2539,6 +2832,11 @@ QSGNode *QQuickText::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data
void QQuickText::updatePolish()
{
Q_D(QQuickText);
+ const bool clipNodeChanged =
+ d->componentComplete && d->clipNode() && d->clipNode()->rect() != clipRect();
+ if (clipNodeChanged)
+ d->dirty(QQuickItemPrivate::Clip);
+
// If the fonts used for rendering are different from the ones used in the GUI thread,
// it means we will get warnings and corrupted text. If this case is detected, we need
// to update the text layout before creating the scenegraph nodes.
@@ -2556,7 +2854,7 @@ void QQuickText::updatePolish()
\qmlproperty real QtQuick::Text::contentWidth
Returns the width of the text, including width past the width
- which is covered due to insufficient wrapping if WrapMode is set.
+ that is covered due to insufficient wrapping if WrapMode is set.
*/
qreal QQuickText::contentWidth() const
{
@@ -2568,7 +2866,7 @@ qreal QQuickText::contentWidth() const
\qmlproperty real QtQuick::Text::contentHeight
Returns the height of the text, including height past the height
- which is covered due to there being more text than fits in the set height.
+ that is covered due to there being more text than fits in the set height.
*/
qreal QQuickText::contentHeight() const
{
@@ -2611,11 +2909,9 @@ void QQuickText::setLineHeight(qreal lineHeight)
This property determines how the line height is specified.
The possible values are:
- \list
- \li Text.ProportionalHeight (default) - this sets the spacing proportional to the
- line (as a multiplier). For example, set to 2 for double spacing.
- \li Text.FixedHeight - this sets the line height to a fixed line height (in pixels).
- \endlist
+ \value Text.ProportionalHeight (default) sets the spacing proportional to the line
+ (as a multiplier). For example, set to 2 for double spacing.
+ \value Text.FixedHeight sets the line height to a fixed line height (in pixels).
*/
QQuickText::LineHeightMode QQuickText::lineHeightMode() const
{
@@ -2643,16 +2939,16 @@ void QQuickText::setLineHeightMode(LineHeightMode mode)
This property specifies how the font size of the displayed text is determined.
The possible values are:
- \list
- \li Text.FixedSize (default) - The size specified by \l font.pixelSize
- or \l font.pointSize is used.
- \li Text.HorizontalFit - The largest size up to the size specified that fits
- within the width of the item without wrapping is used.
- \li Text.VerticalFit - The largest size up to the size specified that fits
- the height of the item is used.
- \li Text.Fit - The largest size up to the size specified that fits within the
- width and height of the item is used.
- \endlist
+ \value Text.FixedSize
+ (default) The size specified by \l font.pixelSize or \l font.pointSize is used.
+ \value Text.HorizontalFit
+ The largest size up to the size specified that fits within the width of the item
+ without wrapping is used.
+ \value Text.VerticalFit
+ The largest size up to the size specified that fits the height of the item is used.
+ \value Text.Fit
+ The largest size up to the size specified that fits within the width and height
+ of the item is used.
The font size of fitted text has a minimum bound specified by the
minimumPointSize or minimumPixelSize property and maximum bound specified
@@ -2757,8 +3053,8 @@ void QQuickText::setMinimumPointSize(int size)
int QQuickText::resourcesLoading() const
{
Q_D(const QQuickText);
- if (d->richText && d->extra.isAllocated() && d->extra->doc)
- return d->extra->doc->resourcesLoading();
+ if (d->richText && d->extra.isAllocated())
+ return d->extra->pixmapsInProgress.size();
return 0;
}
@@ -2808,7 +3104,7 @@ QString QQuickTextPrivate::anchorAt(const QPointF &mousePos) const
if (styledText) {
QString link = anchorAt(&layout, translatedMousePos);
if (link.isEmpty() && elideLayout)
- link = anchorAt(elideLayout, translatedMousePos);
+ link = anchorAt(elideLayout.get(), translatedMousePos);
return link;
} else if (richText && extra.isAllocated() && extra->doc) {
translatedMousePos.rx() -= QQuickTextUtil::alignedX(layedOutTextRect.width(), availableWidth(), q->effectiveHAlign());
@@ -2981,13 +3277,13 @@ void QQuickText::invalidate()
{
Q_D(QQuickText);
d->textHasChanged = true;
- d->updateLayout();
+ QMetaObject::invokeMethod(this,[&]{q_updateLayout();});
}
bool QQuickTextPrivate::transformChanged(QQuickItem *transformedItem)
{
// If there's a lot of text, we may need QQuickText::updatePaintNode() to call
- // QQuickTextNode::addTextLayout() again to populate a different range of lines
+ // QSGInternalTextNode::addTextLayout() again to populate a different range of lines
if (flags & QQuickItem::ItemObservesViewport) {
updateType = UpdatePaintNode;
dirty(QQuickItemPrivate::Content);
@@ -2997,6 +3293,7 @@ bool QQuickTextPrivate::transformChanged(QQuickItem *transformedItem)
/*!
\qmlproperty int QtQuick::Text::renderTypeQuality
+ \since 6.0
Override the default rendering type quality for this component. This is a low-level
customization which can be ignored in most cases. It currently only has an effect
@@ -3010,13 +3307,11 @@ bool QQuickTextPrivate::transformChanged(QQuickItem *transformedItem)
The \c renderTypeQuality may be any integer over 0, or one of the following
predefined values
- \list
- \li Text.DefaultRenderTypeQuality (default) = -1
- \li Text.LowRenderTypeQuality = 26
- \li Text.NormalRenderTypeQuality = 52
- \li Text.HighRenderTypeQuality = 104
- \li Text.VeryHighRenderTypeQuality = 208
- \endlist
+ \value Text.DefaultRenderTypeQuality -1 (default)
+ \value Text.LowRenderTypeQuality 26
+ \value Text.NormalRenderTypeQuality 52
+ \value Text.HighRenderTypeQuality 104
+ \value Text.VeryHighRenderTypeQuality 208
*/
int QQuickText::renderTypeQuality() const
{
@@ -3045,16 +3340,23 @@ void QQuickText::setRenderTypeQuality(int renderTypeQuality)
Override the default rendering type for this component.
Supported render types are:
- \list
- \li Text.QtRendering
- \li Text.NativeRendering
- \endlist
- Select Text.NativeRendering if you prefer text to look native on the target platform and do
+ \value Text.QtRendering Text is rendered using a scalable distance field for each glyph.
+ \value Text.NativeRendering Text is rendered using a platform-specific technique.
+ \value Text.CurveRendering Text is rendered using a curve rasterizer running directly on the
+ graphics hardware. (Introduced in Qt 6.7.0.)
+
+ Select \c Text.NativeRendering if you prefer text to look native on the target platform and do
not require advanced features such as transformation of the text. Using such features in
combination with the NativeRendering render type will lend poor and sometimes pixelated
results.
+ Both \c Text.QtRendering and \c Text.CurveRendering are hardware-accelerated techniques.
+ \c QtRendering is the faster of the two, but uses more memory and will exhibit rendering
+ artifacts at large sizes. \c CurveRendering should be considered as an alternative in cases
+ where \c QtRendering does not give good visual results or where reducing graphics memory
+ consumption is a priority.
+
The default rendering type is determined by \l QQuickWindow::textRenderType().
*/
QQuickText::RenderType QQuickText::renderType() const
@@ -3310,7 +3612,7 @@ void QQuickText::resetBottomPadding()
*/
/*!
- \qmlproperty string QtQuick::Text::fontInfo.pixelSize
+ \qmlproperty int QtQuick::Text::fontInfo.pixelSize
\since 5.9
The pixel size of the font info that has been resolved for the current font
@@ -3347,7 +3649,7 @@ QJSValue QQuickText::fontInfo() const
in a text flow.
Note that the advance can be negative if the text flows from
- the right to the left.
+ right to left.
*/
QSizeF QQuickText::advance() const
{
diff --git a/src/quick/items/qquicktext_p.h b/src/quick/items/qquicktext_p.h
index e1d786d098..ed474b29c0 100644
--- a/src/quick/items/qquicktext_p.h
+++ b/src/quick/items/qquicktext_p.h
@@ -24,7 +24,7 @@ QT_BEGIN_NAMESPACE
class QQuickTextPrivate;
class QQuickTextLine;
-class Q_QUICK_PRIVATE_EXPORT QQuickText : public QQuickImplicitSizeItem, public QQuickTextInterface
+class Q_QUICK_EXPORT QQuickText : public QQuickImplicitSizeItem, public QQuickTextInterface
{
Q_OBJECT
Q_INTERFACES(QQuickTextInterface)
@@ -109,7 +109,8 @@ public:
Q_ENUM(WrapMode)
enum RenderType { QtRendering,
- NativeRendering
+ NativeRendering,
+ CurveRendering
};
Q_ENUM(RenderType)
@@ -300,6 +301,8 @@ protected:
private Q_SLOTS:
void q_updateLayout();
void triggerPreprocess();
+ Q_REVISION(6, 7) QVariant loadResource(int type, const QUrl &source);
+ void resourceRequestFinished();
void imageDownloadFinished();
private:
@@ -310,16 +313,16 @@ private:
Q_DECLARE_MIXED_ENUM_OPERATORS_SYMMETRIC(int, QQuickText::HAlignment, QQuickText::VAlignment)
class QTextLine;
-class QQuickTextLine : public QObject
+class Q_QUICK_EXPORT QQuickTextLine : public QObject
{
Q_OBJECT
- Q_PROPERTY(int number READ number)
- Q_PROPERTY(qreal width READ width WRITE setWidth)
- Q_PROPERTY(qreal height READ height WRITE setHeight)
- Q_PROPERTY(qreal x READ x WRITE setX)
- Q_PROPERTY(qreal y READ y WRITE setY)
- Q_PROPERTY(qreal implicitWidth READ implicitWidth REVISION(2, 15))
- Q_PROPERTY(bool isLast READ isLast REVISION(2, 15))
+ Q_PROPERTY(int number READ number FINAL)
+ Q_PROPERTY(qreal width READ width WRITE setWidth FINAL)
+ Q_PROPERTY(qreal height READ height WRITE setHeight FINAL)
+ Q_PROPERTY(qreal x READ x WRITE setX FINAL)
+ Q_PROPERTY(qreal y READ y WRITE setY FINAL)
+ Q_PROPERTY(qreal implicitWidth READ implicitWidth REVISION(2, 15) FINAL)
+ Q_PROPERTY(bool isLast READ isLast REVISION(2, 15) FINAL)
QML_ANONYMOUS
QML_ADDED_IN_VERSION(2, 0)
@@ -354,7 +357,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickText)
-QML_DECLARE_TYPE(QQuickTextLine)
-
#endif // QQUICKTEXT_P_H
diff --git a/src/quick/items/qquicktext_p_p.h b/src/quick/items/qquicktext_p_p.h
index 9870197c31..6dba7a7d75 100644
--- a/src/quick/items/qquicktext_p_p.h
+++ b/src/quick/items/qquicktext_p_p.h
@@ -23,13 +23,13 @@
#include <QtGui/qtextlayout.h>
#include <private/qquickstyledtext_p.h>
#include <private/qlazilyallocated_p.h>
+#include <private/qquicktextdocument_p.h>
QT_BEGIN_NAMESPACE
class QTextLayout;
-class QQuickTextDocumentWithImageResources;
-class Q_QUICK_PRIVATE_EXPORT QQuickTextPrivate : public QQuickImplicitSizeItemPrivate
+class Q_QUICK_EXPORT QQuickTextPrivate : public QQuickImplicitSizeItemPrivate
{
Q_DECLARE_PUBLIC(QQuickText)
public:
@@ -71,12 +71,12 @@ public:
bool explicitRightPadding : 1;
bool explicitBottomPadding : 1;
qreal lineHeight;
- QQuickTextDocumentWithImageResources *doc;
+ QTextDocument *doc;
+ QQuickTextImageHandler *imageHandler = nullptr;
QString activeLink;
QString hoveredLink;
int minimumPixelSize;
int minimumPointSize;
- int nbActiveDownloads;
int maximumLineCount;
int renderTypeQuality;
bool lineHeightValid : 1;
@@ -84,6 +84,7 @@ public:
QQuickText::FontSizeMode fontSizeMode;
QList<QQuickStyledTextImgTag*> imgTags;
QList<QQuickStyledTextImgTag*> visibleImgTags;
+ QList<QQuickPixmap *> pixmapsInProgress;
QUrl baseUrl;
};
QLazilyAllocated<ExtraData> extra;
@@ -94,8 +95,8 @@ public:
QFontInfo fontInfo;
QTextLayout layout;
- QTextLayout *elideLayout;
- QQuickTextLine *textLine;
+ QScopedPointer<QTextLayout> elideLayout;
+ QScopedPointer<QQuickTextLine> textLine;
qreal lineWidth;
@@ -162,6 +163,8 @@ public:
void ensureDoc();
void updateDocumentText();
+ qreal devicePixelRatio() const;
+
QRectF setupTextLayout(qreal * const baseline);
void setupCustomLineGeometry(QTextLine &line, qreal &height, int fullLayoutTextLength, int lineOffset = 0);
bool isLinkActivatedConnected();
diff --git a/src/quick/items/qquicktextcontrol.cpp b/src/quick/items/qquicktextcontrol.cpp
index 929ce10136..cbc46f7728 100644
--- a/src/quick/items/qquicktextcontrol.cpp
+++ b/src/quick/items/qquicktextcontrol.cpp
@@ -42,7 +42,6 @@
const int textCursorWidth = 1;
QT_BEGIN_NAMESPACE
-Q_DECLARE_LOGGING_CATEGORY(lcHoverTrace)
// could go into QTextCursor...
static QTextLine currentTextLine(const QTextCursor &cursor)
@@ -81,6 +80,7 @@ QQuickTextControlPrivate::QQuickTextControlPrivate()
cursorRectangleChanged(false),
hoveredMarker(false),
selectByTouchDrag(false),
+ imSelectionAfterPress(false),
lastSelectionStart(-1),
lastSelectionEnd(-1)
{}
@@ -583,25 +583,7 @@ void QQuickTextControl::clear()
QQuickTextControl::QQuickTextControl(QTextDocument *doc, QObject *parent)
: QInputControl(TextEdit, *new QQuickTextControlPrivate, parent)
{
- Q_D(QQuickTextControl);
- Q_ASSERT(doc);
-
- QAbstractTextDocumentLayout *layout = doc->documentLayout();
- qmlobject_connect(layout, QAbstractTextDocumentLayout, SIGNAL(update(QRectF)), this, QQuickTextControl, SIGNAL(updateRequest()));
- qmlobject_connect(layout, QAbstractTextDocumentLayout, SIGNAL(updateBlock(QTextBlock)), this, QQuickTextControl, SIGNAL(updateRequest()));
- qmlobject_connect(doc, QTextDocument, SIGNAL(contentsChanged()), this, QQuickTextControl, SIGNAL(textChanged()));
- qmlobject_connect(doc, QTextDocument, SIGNAL(contentsChanged()), this, QQuickTextControl, SLOT(_q_updateCurrentCharFormatAndSelection()));
- qmlobject_connect(doc, QTextDocument, SIGNAL(cursorPositionChanged(QTextCursor)), this, QQuickTextControl, SLOT(_q_updateCursorPosChanged(QTextCursor)));
- connect(doc, &QTextDocument::contentsChange, this, &QQuickTextControl::contentsChange);
-
- layout->setProperty("cursorWidth", textCursorWidth);
-
- d->doc = doc;
- d->cursor = QTextCursor(doc);
- d->lastCharFormat = d->cursor.charFormat();
- doc->setPageSize(QSizeF(0, 0));
- doc->setModified(false);
- doc->setUndoRedoEnabled(true);
+ setDocument(doc);
}
QQuickTextControl::~QQuickTextControl()
@@ -614,6 +596,34 @@ QTextDocument *QQuickTextControl::document() const
return d->doc;
}
+void QQuickTextControl::setDocument(QTextDocument *doc)
+{
+ Q_D(QQuickTextControl);
+ if (!doc || d->doc == doc)
+ return;
+
+ d->doc = doc;
+ d->cursor = QTextCursor(doc);
+ d->lastCharFormat = d->cursor.charFormat();
+ doc->setPageSize(QSizeF(0, 0));
+ doc->setModified(false);
+ doc->setUndoRedoEnabled(true);
+
+ QAbstractTextDocumentLayout *layout = doc->documentLayout();
+ connect(layout, &QAbstractTextDocumentLayout::update, this, &QQuickTextControl::updateRequest);
+ connect(layout, &QAbstractTextDocumentLayout::updateBlock, this, &QQuickTextControl::updateRequest);
+ connect(doc, &QTextDocument::contentsChanged, doc, [d, this]() {
+ d->_q_updateCurrentCharFormatAndSelection();
+ emit textChanged();
+ });
+ connect(doc, &QTextDocument::cursorPositionChanged, doc, [d](const QTextCursor &cursor) {
+ d->_q_updateCursorPosChanged(cursor);
+ });
+ connect(doc, &QTextDocument::contentsChange, this, &QQuickTextControl::contentsChange);
+ if (auto *qtdlayout = qobject_cast<QTextDocumentLayout *>(layout))
+ qtdlayout->setCursorWidth(textCursorWidth);
+}
+
void QQuickTextControl::updateCursorRectangle(bool force)
{
Q_D(QQuickTextControl);
@@ -992,6 +1002,7 @@ void QQuickTextControlPrivate::mousePressEvent(QMouseEvent *e, const QPointF &po
mousePressed = (interactionFlags & Qt::TextSelectableByMouse) && (e->button() & Qt::LeftButton);
mousePressPos = pos.toPoint();
+ imSelectionAfterPress = false;
if (sendMouseEventToInputContext(e, pos))
return;
@@ -1177,7 +1188,7 @@ void QQuickTextControlPrivate::mouseReleaseEvent(QMouseEvent *e, const QPointF &
q->insertFromMimeData(md);
#endif
}
- if (!isMouse && !selectByTouchDrag && interactionFlags.testFlag(Qt::TextEditable))
+ if (!isMouse && !selectByTouchDrag && !imSelectionAfterPress && interactionFlags.testFlag(Qt::TextEditable))
setCursorPosition(pos);
repaintOldAndNewSelection(oldSelection);
@@ -1322,6 +1333,8 @@ void QQuickTextControlPrivate::inputMethodEvent(QInputMethodEvent *e)
for (int i = 0; i < e->attributes().size(); ++i) {
const QInputMethodEvent::Attribute &a = e->attributes().at(i);
if (a.type == QInputMethodEvent::Selection) {
+ if (mousePressed)
+ imSelectionAfterPress = true;
QTextCursor oldCursor = cursor;
int blockStart = a.start + cursor.block().position();
cursor.setPosition(blockStart, QTextCursor::MoveAnchor);
diff --git a/src/quick/items/qquicktextcontrol_p.h b/src/quick/items/qquicktextcontrol_p.h
index 4994c4a51e..293409ca43 100644
--- a/src/quick/items/qquicktextcontrol_p.h
+++ b/src/quick/items/qquicktextcontrol_p.h
@@ -46,6 +46,7 @@ public:
virtual ~QQuickTextControl();
QTextDocument *document() const;
+ void setDocument(QTextDocument *doc);
void setTextCursor(const QTextCursor &cursor);
QTextCursor textCursor() const;
diff --git a/src/quick/items/qquicktextcontrol_p_p.h b/src/quick/items/qquicktextcontrol_p_p.h
index 5717cf6377..f04d298e4a 100644
--- a/src/quick/items/qquicktextcontrol_p_p.h
+++ b/src/quick/items/qquicktextcontrol_p_p.h
@@ -131,6 +131,7 @@ public:
bool cursorRectangleChanged : 1;
bool hoveredMarker: 1;
bool selectByTouchDrag: 1;
+ bool imSelectionAfterPress: 1;
int lastSelectionStart;
int lastSelectionEnd;
diff --git a/src/quick/items/qquicktextdocument.cpp b/src/quick/items/qquicktextdocument.cpp
index ff28abe600..a1fb7adcea 100644
--- a/src/quick/items/qquicktextdocument.cpp
+++ b/src/quick/items/qquicktextdocument.cpp
@@ -5,15 +5,42 @@
#include "qquicktextdocument_p.h"
#include "qquicktextedit_p.h"
-#include "qquicktextedit_p_p.h"
-#include "qquicktext_p_p.h"
-#include <QtQml/qqmlinfo.h>
#include <QtQml/qqmlcontext.h>
-#include <QtQuick/private/qquickpixmapcache_p.h>
+#include <QtQml/qqmlfile.h>
+#include <QtQml/qqmlinfo.h>
+#include <QtQuick/private/qquickpixmap_p.h>
+
+#include <QtCore/qfile.h>
+#include <QtCore/qpointer.h>
QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcTextDoc, "qt.quick.textdocument")
+
+using namespace Qt::StringLiterals;
+
+/*!
+ \qmltype TextDocument
+ \instantiates QQuickTextDocument
+ \inqmlmodule QtQuick
+ \brief A wrapper around TextEdit's backing QTextDocument.
+ \preliminary
+
+ To load text into the document, set the \l source property. If the user then
+ modifies the text and wants to save the same document, call \l save() to save
+ it to the same source again (only if \l {QUrl::isLocalFile()}{it's a local file}).
+ Or call \l saveAs() to save it to a different file.
+
+ This class cannot be instantiated in QML, but is available from \l TextEdit::textDocument.
+
+ \note All loading and saving is done synchronously for now.
+ This may block the UI if the \l source is a slow network drive.
+ This may be improved in future versions of Qt.
+
+ \note This API is considered tech preview and may change in future versions of Qt.
+*/
+
/*!
\class QQuickTextDocument
\since 5.1
@@ -21,23 +48,12 @@ QT_BEGIN_NAMESPACE
\inmodule QtQuick
This class provides access to the QTextDocument of QQuickTextEdit elements.
- This is provided to allow usage of the \l{Rich Text Processing} functionalities of Qt.
- You are not allowed to modify the document, but it can be used to output content, for example with \l{QTextDocumentWriter}),
- or provide additional formatting, for example with \l{QSyntaxHighlighter}.
-
- The class has to be used from C++ directly, using the property of the \l TextEdit.
-
- Warning: The QTextDocument provided is used internally by \l {Qt Quick} elements to provide text manipulation primitives.
- You are not allowed to perform any modification of the internal state of the QTextDocument. If you do, the element
- in question may stop functioning or crash.
+ This is provided to allow usage of the \l{Rich Text Processing} functionalities of Qt,
+ including document modifications. It can also be used to output content,
+ for example with \l{QTextDocumentWriter}, or provide additional formatting,
+ for example with \l{QSyntaxHighlighter}.
*/
-class QQuickTextDocumentPrivate : public QObjectPrivate
-{
-public:
- QPointer<QTextDocument> document;
-};
-
/*!
Constructs a QQuickTextDocument object with
\a parent as the parent object.
@@ -47,74 +63,557 @@ QQuickTextDocument::QQuickTextDocument(QQuickItem *parent)
{
Q_D(QQuickTextDocument);
Q_ASSERT(parent);
- Q_ASSERT(qobject_cast<QQuickTextEdit*>(parent));
- d->document = QPointer<QTextDocument>(qobject_cast<QQuickTextEdit*>(parent)->d_func()->document);
+ d->editor = qobject_cast<QQuickTextEdit *>(parent);
+ Q_ASSERT(d->editor);
+ connect(textDocument(), &QTextDocument::modificationChanged,
+ this, &QQuickTextDocument::modifiedChanged);
}
/*!
- Returns a pointer to the QTextDocument object.
+ \property QQuickTextDocument::status
+ \brief the status of document loading or saving
+ \since 6.7
+ \preliminary
+
+ This property holds the status of document loading or saving. It can be one of:
+
+ \value Null No file has been loaded
+ \value Loading Reading from \l source has begun
+ \value Loaded Reading has successfully finished
+ \value Saving File writing has begun after save() or saveAs()
+ \value Saved Writing has successfully finished
+ \value ReadError An error occurred while reading from \l source
+ \value WriteError An error occurred in save() or saveAs()
+ \value NonLocalFileError saveAs() was called with a URL pointing
+ to a remote resource rather than a local file
+
+ \sa errorString, source, save(), saveAs()
+*/
+
+/*!
+ \qmlproperty enumeration QtQuick::TextDocument::status
+ \readonly
+ \since 6.7
+ \preliminary
+
+ This property holds the status of document loading or saving. It can be one of:
+
+ \value TextDocument.Null No file has been loaded
+ \value TextDocument.Loading Reading from \l source has begun
+ \value TextDocument.Loaded Reading has successfully finished
+ \value TextDocument.Saving File writing has begun after save() or saveAs()
+ \value TextDocument.Saved Writing has successfully finished
+ \value TextDocument.ReadError An error occurred while reading from \l source
+ \value TextDocument.WriteError An error occurred in save() or saveAs()
+ \value TextDocument.NonLocalFileError saveAs() was called with a URL pointing
+ to a remote resource rather than a local file
+
+ Use this status to provide an update or respond to the status change in some way.
+ For example, you could:
+
+ \list
+ \li Trigger a state change:
+ \qml
+ State {
+ name: 'loaded'
+ when: textEdit.textDocument.status == textEdit.textDocument.Loaded
+ }
+ \endqml
+
+ \li Implement an \c onStatusChanged signal handler:
+ \qml
+ TextEdit {
+ onStatusChanged: {
+ if (textDocument.status === textDocument.Loaded)
+ console.log('Loaded')
+ }
+ }
+ \endqml
+
+ \li Bind to the status value:
+
+ \snippet qml/textEditStatusSwitch.qml 0
+
+ \endlist
+
+ \sa errorString, source, save(), saveAs()
+*/
+QQuickTextDocument::Status QQuickTextDocument::status() const
+{
+ Q_D(const QQuickTextDocument);
+ return d->status;
+}
+
+/*!
+ \property QQuickTextDocument::errorString
+ \brief a human-readable string describing the error that occurred during loading or saving, if any
+ \since 6.7
+ \preliminary
+
+ By default this string is empty.
+
+ \sa status, source, save(), saveAs()
+*/
+
+/*!
+ \qmlproperty string QtQuick::TextDocument::errorString
+ \readonly
+ \since 6.7
+ \preliminary
+
+ This property holds a human-readable string describing the error that
+ occurred during loading or saving, if any; otherwise, an empty string.
+
+ \sa status, source, save(), saveAs()
+*/
+QString QQuickTextDocument::errorString() const
+{
+ Q_D(const QQuickTextDocument);
+ return d->errorString;
+}
+
+void QQuickTextDocumentPrivate::setStatus(QQuickTextDocument::Status s, const QString &err)
+{
+ Q_Q(QQuickTextDocument);
+ if (status == s)
+ return;
+
+ status = s;
+ emit q->statusChanged();
+
+ if (errorString == err)
+ return;
+ errorString = err;
+ emit q->errorStringChanged();
+ if (!err.isEmpty())
+ qmlWarning(q) << err;
+}
+
+/*!
+ \property QQuickTextDocument::source
+ \brief the URL from which to load document contents
+ \since 6.7
+ \preliminary
+
+ QQuickTextDocument can handle any text format supported by Qt, loaded from
+ any URL scheme supported by Qt.
+
+ The \c source property cannot be changed while the document's \l modified
+ state is \c true. If the user has modified the document contents, you
+ should prompt the user whether to \l save(), or else discard changes by
+ setting \l modified to \c false before setting the \c source property to a
+ different URL.
+
+ \sa QTextDocumentWriter::supportedDocumentFormats()
*/
-QTextDocument* QQuickTextDocument::textDocument() const
+
+/*!
+ \qmlproperty url QtQuick::TextDocument::source
+ \since 6.7
+ \preliminary
+
+ QQuickTextDocument can handle any text format supported by Qt, loaded from
+ any URL scheme supported by Qt.
+
+ The URL may be absolute, or relative to the URL of the component.
+
+ The \c source property cannot be changed while the document's \l modified
+ state is \c true. If the user has modified the document contents, you
+ should prompt the user whether to \l save(), or else discard changes by
+ setting \c {modified = false} before setting the \l source property to a
+ different URL.
+
+ \sa QTextDocumentWriter::supportedDocumentFormats()
+*/
+QUrl QQuickTextDocument::source() const
{
Q_D(const QQuickTextDocument);
- return d->document.data();
+ return d->url;
+}
+
+void QQuickTextDocument::setSource(const QUrl &url)
+{
+ Q_D(QQuickTextDocument);
+
+ if (url == d->url)
+ return;
+
+ if (isModified()) {
+ qmlWarning(this) << "Existing document modified: you should save(),"
+ "or call TextEdit.clear() before setting a different source";
+ return;
+ }
+
+ d->url = url;
+ emit sourceChanged();
+ d->load();
+}
+
+/*!
+ \property QQuickTextDocument::modified
+ \brief whether the document has been modified by the user
+ \since 6.7
+ \preliminary
+
+ This property holds whether the document has been modified by the user
+ since the last time it was loaded or saved. By default, this property is
+ \c false.
+
+ As with \l QTextDocument::modified, you can set the modified property:
+ for example, set it to \c false to allow setting the \l source property
+ to a different URL (thus discarding the user's changes).
+
+ \sa QTextDocument::modified
+*/
+
+/*!
+ \qmlproperty bool QtQuick::TextDocument::modified
+ \since 6.7
+ \preliminary
+
+ This property holds whether the document has been modified by the user
+ since the last time it was loaded or saved. By default, this property is
+ \c false.
+
+ As with \l QTextDocument::modified, you can set the modified property:
+ for example, set it to \c false to allow setting the \l source property
+ to a different URL (thus discarding the user's changes).
+
+ \sa QTextDocument::modified
+*/
+bool QQuickTextDocument::isModified() const
+{
+ const auto *doc = textDocument();
+ return doc && doc->isModified();
+}
+
+void QQuickTextDocument::setModified(bool modified)
+{
+ if (auto *doc = textDocument())
+ doc->setModified(modified);
+}
+
+void QQuickTextDocumentPrivate::load()
+{
+ auto *doc = editor->document();
+ if (!doc) {
+ setStatus(QQuickTextDocument::Status::ReadError,
+ QQuickTextDocument::tr("Null document object: cannot load"));
+ return;
+ }
+ const QQmlContext *context = qmlContext(editor);
+ const QUrl &resolvedUrl = context ? context->resolvedUrl(url) : url;
+ const QString filePath = QQmlFile::urlToLocalFileOrQrc(resolvedUrl);
+ QFile file(filePath);
+ if (file.exists()) {
+#if QT_CONFIG(mimetype)
+ QMimeType mimeType = QMimeDatabase().mimeTypeForFile(filePath);
+ const bool isHtml = mimeType.inherits("text/html"_L1);
+ const bool isMarkdown = mimeType.inherits("text/markdown"_L1);
+#else
+ const bool isHtml = filePath.endsWith(".html"_L1, Qt::CaseInsensitive) ||
+ filePath.endsWith(".htm"_L1, Qt::CaseInsensitive);
+ const bool isMarkdown = filePath.endsWith(".md"_L1, Qt::CaseInsensitive) ||
+ filePath.endsWith(".markdown"_L1, Qt::CaseInsensitive);
+#endif
+ if (isHtml)
+ detectedFormat = Qt::RichText;
+ else if (isMarkdown)
+ detectedFormat = Qt::MarkdownText;
+ else
+ detectedFormat = Qt::PlainText;
+ if (file.open(QFile::ReadOnly | QFile::Text)) {
+ setStatus(QQuickTextDocument::Status::Loading, {});
+ QByteArray data = file.readAll();
+ doc->setBaseUrl(resolvedUrl.adjusted(QUrl::RemoveFilename));
+#if QT_CONFIG(textmarkdownreader) || QT_CONFIG(texthtmlparser)
+ const bool plainText = editor->textFormat() == QQuickTextEdit::PlainText;
+#endif
+#if QT_CONFIG(textmarkdownreader)
+ if (!plainText && isMarkdown) {
+ doc->setMarkdown(QString::fromUtf8(data));
+ } else
+#endif
+#if QT_CONFIG(texthtmlparser)
+ if (!plainText && isHtml) {
+ // If a user loads an HTML file, remember the encoding.
+ // If the user then calls save() later, the same encoding will be used.
+ encoding = QStringConverter::encodingForHtml(data);
+ if (encoding) {
+ QStringDecoder decoder(*encoding);
+ doc->setHtml(decoder(data));
+ } else {
+ // fall back to utf8
+ doc->setHtml(QString::fromUtf8(data));
+ }
+ } else
+#endif
+ {
+ doc->setPlainText(QString::fromUtf8(data));
+ }
+ setStatus(QQuickTextDocument::Status::Loaded, {});
+ qCDebug(lcTextDoc) << editor << "loaded" << filePath
+ << "as" << editor->textFormat() << "detected" << detectedFormat
+#if QT_CONFIG(mimetype)
+ << "(file type" << mimeType << ')'
+#endif
+ ;
+ doc->setModified(false);
+ return;
+ }
+ setStatus(QQuickTextDocument::Status::ReadError,
+ QQuickTextDocument::tr("Failed to read: %1").arg(file.errorString()));
+ } else {
+ setStatus(QQuickTextDocument::Status::ReadError,
+ QQuickTextDocument::tr("%1 does not exist").arg(filePath));
+ }
}
-QQuickTextDocumentWithImageResources::QQuickTextDocumentWithImageResources(QQuickItem *parent)
-: QTextDocument(parent), outstanding(0)
+void QQuickTextDocumentPrivate::writeTo(const QUrl &fileUrl)
{
- setUndoRedoEnabled(false);
- documentLayout()->registerHandler(QTextFormat::ImageObject, this);
- connect(this, &QTextDocument::baseUrlChanged, [this]() {
- clearResources();
- markContentsDirty(0, characterCount());
- });
+ auto *doc = editor->document();
+ if (!doc)
+ return;
+
+ const QString filePath = fileUrl.toLocalFile();
+ const bool sameUrl = fileUrl == url;
+ if (!sameUrl) {
+#if QT_CONFIG(mimetype)
+ const auto type = QMimeDatabase().mimeTypeForUrl(fileUrl);
+ if (type.inherits("text/html"_L1))
+ detectedFormat = Qt::RichText;
+ else if (type.inherits("text/markdown"_L1))
+ detectedFormat = Qt::MarkdownText;
+ else
+ detectedFormat = Qt::PlainText;
+#else
+ if (filePath.endsWith(".html"_L1, Qt::CaseInsensitive) ||
+ filePath.endsWith(".htm"_L1, Qt::CaseInsensitive))
+ detectedFormat = Qt::RichText;
+ else if (filePath.endsWith(".md"_L1, Qt::CaseInsensitive) ||
+ filePath.endsWith(".markdown"_L1, Qt::CaseInsensitive))
+ detectedFormat = Qt::MarkdownText;
+ else
+ detectedFormat = Qt::PlainText;
+#endif
+ }
+ QFile file(filePath);
+ if (!file.open(QFile::WriteOnly | QFile::Truncate |
+ (detectedFormat == Qt::RichText ? QFile::NotOpen : QFile::Text))) {
+ setStatus(QQuickTextDocument::Status::WriteError,
+ QQuickTextDocument::tr("Cannot save: %1").arg(file.errorString()));
+ return;
+ }
+ setStatus(QQuickTextDocument::Status::Saving, {});
+ QByteArray raw;
+
+ switch (detectedFormat) {
+#if QT_CONFIG(textmarkdownwriter)
+ case Qt::MarkdownText:
+ raw = doc->toMarkdown().toUtf8();
+ break;
+#endif
+#if QT_CONFIG(texthtmlparser)
+ case Qt::RichText:
+ if (sameUrl && encoding) {
+ QStringEncoder enc(*encoding);
+ raw = enc.encode(doc->toHtml());
+ } else {
+ // default to UTF-8 unless the user is saving the same file as previously loaded
+ raw = doc->toHtml().toUtf8();
+ }
+ break;
+#endif
+ default:
+ raw = doc->toPlainText().toUtf8();
+ break;
+ }
+
+ file.write(raw);
+ file.close();
+ setStatus(QQuickTextDocument::Status::Saved, {});
+ doc->setModified(false);
}
-QQuickTextDocumentWithImageResources::~QQuickTextDocumentWithImageResources()
+QTextDocument *QQuickTextDocumentPrivate::document() const
{
- if (!m_resources.isEmpty())
- qDeleteAll(m_resources);
+ return editor->document();
}
-QVariant QQuickTextDocumentWithImageResources::loadResource(int type, const QUrl &name)
+void QQuickTextDocumentPrivate::setDocument(QTextDocument *doc)
{
- QVariant resource = QTextDocument::loadResource(type, name);
- if (resource.isNull() && type == QTextDocument::ImageResource) {
- QQmlContext *context = qmlContext(parent());
- QUrl url = baseUrl().resolved(name);
- QQuickPixmap *p = loadPixmap(context, url);
- resource = p->image();
+ Q_Q(QQuickTextDocument);
+ QTextDocument *oldDoc = editor->document();
+ if (doc == oldDoc)
+ return;
+
+ if (oldDoc)
+ oldDoc->disconnect(q);
+ if (doc) {
+ q->connect(doc, &QTextDocument::modificationChanged,
+ q, &QQuickTextDocument::modifiedChanged);
}
+ editor->setDocument(doc);
+ emit q->textDocumentChanged();
+}
+
+/*!
+ Returns a pointer to the QTextDocument object.
+*/
+QTextDocument *QQuickTextDocument::textDocument() const
+{
+ Q_D(const QQuickTextDocument);
+ return d->document();
+}
+
+/*!
+ \brief Sets the given \a document.
+ \since 6.7
+
+ The caller retains ownership of the document.
+*/
+void QQuickTextDocument::setTextDocument(QTextDocument *document)
+{
+ d_func()->setDocument(document);
+}
+
+/*!
+ \fn void QQuickTextDocument::textDocumentChanged()
+ \since 6.7
+
+ This signal is emitted when the underlying QTextDocument is
+ replaced with a different instance.
+
+ \sa setTextDocument()
+*/
+
+/*!
+ \preliminary
+ \fn void QQuickTextDocument::sourceChanged()
+*/
+
+/*!
+ \preliminary
+ \fn void QQuickTextDocument::modifiedChanged()
+*/
+
+/*!
+ \preliminary
+ \fn void QQuickTextDocument::statusChanged()
+*/
+
+/*!
+ \preliminary
+ \fn void QQuickTextDocument::errorStringChanged()
+*/
+
+/*!
+ \fn void QQuickTextDocument::save()
+ \since 6.7
+ \preliminary
+
+ Saves the contents to the same file and format specified by \l source.
+
+ \note You can save only to a \l {QUrl::isLocalFile()}{file on a mounted filesystem}.
+
+ \sa source, saveAs()
+*/
+
+/*!
+ \qmlmethod void QtQuick::TextDocument::save()
+ \brief Saves the contents to the same file and format specified by \l source.
+ \since 6.7
+ \preliminary
+
+ \note You can save only to a \l {QUrl::isLocalFile()}{file on a mounted filesystem}.
- return resource;
+ \sa source, saveAs()
+*/
+void QQuickTextDocument::save()
+{
+ Q_D(QQuickTextDocument);
+ d->writeTo(d->url);
}
-void QQuickTextDocumentWithImageResources::requestFinished()
+/*!
+ \fn void QQuickTextDocument::saveAs(const QUrl &url)
+ \brief Saves the contents to the file and format specified by \a url.
+ \since 6.7
+ \preliminary
+
+ The file extension in \a url specifies the file format
+ (as determined by QMimeDatabase::mimeTypeForUrl()).
+
+ \note You can save only to a \l {QUrl::isLocalFile()}{file on a mounted filesystem}.
+
+ \sa source, save()
+*/
+
+/*!
+ \qmlmethod void QtQuick::TextDocument::saveAs(url url)
+ \brief Saves the contents to the file and format specified by \a url.
+ \since 6.7
+ \preliminary
+
+ The file extension in \a url specifies the file format
+ (as determined by QMimeDatabase::mimeTypeForUrl()).
+
+ \note You can save only to a \l {QUrl::isLocalFile()}{file on a mounted filesystem}.
+
+ \sa source, save()
+*/
+void QQuickTextDocument::saveAs(const QUrl &url)
{
- outstanding--;
- if (outstanding == 0) {
- markContentsDirty(0, characterCount());
- emit imagesLoaded();
+ Q_D(QQuickTextDocument);
+ if (!url.isLocalFile()) {
+ d->setStatus(QQuickTextDocument::Status::NonLocalFileError,
+ QQuickTextDocument::tr("Can only save to local files"));
+ return;
}
+ d->writeTo(url);
+
+ if (url == d->url)
+ return;
+
+ d->url = url;
+ emit sourceChanged();
}
-QSizeF QQuickTextDocumentWithImageResources::intrinsicSize(
- QTextDocument *, int, const QTextFormat &format)
+QQuickTextImageHandler::QQuickTextImageHandler(QObject *parent)
+ : QObject(parent)
+{
+}
+
+QSizeF QQuickTextImageHandler::intrinsicSize(
+ QTextDocument *doc, int, const QTextFormat &format)
{
if (format.isImageFormat()) {
QTextImageFormat imageFormat = format.toImageFormat();
-
- const int width = qRound(imageFormat.width());
+ int width = qRound(imageFormat.width());
const bool hasWidth = imageFormat.hasProperty(QTextFormat::ImageWidth) && width > 0;
const int height = qRound(imageFormat.height());
const bool hasHeight = imageFormat.hasProperty(QTextFormat::ImageHeight) && height > 0;
+ const auto maxWidth = imageFormat.maximumWidth();
+ const bool hasMaxWidth = imageFormat.hasProperty(QTextFormat::ImageMaxWidth) && maxWidth.type() != QTextLength::VariableLength;
+
+ int effectiveMaxWidth = INT_MAX;
+ if (hasMaxWidth) {
+ if (maxWidth.type() == QTextLength::PercentageLength) {
+ effectiveMaxWidth = (doc->pageSize().width() - 2 * doc->documentMargin()) * maxWidth.value(100) / 100;
+ } else {
+ effectiveMaxWidth = maxWidth.rawValue();
+ }
+
+ width = qMin(effectiveMaxWidth, width);
+ }
QSizeF size(width, height);
if (!hasWidth || !hasHeight) {
- QVariant res = resource(QTextDocument::ImageResource, QUrl(imageFormat.name()));
+ QVariant res = doc->resource(QTextDocument::ImageResource, QUrl(imageFormat.name()));
QImage image = res.value<QImage>();
if (image.isNull()) {
+ // autotests expect us to reserve a 16x16 space for a "broken image" icon,
+ // even though we don't actually display one
if (!hasWidth)
size.setWidth(16);
if (!hasHeight)
@@ -122,12 +621,17 @@ QSizeF QQuickTextDocumentWithImageResources::intrinsicSize(
return size;
}
QSize imgSize = image.size();
+ if (imgSize.width() > effectiveMaxWidth) {
+ // image is bigger than effectiveMaxWidth, scale it down
+ imgSize.setHeight(effectiveMaxWidth * imgSize.height() / (qreal) imgSize.width());
+ imgSize.setWidth(effectiveMaxWidth);
+ }
if (!hasWidth) {
if (!hasHeight)
size.setWidth(imgSize.width());
else
- size.setWidth(qRound(height * (imgSize.width() / (qreal) imgSize.height())));
+ size.setWidth(qMin(effectiveMaxWidth, qRound(height * (imgSize.width() / (qreal) imgSize.height()))));
}
if (!hasHeight) {
if (!hasWidth)
@@ -141,54 +645,6 @@ QSizeF QQuickTextDocumentWithImageResources::intrinsicSize(
return QSizeF();
}
-void QQuickTextDocumentWithImageResources::drawObject(
- QPainter *, const QRectF &, QTextDocument *, int, const QTextFormat &)
-{
-}
-
-QImage QQuickTextDocumentWithImageResources::image(const QTextImageFormat &format) const
-{
- QVariant res = resource(QTextDocument::ImageResource, QUrl(format.name()));
- return res.value<QImage>();
-}
-
-QQuickPixmap *QQuickTextDocumentWithImageResources::loadPixmap(
- QQmlContext *context, const QUrl &url)
-{
-
- QHash<QUrl, QQuickPixmap *>::Iterator iter = m_resources.find(url);
-
- if (iter == m_resources.end()) {
- QQuickPixmap *p = new QQuickPixmap(context->engine(), url);
- iter = m_resources.insert(url, p);
-
- if (p->isLoading()) {
- p->connectFinished(this, SLOT(requestFinished()));
- outstanding++;
- }
- }
-
- QQuickPixmap *p = *iter;
- if (p->isError()) {
- if (!errors.contains(url)) {
- errors.insert(url);
- qmlWarning(parent()) << p->error();
- }
- }
- return p;
-}
-
-void QQuickTextDocumentWithImageResources::clearResources()
-{
- for (QQuickPixmap *pixmap : std::as_const(m_resources))
- pixmap->clear(this);
- qDeleteAll(m_resources);
- m_resources.clear();
- outstanding = 0;
-}
-
-QSet<QUrl> QQuickTextDocumentWithImageResources::errors;
-
QT_END_NAMESPACE
#include "moc_qquicktextdocument.cpp"
diff --git a/src/quick/items/qquicktextdocument.h b/src/quick/items/qquicktextdocument.h
index b73a76ec39..bccda205bd 100644
--- a/src/quick/items/qquicktextdocument.h
+++ b/src/quick/items/qquicktextdocument.h
@@ -13,12 +13,51 @@ class QQuickTextDocumentPrivate;
class Q_QUICK_EXPORT QQuickTextDocument : public QObject
{
Q_OBJECT
- QML_ANONYMOUS
+ Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged REVISION(6, 7))
+ Q_PROPERTY(bool modified READ isModified WRITE setModified NOTIFY modifiedChanged REVISION(6, 7))
+ Q_PROPERTY(Status status READ status NOTIFY statusChanged REVISION(6, 7))
+ Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged REVISION(6, 7))
+
+ QML_NAMED_ELEMENT(TextDocument)
+ QML_UNCREATABLE("TextDocument is only available as a property of TextEdit or TextArea.")
QML_ADDED_IN_VERSION(2, 0)
public:
+ enum class Status : quint8 {
+ Null = 0,
+ Loading,
+ Loaded,
+ Saving,
+ Saved,
+ ReadError,
+ WriteError,
+ NonLocalFileError,
+ };
+ Q_ENUM(Status)
+
QQuickTextDocument(QQuickItem *parent);
+
+ QUrl source() const;
+ void setSource(const QUrl &url);
+
+ bool isModified() const;
+ void setModified(bool modified);
+
QTextDocument *textDocument() const;
+ void setTextDocument(QTextDocument *document);
+
+ Q_REVISION(6, 7) Q_INVOKABLE void save();
+ Q_REVISION(6, 7) Q_INVOKABLE void saveAs(const QUrl &url);
+
+ Status status() const;
+ QString errorString() const;
+
+Q_SIGNALS:
+ Q_REVISION(6,7) void textDocumentChanged();
+ Q_REVISION(6, 7) void sourceChanged();
+ Q_REVISION(6, 7) void modifiedChanged();
+ Q_REVISION(6, 7) void statusChanged();
+ Q_REVISION(6, 7) void errorStringChanged();
private:
Q_DISABLE_COPY(QQuickTextDocument)
@@ -27,6 +66,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickTextDocument)
-
#endif
diff --git a/src/quick/items/qquicktextdocument_p.h b/src/quick/items/qquicktextdocument_p.h
index d7404aee3e..3e5236a55c 100644
--- a/src/quick/items/qquicktextdocument_p.h
+++ b/src/quick/items/qquicktextdocument_p.h
@@ -15,56 +15,76 @@
// We mean it.
//
-#include <QtCore/qhash.h>
-#include <QtCore/qvariant.h>
-#include <QtGui/qimage.h>
-#include <QtGui/qtextdocument.h>
+#include "qquicktextdocument.h"
+
#include <QtGui/qabstracttextdocumentlayout.h>
-#include <QtGui/qtextlayout.h>
-#include <QtCore/private/qglobal_p.h>
+#include <QtGui/qtextdocument.h>
+#include <QtGui/qtextdocumentfragment.h>
+#include <QtGui/qtextformat.h>
+#include <QtCore/qrect.h>
+#include <QtCore/private/qobject_p_p.h>
+
+#if QT_CONFIG(mimetype)
+#include <QtCore/qmimedatabase.h>
+#endif
QT_BEGIN_NAMESPACE
-class QQuickItem;
class QQuickPixmap;
-class QQmlContext;
-
-class Q_AUTOTEST_EXPORT QQuickTextDocumentWithImageResources : public QTextDocument, public QTextObjectInterface
+class QQuickTextEdit;
+
+/*! \internal
+ QTextImageHandler would attempt to resolve relative paths, and load the
+ image itself if the document returns an invalid image from loadResource().
+ We replace it with this version instead, because Qt Quick's text resources
+ are resolved against the Text item's context, and because we override
+ intrinsicSize(). drawObject() is empty because we don't need to use this
+ handler to paint images: they get put into scene graph nodes instead.
+*/
+class QQuickTextImageHandler : public QObject, public QTextObjectInterface
{
Q_OBJECT
Q_INTERFACES(QTextObjectInterface)
public:
- QQuickTextDocumentWithImageResources(QQuickItem *parent);
- virtual ~QQuickTextDocumentWithImageResources();
-
- int resourcesLoading() const { return outstanding; }
-
+ QQuickTextImageHandler(QObject *parent = nullptr);
+ ~QQuickTextImageHandler() override = default;
QSizeF intrinsicSize(QTextDocument *doc, int posInDocument, const QTextFormat &format) override;
- void drawObject(QPainter *p, const QRectF &rect, QTextDocument *doc, int posInDocument, const QTextFormat &format) override;
-
- QImage image(const QTextImageFormat &format) const;
-
-public Q_SLOTS:
- void clearResources();
-
-Q_SIGNALS:
- void imagesLoaded();
-
-protected:
- QVariant loadResource(int type, const QUrl &name) override;
-
- QQuickPixmap *loadPixmap(QQmlContext *context, const QUrl &name);
-
-private Q_SLOTS:
- void requestFinished();
+ void drawObject(QPainter *, const QRectF &, QTextDocument *, int, const QTextFormat &) override { }
+};
-private:
- QHash<QUrl, QQuickPixmap *> m_resources;
+class QQuickTextDocumentPrivate : public QObjectPrivate
+{
+ Q_DECLARE_PUBLIC(QQuickTextDocument)
+public:
+ static QQuickTextDocumentPrivate *get(QQuickTextDocument *doc) { return doc->d_func(); }
+ static const QQuickTextDocumentPrivate *get(const QQuickTextDocument *doc) { return doc->d_func(); }
+
+ void load();
+ void writeTo(const QUrl &fileUrl);
+ QTextDocument *document() const;
+ void setDocument(QTextDocument *doc);
+ void setStatus(QQuickTextDocument::Status s, const QString &err);
+
+ // so far the QQuickItem given to the QQuickTextDocument ctor is always a QQuickTextEdit
+ QQuickTextEdit *editor = nullptr;
+ QUrl url;
+ QString errorString;
+ Qt::TextFormat detectedFormat = Qt::AutoText; // url's extension, independent of TextEdit.textFormat
+ std::optional<QStringConverter::Encoding> encoding; // only relevant for HTML (Qt::RichText)
+ QQuickTextDocument::Status status = QQuickTextDocument::Status::Null;
+};
- int outstanding;
- static QSet<QUrl> errors;
+namespace QtPrivate {
+class ProtectedLayoutAccessor: public QAbstractTextDocumentLayout
+{
+public:
+ inline QTextCharFormat formatAccessor(int pos)
+ {
+ return format(pos);
+ }
};
+} // namespace QtPrivate
QT_END_NAMESPACE
-#endif // QQUICKDOCUMENT_P_H
+#endif // QQUICKTEXTDOCUMENT_P_H
diff --git a/src/quick/items/qquicktextedit.cpp b/src/quick/items/qquicktextedit.cpp
index 191a33167f..03e5c14ba1 100644
--- a/src/quick/items/qquicktextedit.cpp
+++ b/src/quick/items/qquicktextedit.cpp
@@ -5,9 +5,8 @@
#include "qquicktextedit_p_p.h"
#include "qquicktextcontrol_p.h"
#include "qquicktextdocument_p.h"
-#include "qquickevents_p_p.h"
#include "qquickwindow.h"
-#include "qquicktextnode_p.h"
+#include "qsginternaltextnode_p.h"
#include "qquicktextnodeengine_p.h"
#include <QtCore/qmath.h>
@@ -23,6 +22,11 @@
#include <private/qqmlproperty_p.h>
#include <private/qtextengine_p.h>
#include <private/qsgadaptationlayer_p.h>
+#include <QtQuick/private/qquickpixmapcache_p.h>
+
+#if QT_CONFIG(accessibility)
+#include <private/qquickaccessibleattached_p.h>
+#endif
#include "qquicktextdocument.h"
@@ -30,8 +34,9 @@
QT_BEGIN_NAMESPACE
-Q_DECLARE_LOGGING_CATEGORY(lcVP)
-Q_LOGGING_CATEGORY(lcTextEdit, "qt.quick.textedit")
+Q_STATIC_LOGGING_CATEGORY(lcTextEdit, "qt.quick.textedit")
+
+using namespace Qt::StringLiterals;
/*!
\qmltype TextEdit
@@ -79,7 +84,7 @@ TextEdit {
You can translate between cursor positions (characters from the start of the document) and pixel
points using positionAt() and positionToRectangle().
- \sa Text, TextInput
+ \sa Text, TextInput, TextArea, {Qt Quick Controls - Text Editor}
*/
/*!
@@ -101,22 +106,13 @@ static const int nodeBreakingSize = 300;
const int QQuickTextEditPrivate::largeTextSizeThreshold = QQUICKTEXT_LARGETEXT_THRESHOLD;
namespace {
- class ProtectedLayoutAccessor: public QAbstractTextDocumentLayout
- {
- public:
- inline QTextCharFormat formatAccessor(int pos)
- {
- return format(pos);
- }
- };
-
class RootNode : public QSGTransformNode
{
public:
RootNode() : cursorNode(nullptr), frameDecorationsNode(nullptr)
{ }
- void resetFrameDecorations(QQuickTextNode* newNode)
+ void resetFrameDecorations(QSGInternalTextNode* newNode)
{
if (frameDecorationsNode) {
removeChildNode(frameDecorationsNode);
@@ -139,7 +135,7 @@ namespace {
}
QSGInternalRectangleNode *cursorNode;
- QQuickTextNode* frameDecorationsNode;
+ QSGInternalTextNode* frameDecorationsNode;
};
}
@@ -151,6 +147,12 @@ QQuickTextEdit::QQuickTextEdit(QQuickItem *parent)
d->init();
}
+QQuickTextEdit::~QQuickTextEdit()
+{
+ Q_D(QQuickTextEdit);
+ qDeleteAll(d->pixmapsInProgress);
+}
+
QQuickTextEdit::QQuickTextEdit(QQuickTextEditPrivate &dd, QQuickItem *parent)
: QQuickImplicitSizeItem(dd, parent)
{
@@ -211,17 +213,16 @@ QString QQuickTextEdit::text() const
The requested weight of the font. The weight requested must be an integer
between 1 and 1000, or one of the predefined values:
- \list
- \li Font.Thin
- \li Font.Light
- \li Font.ExtraLight
- \li Font.Normal - the default
- \li Font.Medium
- \li Font.DemiBold
- \li Font.Bold
- \li Font.ExtraBold
- \li Font.Black
- \endlist
+
+ \value Font.Thin 100
+ \value Font.ExtraLight 200
+ \value Font.Light 300
+ \value Font.Normal 400 (default)
+ \value Font.Medium 500
+ \value Font.DemiBold 600
+ \value Font.Bold 700
+ \value Font.ExtraBold 800
+ \value Font.Black 900
\qml
TextEdit { text: "Hello"; font.weight: Font.DemiBold }
@@ -286,13 +287,12 @@ QString QQuickTextEdit::text() const
Sets the capitalization for the text.
- \list
- \li Font.MixedCase - This is the normal text rendering option where no capitalization change is applied.
- \li Font.AllUppercase - This alters the text to be rendered in all uppercase type.
- \li Font.AllLowercase - This alters the text to be rendered in all lowercase type.
- \li Font.SmallCaps - This alters the text to be rendered in small-caps type.
- \li Font.Capitalize - This alters the text to be rendered with the first character of each word as an uppercase character.
- \endlist
+ \value Font.MixedCase no capitalization change is applied
+ \value Font.AllUppercase alters the text to be rendered in all uppercase type
+ \value Font.AllLowercase alters the text to be rendered in all lowercase type
+ \value Font.SmallCaps alters the text to be rendered in small-caps type
+ \value Font.Capitalize alters the text to be rendered with the first character of
+ each word as an uppercase character
\qml
TextEdit { text: "Hello"; font.capitalization: Font.AllLowercase }
@@ -309,23 +309,21 @@ QString QQuickTextEdit::text() const
\note This property only has an effect when used together with render type TextEdit.NativeRendering.
- \list
- \li Font.PreferDefaultHinting - Use the default hinting level for the target platform.
- \li Font.PreferNoHinting - If possible, render text without hinting the outlines
- of the glyphs. The text layout will be typographically accurate, using the same metrics
- as are used e.g. when printing.
- \li Font.PreferVerticalHinting - If possible, render text with no horizontal hinting,
- but align glyphs to the pixel grid in the vertical direction. The text will appear
- crisper on displays where the density is too low to give an accurate rendering
- of the glyphs. But since the horizontal metrics of the glyphs are unhinted, the text's
- layout will be scalable to higher density devices (such as printers) without impacting
- details such as line breaks.
- \li Font.PreferFullHinting - If possible, render text with hinting in both horizontal and
- vertical directions. The text will be altered to optimize legibility on the target
- device, but since the metrics will depend on the target size of the text, the positions
- of glyphs, line breaks, and other typographical detail will not scale, meaning that a
- text layout may look different on devices with different pixel densities.
- \endlist
+ \value Font.PreferDefaultHinting Use the default hinting level for the target platform.
+ \value Font.PreferNoHinting If possible, render text without hinting the outlines
+ of the glyphs. The text layout will be typographically accurate, using the same metrics
+ as are used e.g. when printing.
+ \value Font.PreferVerticalHinting If possible, render text with no horizontal hinting,
+ but align glyphs to the pixel grid in the vertical direction. The text will appear
+ crisper on displays where the density is too low to give an accurate rendering
+ of the glyphs. But since the horizontal metrics of the glyphs are unhinted, the text's
+ layout will be scalable to higher density devices (such as printers) without impacting
+ details such as line breaks.
+ \value Font.PreferFullHinting If possible, render text with hinting in both horizontal and
+ vertical directions. The text will be altered to optimize legibility on the target
+ device, but since the metrics will depend on the target size of the text, the positions
+ of glyphs, line breaks, and other typographical detail will not scale, meaning that a
+ text layout may look different on devices with different pixel densities.
\qml
TextEdit { text: "Hello"; renderType: TextEdit.NativeRendering; font.hintingPreference: Font.PreferVerticalHinting }
@@ -363,6 +361,34 @@ QString QQuickTextEdit::text() const
*/
/*!
+ \qmlproperty object QtQuick::TextEdit::font.variableAxes
+ \since 6.7
+
+ \include qquicktext.cpp qml-font-variable-axes
+*/
+
+/*!
+ \qmlproperty object QtQuick::TextEdit::font.features
+ \since 6.6
+
+ \include qquicktext.cpp qml-font-features
+*/
+
+/*!
+ \qmlproperty bool QtQuick::TextEdit::font.contextFontMerging
+ \since 6.8
+
+ \include qquicktext.cpp qml-font-context-font-merging
+*/
+
+/*!
+ \qmlproperty bool QtQuick::TextEdit::font.preferTypoLineMetrics
+ \since 6.8
+
+ \include qquicktext.cpp qml-font-prefer-typo-line-metrics
+*/
+
+/*!
\qmlproperty string QtQuick::TextEdit::text
The text to display. If the text format is AutoText the text edit will
@@ -376,7 +402,20 @@ QString QQuickTextEdit::text() const
remarkably better performance for modifying especially large rich text
content.
- \sa clear(), textFormat
+ Note that some keyboards use a predictive function. In this case,
+ the text being composed by the input method is not part of this property.
+ The part of the text related to the predictions is underlined and stored in
+ the \l preeditText property.
+
+ If you used \l TextDocument::source to load text, you can retrieve the
+ loaded text from this property. In that case, you can then change
+ \l textFormat to do format conversions that will change the value of the
+ \c text property. For example, if \c textFormat is \c RichText or
+ \c AutoText and you load an HTML file, then set \c textFormat to
+ \c MarkdownText afterwards, the \c text property will contain the
+ conversion from HTML to Markdown.
+
+ \sa clear(), preeditText, textFormat
*/
void QQuickTextEdit::setText(const QString &text)
{
@@ -384,7 +423,6 @@ void QQuickTextEdit::setText(const QString &text)
if (QQuickTextEdit::text() == text)
return;
- d->document->clearResources();
d->richText = d->format == RichText || (d->format == AutoText && Qt::mightBeRichText(text));
d->markdownText = d->format == MarkdownText;
if (!isComponentComplete()) {
@@ -426,6 +464,11 @@ void QQuickTextEdit::q_invalidate()
\since 5.7
This property contains partial text input from an input method.
+
+ To turn off partial text that results from predictions, set the \c Qt.ImhNoPredictiveText
+ flag in inputMethodHints.
+
+ \sa inputMethodHints
*/
QString QQuickTextEdit::preeditText() const
{
@@ -442,6 +485,7 @@ QString QQuickTextEdit::preeditText() const
\value TextEdit.PlainText (default) all styling tags are treated as plain text
\value TextEdit.AutoText detected via the Qt::mightBeRichText() heuristic
+ or the file format of \l TextDocument::source
\value TextEdit.RichText \l {Supported HTML Subset} {a subset of HTML 4}
\value TextEdit.MarkdownText \l {https://commonmark.org/help/}{CommonMark} plus the
\l {https://guides.github.com/features/mastering-markdown/}{GitHub}
@@ -449,9 +493,12 @@ QString QQuickTextEdit::preeditText() const
The default is \c TextEdit.PlainText. If the text format is set to
\c TextEdit.AutoText, the text edit will automatically determine whether
- the text should be treated as rich text. This determination is made using
- Qt::mightBeRichText(), which can detect the presence of an HTML tag on the
- first line of text, but cannot distinguish Markdown from plain text.
+ the text should be treated as rich text. If the \l text property is set,
+ this determination is made using Qt::mightBeRichText(), which can detect
+ the presence of an HTML tag on the first line of text, but cannot
+ distinguish Markdown from plain text. If the \l TextDocument::source
+ property is set, this determination is made from the
+ \l {QMimeDatabase::mimeTypeForFile()}{mime type of the file}.
\table
\row
@@ -464,14 +511,23 @@ QString QQuickTextEdit::preeditText() const
\l {https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown}{GitHub checkbox extension}
are interactively checkable.
- \note Interactively typing markup or markdown formatting is not supported.
+ If the \l TextDocument::source property is set, changing the \c textFormat
+ property after loading has the effect of converting from the detected
+ format to the requested format. For example, you can convert between HTML
+ and Markdown. However if either of those "rich" formats is loaded and then
+ you set \c textFormat to \c PlainText, the TextEdit will show the raw
+ markup. Thus, suitable bindings (e.g. to a checkable Control) can enable
+ the user to toggle back and forth between "raw" and WYSIWYG editing.
+
+ \note Interactively typing markup or markdown formatting in WYSIWYG mode
+ is not supported; but you can switch to \c PlainText, make changes, then
+ switch back to the appropriate \c textFormat.
\note With \c Text.MarkdownText, and with the supported subset of HTML,
some decorative elements are not rendered as they would be in a web browser:
\list
\li code blocks use the \l {QFontDatabase::FixedFont}{default monospace font} but without a surrounding highlight box
\li block quotes are indented, but there is no vertical line alongside the quote
- \li horizontal rules are not rendered
\endlist
*/
QQuickTextEdit::TextFormat QQuickTextEdit::textFormat() const
@@ -486,35 +542,135 @@ void QQuickTextEdit::setTextFormat(TextFormat format)
if (format == d->format)
return;
- const bool wasRich = d->richText;
- const bool wasMarkdown = d->markdownText;
- d->richText = format == RichText || (format == AutoText && (wasRich || Qt::mightBeRichText(text())));
- d->markdownText = format == MarkdownText;
+ auto mightBeRichText = [this]() {
+ return Qt::mightBeRichText(text());
+ };
+
+ auto findSourceFormat = [d, mightBeRichText](Qt::TextFormat detectedFormat) {
+ if (d->format == PlainText)
+ return PlainText;
+ if (d->richText) return RichText;
+ if (d->markdownText) return MarkdownText;
+ if (detectedFormat == Qt::AutoText && mightBeRichText())
+ return RichText;
+ return PlainText;
+ };
+
+ auto findDestinationFormat = [format, mightBeRichText](Qt::TextFormat detectedFormat, TextFormat sourceFormat) {
+ if (format == AutoText) {
+ if (detectedFormat == Qt::MarkdownText || (detectedFormat == Qt::AutoText && sourceFormat == MarkdownText))
+ return MarkdownText;
+ if (detectedFormat == Qt::RichText || (detectedFormat == Qt::AutoText && (sourceFormat == RichText || mightBeRichText())))
+ return RichText;
+ return PlainText; // fallback
+ }
+ return format;
+ };
+
+ bool textCachedChanged = false;
+ bool converted = false;
if (isComponentComplete()) {
-#if QT_CONFIG(texthtmlparser)
- if (wasRich && !d->richText && !d->markdownText) {
- d->control->setPlainText(!d->textCached ? d->control->toHtml() : d->text);
- updateSize();
- } else if (!wasRich && d->richText) {
- d->control->setHtml(!d->textCached ? d->control->toPlainText() : d->text);
- updateSize();
+ Qt::TextFormat detectedFormat = Qt::AutoText; // default if we don't know
+ if (d->quickDocument) {
+ // If QQuickTextDocument is in use, content can be loaded from a file,
+ // and then mime type detection overrides mightBeRichText().
+ detectedFormat = QQuickTextDocumentPrivate::get(d->quickDocument)->detectedFormat;
+ }
+
+ const TextFormat sourceFormat = findSourceFormat(detectedFormat);
+ const TextFormat destinationFormat = findDestinationFormat(detectedFormat, sourceFormat);
+
+ d->richText = destinationFormat == RichText;
+ d->markdownText = destinationFormat == MarkdownText;
+
+ // If converting between markdown and HTML, avoid using cached text: have QTD re-generate it
+ if (format != PlainText && (sourceFormat != destinationFormat)) {
+ d->textCached = false;
+ textCachedChanged = true;
}
+
+ switch (destinationFormat) {
+ case PlainText:
+#if QT_CONFIG(texthtmlparser)
+ if (sourceFormat == RichText) {
+ // If rich or unknown text was loaded and now the user wants plain text, get the raw HTML.
+ // But if we didn't set textCached to false above, assume d->text already contains HTML.
+ // This will allow the user to see the actual HTML they loaded (rather than Qt regenerating crufty HTML).
+ d->control->setPlainText(d->textCached ? d->text : d->control->toHtml());
+ converted = true;
+ }
#endif
#if QT_CONFIG(textmarkdownwriter) && QT_CONFIG(textmarkdownreader)
- if (wasMarkdown && !d->markdownText && !d->richText) {
- d->control->setPlainText(!d->textCached ? d->control->toMarkdown() : d->text);
- updateSize();
- } else if (!wasMarkdown && d->markdownText) {
- d->control->setMarkdownText(!d->textCached ? d->control->toPlainText() : d->text);
- updateSize();
- }
+ if (sourceFormat == MarkdownText) {
+ // If markdown or unknown text was loaded and now the user wants plain text, get the raw Markdown.
+ // But if we didn't set textCached to false above, assume d->text already contains markdown.
+ // This will allow the user to see the actual markdown they loaded.
+ d->control->setPlainText(d->textCached ? d->text : d->control->toMarkdown());
+ converted = true;
+ }
+#endif
+ break;
+ case RichText:
+#if QT_CONFIG(texthtmlparser)
+ switch (sourceFormat) {
+ case MarkdownText:
+ // If markdown was loaded and now the user wants HTML, convert markdown to HTML.
+ d->control->setHtml(d->control->toHtml());
+ converted = true;
+ break;
+ case PlainText:
+ // If plain text was loaded and now the user wants HTML, interpret plain text as HTML.
+ // But if we didn't set textCached to false above, assume d->text already contains HTML.
+ d->control->setHtml(d->textCached ? d->text : d->control->toPlainText());
+ converted = true;
+ break;
+ case AutoText:
+ case RichText: // nothing to do
+ break;
+ }
+#endif
+ break;
+ case MarkdownText:
+#if QT_CONFIG(textmarkdownwriter) && QT_CONFIG(textmarkdownreader)
+ switch (sourceFormat) {
+ case RichText:
+ // If HTML was loaded and now the user wants markdown, convert HTML to markdown.
+ d->control->setMarkdownText(d->control->toMarkdown());
+ converted = true;
+ break;
+ case PlainText:
+ // If plain text was loaded and now the user wants markdown, interpret plain text as markdown.
+ // But if we didn't set textCached to false above, assume d->text already contains markdown.
+ d->control->setMarkdownText(d->textCached ? d->text : d->control->toPlainText());
+ converted = true;
+ break;
+ case AutoText:
+ case MarkdownText: // nothing to do
+ break;
+ }
#endif
+ break;
+ case AutoText: // nothing to do
+ break;
+ }
+
+ if (converted)
+ updateSize();
+ } else {
+ d->richText = format == RichText || (format == AutoText && (d->richText || mightBeRichText()));
+ d->markdownText = format == MarkdownText;
}
+ qCDebug(lcTextEdit) << d->format << "->" << format
+ << "rich?" << d->richText << "md?" << d->markdownText
+ << "converted?" << converted << "cache invalidated?" << textCachedChanged;
+
d->format = format;
d->control->setAcceptRichText(d->format != PlainText);
emit textFormatChanged(d->format);
+ if (textCachedChanged)
+ emit textChanged();
}
/*!
@@ -523,16 +679,23 @@ void QQuickTextEdit::setTextFormat(TextFormat format)
Override the default rendering type for this component.
Supported render types are:
- \list
- \li Text.QtRendering
- \li Text.NativeRendering
- \endlist
- Select Text.NativeRendering if you prefer text to look native on the target platform and do
+ \value TextEdit.QtRendering Text is rendered using a scalable distance field for each glyph.
+ \value TextEdit.NativeRendering Text is rendered using a platform-specific technique.
+ \value TextEdit.CurveRendering Text is rendered using a curve rasterizer running directly on
+ the graphics hardware. (Introduced in Qt 6.7.0.)
+
+ Select \c TextEdit.NativeRendering if you prefer text to look native on the target platform and do
not require advanced features such as transformation of the text. Using such features in
combination with the NativeRendering render type will lend poor and sometimes pixelated
results.
+ Both \c TextEdit.QtRendering and \c TextEdit.CurveRendering are hardware-accelerated techniques.
+ \c QtRendering is the faster of the two, but uses more memory and will exhibit rendering
+ artifacts at large sizes. \c CurveRendering should be considered as an alternative in cases
+ where \c QtRendering does not give good visual results or where reducing graphics memory
+ consumption is a priority.
+
The default rendering type is determined by \l QQuickWindow::textRenderType().
*/
QQuickTextEdit::RenderType QQuickTextEdit::renderType() const
@@ -678,19 +841,21 @@ void QQuickTextEdit::setSelectedTextColor(const QColor &color)
the left.
Valid values for \c horizontalAlignment are:
- \list
- \li TextEdit.AlignLeft (default)
- \li TextEdit.AlignRight
- \li TextEdit.AlignHCenter
- \li TextEdit.AlignJustify
- \endlist
+
+ \value TextEdit.AlignLeft
+ left alignment with ragged edges on the right (default)
+ \value TextEdit.AlignRight
+ align each line to the right with ragged edges on the left
+ \value TextEdit.AlignHCenter
+ align each line to the center
+ \value TextEdit.AlignJustify
+ align each line to both right and left, spreading out words as necessary
Valid values for \c verticalAlignment are:
- \list
- \li TextEdit.AlignTop (default)
- \li TextEdit.AlignBottom
- \li TextEdit.AlignVCenter
- \endlist
+
+ \value TextEdit.AlignTop start at the top of the item (default)
+ \value TextEdit.AlignBottom align the last line to the bottom and other lines above
+ \value TextEdit.AlignVCenter align the center vertically
When using the attached property LayoutMirroring::enabled to mirror application
layouts, the horizontal alignment of text will also be mirrored. However, the property
@@ -706,11 +871,11 @@ QQuickTextEdit::HAlignment QQuickTextEdit::hAlign() const
void QQuickTextEdit::setHAlign(HAlignment align)
{
Q_D(QQuickTextEdit);
- bool forceAlign = d->hAlignImplicit && d->effectiveLayoutMirror;
- d->hAlignImplicit = false;
- if (d->setHAlign(align, forceAlign) && isComponentComplete()) {
+
+ if (d->setHAlign(align, true) && isComponentComplete()) {
d->updateDefaultTextOption();
updateSize();
+ updateWholeDocument();
}
}
@@ -743,21 +908,34 @@ QQuickTextEdit::HAlignment QQuickTextEdit::effectiveHAlign() const
return effectiveAlignment;
}
-bool QQuickTextEditPrivate::setHAlign(QQuickTextEdit::HAlignment alignment, bool forceAlign)
+bool QQuickTextEditPrivate::setHAlign(QQuickTextEdit::HAlignment align, bool forceAlign)
{
Q_Q(QQuickTextEdit);
- if (hAlign != alignment || forceAlign) {
- QQuickTextEdit::HAlignment oldEffectiveHAlign = q->effectiveHAlign();
- hAlign = alignment;
- emit q->horizontalAlignmentChanged(alignment);
- if (oldEffectiveHAlign != q->effectiveHAlign())
- emit q->effectiveHorizontalAlignmentChanged();
+ if (hAlign == align && !forceAlign)
+ return false;
+
+ const bool wasImplicit = hAlignImplicit;
+ const auto oldEffectiveHAlign = q->effectiveHAlign();
+
+ hAlignImplicit = !forceAlign;
+ if (hAlign != align) {
+ hAlign = align;
+ emit q->horizontalAlignmentChanged(align);
+ }
+
+ if (q->effectiveHAlign() != oldEffectiveHAlign) {
+ emit q->effectiveHorizontalAlignmentChanged();
return true;
}
+
+ if (forceAlign && wasImplicit) {
+ // QTBUG-120052 - when horizontal text alignment is set explicitly,
+ // we need notify any other controls that may depend on it, like QQuickPlaceholderText
+ emit q->effectiveHorizontalAlignmentChanged();
+ }
return false;
}
-
Qt::LayoutDirection QQuickTextEditPrivate::textDirection(const QString &text) const
{
const QChar *character = text.constData();
@@ -780,20 +958,24 @@ Qt::LayoutDirection QQuickTextEditPrivate::textDirection(const QString &text) co
bool QQuickTextEditPrivate::determineHorizontalAlignment()
{
Q_Q(QQuickTextEdit);
- if (hAlignImplicit && q->isComponentComplete()) {
- Qt::LayoutDirection direction = contentDirection;
+ if (!hAlignImplicit || !q->isComponentComplete())
+ return false;
+
+ Qt::LayoutDirection direction = contentDirection;
#if QT_CONFIG(im)
- if (direction == Qt::LayoutDirectionAuto) {
- const QString preeditText = control->textCursor().block().layout()->preeditAreaText();
- direction = textDirection(preeditText);
- }
- if (direction == Qt::LayoutDirectionAuto)
- direction = qGuiApp->inputMethod()->inputDirection();
+ if (direction == Qt::LayoutDirectionAuto) {
+ QTextBlock block = control->textCursor().block();
+ if (!block.layout())
+ return false;
+ direction = textDirection(block.layout()->preeditAreaText());
+ }
+ if (direction == Qt::LayoutDirectionAuto)
+ direction = qGuiApp->inputMethod()->inputDirection();
#endif
- return setHAlign(direction == Qt::RightToLeft ? QQuickTextEdit::AlignRight : QQuickTextEdit::AlignLeft);
- }
- return false;
+ const auto implicitHAlign = direction == Qt::RightToLeft ?
+ QQuickTextEdit::AlignRight : QQuickTextEdit::AlignLeft;
+ return setHAlign(implicitHAlign);
}
void QQuickTextEditPrivate::mirrorChange()
@@ -840,6 +1022,26 @@ Qt::InputMethodHints QQuickTextEditPrivate::effectiveInputMethodHints() const
}
#endif
+#if QT_CONFIG(accessibility)
+void QQuickTextEditPrivate::accessibilityActiveChanged(bool active)
+{
+ if (!active)
+ return;
+
+ Q_Q(QQuickTextEdit);
+ if (QQuickAccessibleAttached *accessibleAttached = qobject_cast<QQuickAccessibleAttached *>(
+ qmlAttachedPropertiesObject<QQuickAccessibleAttached>(q, true))) {
+ accessibleAttached->setRole(effectiveAccessibleRole());
+ accessibleAttached->set_readOnly(q->isReadOnly());
+ }
+}
+
+QAccessible::Role QQuickTextEditPrivate::accessibleRole() const
+{
+ return QAccessible::EditableText;
+}
+#endif
+
void QQuickTextEditPrivate::setTopPadding(qreal value, bool reset)
{
Q_Q(QQuickTextEdit);
@@ -930,20 +1132,26 @@ void QQuickTextEdit::setVAlign(QQuickTextEdit::VAlignment alignment)
moveCursorDelegate();
emit verticalAlignmentChanged(d->vAlign);
}
+
/*!
\qmlproperty enumeration QtQuick::TextEdit::wrapMode
Set this property to wrap the text to the TextEdit item's width.
The text will only wrap if an explicit width has been set.
- \list
- \li TextEdit.NoWrap - no wrapping will be performed. If the text contains insufficient newlines, then implicitWidth will exceed a set width.
- \li TextEdit.WordWrap - wrapping is done on word boundaries only. If a word is too long, implicitWidth will exceed a set width.
- \li TextEdit.WrapAnywhere - wrapping is done at any point on a line, even if it occurs in the middle of a word.
- \li TextEdit.Wrap - if possible, wrapping occurs at a word boundary; otherwise it will occur at the appropriate point on the line, even in the middle of a word.
- \endlist
+ \value TextEdit.NoWrap
+ (default) no wrapping will be performed. If the text contains insufficient newlines,
+ \l {Item::}{implicitWidth} will exceed a set width.
+ \value TextEdit.WordWrap
+ wrapping is done on word boundaries only. If a word is too long,
+ \l {Item::}{implicitWidth} will exceed a set width.
+ \value TextEdit.WrapAnywhere
+ wrapping is done at any point on a line, even if it occurs in the middle of a word.
+ \value TextEdit.Wrap
+ if possible, wrapping occurs at a word boundary; otherwise it will occur at the appropriate
+ point on the line, even in the middle of a word.
- The default is TextEdit.NoWrap. If you set a width, consider using TextEdit.Wrap.
+ The default is \c TextEdit.NoWrap. If you set a width, consider using \c TextEdit.Wrap.
*/
QQuickTextEdit::WrapMode QQuickTextEdit::wrapMode() const
{
@@ -1107,6 +1315,24 @@ int QQuickTextEdit::positionAt(qreal x, qreal y) const
}
/*!
+ \qmlproperty QtQuick::TextSelection QtQuick::TextEdit::cursorSelection
+ \since 6.7
+ \preliminary
+
+ This property is an object that provides properties of the text that is
+ currently selected, if any, alongside the text cursor.
+
+ \sa selectedText, selectionStart, selectionEnd
+*/
+QQuickTextSelection *QQuickTextEdit::cursorSelection() const
+{
+ Q_D(const QQuickTextEdit);
+ if (!d->cursorSelection)
+ d->cursorSelection = new QQuickTextSelection(const_cast<QQuickTextEdit *>(this));
+ return d->cursorSelection;
+}
+
+/*!
\qmlmethod QtQuick::TextEdit::moveCursorSelection(int position, SelectionMode mode)
Moves the cursor to \a position and updates the selection according to the optional \a mode
@@ -1120,13 +1346,12 @@ int QQuickTextEdit::positionAt(qreal x, qreal y) const
The selection mode specifies whether the selection is updated on a per character or a per word
basis. If not specified the selection mode will default to \c {TextEdit.SelectCharacters}.
- \list
- \li TextEdit.SelectCharacters - Sets either the selectionStart or selectionEnd (whichever was at
- the previous cursor position) to the specified position.
- \li TextEdit.SelectWords - Sets the selectionStart and selectionEnd to include all
- words between the specified position and the previous cursor position. Words partially in the
- range are included.
- \endlist
+ \value TextEdit.SelectCharacters
+ Sets either the selectionStart or selectionEnd (whichever was at the previous cursor position)
+ to the specified position.
+ \value TextEdit.SelectWords
+ Sets the selectionStart and selectionEnd to include all words between the specified position
+ and the previous cursor position. Words partially in the range are included.
For example, take this sequence of calls:
@@ -1424,38 +1649,31 @@ void QQuickTextEdit::setTextMargin(qreal margin)
Flags that alter behaviour are:
- \list
- \li Qt.ImhHiddenText - Characters should be hidden, as is typically used when entering passwords.
- \li Qt.ImhSensitiveData - Typed text should not be stored by the active input method
- in any persistent storage like predictive user dictionary.
- \li Qt.ImhNoAutoUppercase - The input method should not try to automatically switch to upper case
- when a sentence ends.
- \li Qt.ImhPreferNumbers - Numbers are preferred (but not required).
- \li Qt.ImhPreferUppercase - Upper case letters are preferred (but not required).
- \li Qt.ImhPreferLowercase - Lower case letters are preferred (but not required).
- \li Qt.ImhNoPredictiveText - Do not use predictive text (i.e. dictionary lookup) while typing.
-
- \li Qt.ImhDate - The text editor functions as a date field.
- \li Qt.ImhTime - The text editor functions as a time field.
- \endlist
+ \value Qt.ImhHiddenText Characters should be hidden, as is typically used when entering passwords.
+ \value Qt.ImhSensitiveData Typed text should not be stored by the active input method
+ in any persistent storage like predictive user dictionary.
+ \value Qt.ImhNoAutoUppercase The input method should not try to automatically switch to
+ upper case when a sentence ends.
+ \value Qt.ImhPreferNumbers Numbers are preferred (but not required).
+ \value Qt.ImhPreferUppercase Upper case letters are preferred (but not required).
+ \value Qt.ImhPreferLowercase Lower case letters are preferred (but not required).
+ \value Qt.ImhNoPredictiveText Do not use predictive text (i.e. dictionary lookup) while typing.
+ \value Qt.ImhDate The text editor functions as a date field.
+ \value Qt.ImhTime The text editor functions as a time field.
Flags that restrict input (exclusive flags) are:
- \list
- \li Qt.ImhDigitsOnly - Only digits are allowed.
- \li Qt.ImhFormattedNumbersOnly - Only number input is allowed. This includes decimal point and minus sign.
- \li Qt.ImhUppercaseOnly - Only upper case letter input is allowed.
- \li Qt.ImhLowercaseOnly - Only lower case letter input is allowed.
- \li Qt.ImhDialableCharactersOnly - Only characters suitable for phone dialing are allowed.
- \li Qt.ImhEmailCharactersOnly - Only characters suitable for email addresses are allowed.
- \li Qt.ImhUrlCharactersOnly - Only characters suitable for URLs are allowed.
- \endlist
+ \value Qt.ImhDigitsOnly Only digits are allowed.
+ \value Qt.ImhFormattedNumbersOnly Only number input is allowed. This includes decimal point and minus sign.
+ \value Qt.ImhUppercaseOnly Only upper case letter input is allowed.
+ \value Qt.ImhLowercaseOnly Only lower case letter input is allowed.
+ \value Qt.ImhDialableCharactersOnly Only characters suitable for phone dialing are allowed.
+ \value Qt.ImhEmailCharactersOnly Only characters suitable for email addresses are allowed.
+ \value Qt.ImhUrlCharactersOnly Only characters suitable for URLs are allowed.
Masks:
- \list
- \li Qt.ImhExclusiveInputMask - This mask yields nonzero if any of the exclusive flags are used.
- \endlist
+ \value Qt.ImhExclusiveInputMask This mask yields nonzero if any of the exclusive flags are used.
*/
Qt::InputMethodHints QQuickTextEdit::inputMethodHints() const
@@ -1487,14 +1705,35 @@ void QQuickTextEdit::setInputMethodHints(Qt::InputMethodHints hints)
void QQuickTextEdit::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
{
Q_D(QQuickTextEdit);
- if (!d->inLayout && ((newGeometry.width() != oldGeometry.width() && widthValid())
- || (newGeometry.height() != oldGeometry.height() && heightValid()))) {
+ if (!d->inLayout && ((newGeometry.width() != oldGeometry.width())
+ || (newGeometry.height() != oldGeometry.height()))) {
updateSize();
updateWholeDocument();
- moveCursorDelegate();
+ if (widthValid() || heightValid())
+ moveCursorDelegate();
}
QQuickImplicitSizeItem::geometryChange(newGeometry, oldGeometry);
+}
+void QQuickTextEdit::itemChange(ItemChange change, const ItemChangeData &value)
+{
+ Q_D(QQuickTextEdit);
+ Q_UNUSED(value);
+ switch (change) {
+ case ItemDevicePixelRatioHasChanged:
+ if (d->renderType == NativeRendering) {
+ // Native rendering optimizes for a given pixel grid, so its results must not be scaled.
+ // Text layout code respects the current device pixel ratio automatically, we only need
+ // to rerun layout after the ratio changed.
+ updateSize();
+ updateWholeDocument();
+ }
+ break;
+
+ default:
+ break;
+ }
+ QQuickImplicitSizeItem::itemChange(change, value);
}
/*!
@@ -1509,20 +1748,17 @@ void QQuickTextEdit::componentComplete()
const QUrl url = baseUrl();
const QQmlContext *context = qmlContext(this);
d->document->setBaseUrl(context ? context->resolvedUrl(url) : url);
+ if (!d->text.isEmpty()) {
#if QT_CONFIG(texthtmlparser)
- if (d->richText)
- d->control->setHtml(d->text);
- else
+ if (d->richText)
+ d->control->setHtml(d->text);
+ else
#endif
#if QT_CONFIG(textmarkdownreader)
- if (d->markdownText)
- d->control->setMarkdownText(d->text);
- else
-#endif
- if (!d->text.isEmpty()) {
if (d->markdownText)
d->control->setMarkdownText(d->text);
else
+#endif
d->control->setPlainText(d->text);
}
@@ -1535,6 +1771,17 @@ void QQuickTextEdit::componentComplete()
if (d->cursorComponent && isCursorVisible())
QQuickTextUtil::createCursor(d);
polish();
+
+#if QT_CONFIG(accessibility)
+ if (QAccessible::isActive())
+ d->accessibilityActiveChanged(true);
+#endif
+}
+
+int QQuickTextEdit::resourcesLoading() const
+{
+ Q_D(const QQuickTextEdit);
+ return d->pixmapsInProgress.size();
}
/*!
@@ -1627,10 +1874,8 @@ void QQuickTextEdit::setSelectByMouse(bool on)
Specifies how text should be selected using a mouse.
- \list
- \li TextEdit.SelectCharacters - The selection is updated with individual characters. (Default)
- \li TextEdit.SelectWords - The selection is updated with whole words.
- \endlist
+ \value TextEdit.SelectCharacters (default) The selection is updated with individual characters.
+ \value TextEdit.SelectWords The selection is updated with whole words.
This property only applies when \l selectByMouse is true.
*/
@@ -1694,6 +1939,13 @@ void QQuickTextEdit::setReadOnly(bool r)
} else if (hasActiveFocus()) {
setCursorVisible(true);
}
+
+#if QT_CONFIG(accessibility)
+ if (QAccessible::isActive()) {
+ if (QQuickAccessibleAttached *accessibleAttached = QQuickAccessibleAttached::attachedProperties(this))
+ accessibleAttached->set_readOnly(r);
+ }
+#endif
}
bool QQuickTextEdit::isReadOnly() const
@@ -2055,6 +2307,87 @@ void QQuickTextEdit::triggerPreprocess()
update();
}
+/*! \internal
+ QTextDocument::loadResource() calls this to load inline images etc.
+ But if it's a local file, don't do it: let QTextDocument::loadResource()
+ load it in the default way. QQuickPixmap is for QtQuick-specific uses.
+*/
+QVariant QQuickTextEdit::loadResource(int type, const QUrl &source)
+{
+ Q_D(QQuickTextEdit);
+ const QUrl url = d->document->baseUrl().resolved(source);
+ if (url.isLocalFile()) {
+ // qmlWarning if the file doesn't exist (because QTextDocument::loadResource() can't do that)
+ QFileInfo fi(QQmlFile::urlToLocalFileOrQrc(url));
+ if (!fi.exists())
+ qmlWarning(this) << "Cannot open: " << url.toString();
+ // let QTextDocument::loadResource() handle local file loading
+ return {};
+ }
+
+ // see if we already started a load job
+ auto existingJobIter = std::find_if(
+ d->pixmapsInProgress.cbegin(), d->pixmapsInProgress.cend(),
+ [&url](const auto *job) { return job->url() == url; } );
+ if (existingJobIter != d->pixmapsInProgress.cend()) {
+ const QQuickPixmap *job = *existingJobIter;
+ if (job->isError()) {
+ qmlWarning(this) << job->error();
+ d->pixmapsInProgress.erase(existingJobIter);
+ delete job;
+ return QImage();
+ } else {
+ qCDebug(lcTextEdit) << "already downloading" << url;
+ // existing job: return a null variant if it's not done yet
+ return job->isReady() ? job->image() : QVariant();
+ }
+ }
+
+ // not found: start a new load job
+ qCDebug(lcTextEdit) << "loading" << source << "resolved" << url
+ << "type" << static_cast<QTextDocument::ResourceType>(type);
+ QQmlContext *context = qmlContext(this);
+ Q_ASSERT(context);
+ // don't cache it in QQuickPixmapCache, because it's cached in QTextDocumentPrivate::cachedResources
+ QQuickPixmap *p = new QQuickPixmap(context->engine(), url, QQuickPixmap::Options{});
+ p->connectFinished(this, SLOT(resourceRequestFinished()));
+ d->pixmapsInProgress.append(p);
+ // the new job is probably not done; return a null variant if the caller should poll again
+ return p->isReady() ? p->image() : QVariant();
+}
+
+/*! \internal
+ Handle completion of a download that QQuickTextEdit::loadResource() started.
+*/
+void QQuickTextEdit::resourceRequestFinished()
+{
+ Q_D(QQuickTextEdit);
+ for (auto it = d->pixmapsInProgress.cbegin(); it != d->pixmapsInProgress.cend(); ++it) {
+ auto *job = *it;
+ if (job->isError()) {
+ // get QTextDocument::loadResource() to call QQuickTextEdit::loadResource() again, to return the placeholder
+ qCDebug(lcTextEdit) << "failed to load (error)" << job->url();
+ d->document->resource(QTextDocument::ImageResource, job->url());
+ // that will call QQuickTextEdit::loadResource() which will delete the job;
+ // so leave it in pixmapsInProgress for now, and stop this loop
+ break;
+ } else if (job->isReady()) {
+ // get QTextDocument::loadResource() to call QQuickTextEdit::loadResource() again, and cache the result
+ auto res = d->document->resource(QTextDocument::ImageResource, job->url());
+ // If QTextDocument::resource() returned a valid variant, it's been cached too. Either way, the job is done.
+ qCDebug(lcTextEdit) << (res.isValid() ? "done downloading" : "failed to load") << job->url() << job->rect();
+ d->pixmapsInProgress.erase(it);
+ delete job;
+ break;
+ }
+ }
+ if (d->pixmapsInProgress.isEmpty()) {
+ invalidate();
+ updateSize();
+ q_invalidate();
+ }
+}
+
typedef QQuickTextEditPrivate::Node TextNode;
using TextNodeIterator = QQuickTextEditPrivate::TextNodeIterator;
@@ -2063,7 +2396,7 @@ static inline bool operator<(const TextNode &n1, const TextNode &n2)
return n1.startPos() < n2.startPos();
}
-static inline void updateNodeTransform(QQuickTextNode* node, const QPointF &topLeft)
+static inline void updateNodeTransform(QSGInternalTextNode *node, const QPointF &topLeft)
{
QMatrix4x4 transformMatrix;
transformMatrix.translate(topLeft.x(), topLeft.y());
@@ -2089,6 +2422,23 @@ void QQuickTextEdit::invalidateFontCaches()
}
}
+QTextDocument *QQuickTextEdit::document() const
+{
+ Q_D(const QQuickTextEdit);
+ return d->document;
+}
+
+void QQuickTextEdit::setDocument(QTextDocument *doc)
+{
+ Q_D(QQuickTextEdit);
+ if (d->ownsDocument)
+ delete d->document;
+ d->document = doc;
+ d->ownsDocument = false;
+ d->control->setDocument(doc);
+ q_textChanged();
+}
+
inline void resetEngine(QQuickTextNodeEngine *engine, const QColor& textColor, const QColor& selectedTextColor, const QColor& selectionColor)
{
*engine = QQuickTextNodeEngine();
@@ -2114,7 +2464,7 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
delete oldNode;
oldNode = nullptr;
- // If we had any QQuickTextNode node references, they were deleted along with the root node
+ // If we had any QSGInternalTextNode node references, they were deleted along with the root node
// But here we must delete the Node structures in textNodeMap
d->textNodeMap.clear();
}
@@ -2123,6 +2473,10 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
RootNode *rootNode = static_cast<RootNode *>(oldNode);
TextNodeIterator nodeIterator = d->textNodeMap.begin();
+ std::optional<int> firstPosAcrossAllNodes;
+ if (nodeIterator != d->textNodeMap.end())
+ firstPosAcrossAllNodes = nodeIterator->startPos();
+
while (nodeIterator != d->textNodeMap.end() && !nodeIterator->dirty())
++nodeIterator;
@@ -2139,7 +2493,7 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
firstDirtyPos = nodeIterator->startPos();
// ### this could be optimized if the first and last dirty nodes are not connected
// as the intermediate text nodes would usually only need to be transformed differently.
- QQuickTextNode *firstCleanNode = nullptr;
+ QSGInternalTextNode *firstCleanNode = nullptr;
auto it = d->textNodeMap.constEnd();
while (it != nodeIterator) {
--it;
@@ -2165,7 +2519,7 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
rootNode->resetFrameDecorations(d->createTextNode());
resetEngine(&frameDecorationsEngine, d->color, d->selectedTextColor, d->selectionColor);
- QQuickTextNode *node = nullptr;
+ QSGInternalTextNode *node = nullptr;
int currentNodeSize = 0;
int nodeStart = firstDirtyPos;
@@ -2184,14 +2538,17 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
d->firstBlockInViewport = -1;
d->firstBlockPastViewport = -1;
+ int frameCount = -1;
while (!frames.isEmpty()) {
QTextFrame *textFrame = frames.takeFirst();
+ ++frameCount;
+ if (frameCount > 0)
+ firstDirtyPos = 0;
+ qCDebug(lcVP) << "frame" << frameCount << textFrame
+ << "from" << positionToRectangle(textFrame->firstPosition()).topLeft()
+ << "to" << positionToRectangle(textFrame->lastPosition()).bottomRight();
frames.append(textFrame->childFrames());
frameDecorationsEngine.addFrameDecorations(d->document, textFrame);
-
- if (textFrame->lastPosition() < firstDirtyPos
- || textFrame->firstPosition() >= firstCleanNode.startPos())
- continue;
resetEngine(&engine, d->color, d->selectedTextColor, d->selectionColor);
if (textFrame->firstPosition() > textFrame->lastPosition()
@@ -2199,7 +2556,7 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
node = d->createTextNode();
updateNodeTransform(node, d->document->documentLayout()->frameBoundingRect(textFrame).topLeft());
const int pos = textFrame->firstPosition() - 1;
- ProtectedLayoutAccessor *a = static_cast<ProtectedLayoutAccessor *>(d->document->documentLayout());
+ auto *a = static_cast<QtPrivate::ProtectedLayoutAccessor *>(d->document->documentLayout());
QTextCharFormat format = a->formatAccessor(pos);
QTextBlock block = textFrame->firstCursorPosition().block();
nodeOffset = d->document->documentLayout()->blockBoundingRect(block).topLeft();
@@ -2241,7 +2598,8 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
coveredRegion = block.layout()->boundingRect().adjusted(nodeOffset.x(), nodeOffset.y(), nodeOffset.x(), nodeOffset.y());
inView = coveredRegion.bottom() > viewport.top();
}
- if (d->firstBlockInViewport < 0 && inView) {
+ const bool potentiallyScrollingBackwards = firstPosAcrossAllNodes && *firstPosAcrossAllNodes == firstDirtyPos;
+ if (d->firstBlockInViewport < 0 && inView && potentiallyScrollingBackwards) {
// During backward scrolling, we need to iterate backwards from textNodeMap.begin() to fill the top of the viewport.
if (coveredRegion.top() > viewport.top() + 1) {
qCDebug(lcVP) << "checking backwards from block" << block.blockNumber() << "@" << nodeOffset.y() << coveredRegion;
@@ -2261,7 +2619,6 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
}
}
qCDebug(lcVP) << "first block in viewport" << block.blockNumber() << "@" << nodeOffset.y() << coveredRegion;
- d->firstBlockInViewport = block.blockNumber();
if (block.layout())
d->renderedRegion = coveredRegion;
} else {
@@ -2274,9 +2631,16 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
}
break; // skip rest of blocks in this frame
}
- if (inView && !block.text().isEmpty() && coveredRegion.isValid())
+ if (inView && !block.text().isEmpty() && coveredRegion.isValid()) {
d->renderedRegion = d->renderedRegion.united(coveredRegion);
+ // In case we're going to visit more (nested) frames after this, ensure that we
+ // don't omit any blocks that fit within the region that we claim as fully rendered.
+ if (!frames.isEmpty())
+ viewport = viewport.united(d->renderedRegion);
+ }
}
+ if (inView && d->firstBlockInViewport < 0)
+ d->firstBlockInViewport = block.blockNumber();
}
bool createdNodeInView = false;
@@ -2420,12 +2784,7 @@ bool QQuickTextEdit::isInputMethodComposing() const
}
QQuickTextEditPrivate::ExtraData::ExtraData()
- : padding(0)
- , topPadding(0)
- , leftPadding(0)
- , rightPadding(0)
- , bottomPadding(0)
- , explicitTopPadding(false)
+ : explicitTopPadding(false)
, explicitLeftPadding(false)
, explicitRightPadding(false)
, explicitBottomPadding(false)
@@ -2451,7 +2810,10 @@ void QQuickTextEditPrivate::init()
q->setAcceptHoverEvents(true);
- document = new QQuickTextDocumentWithImageResources(q);
+ document = new QTextDocument(q);
+ ownsDocument = true;
+ auto *imageHandler = new QQuickTextImageHandler(document);
+ document->documentLayout()->registerHandler(QTextFormat::ImageObject, imageHandler);
control = new QQuickTextControl(document, q);
control->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard | Qt::TextEditable);
@@ -2472,10 +2834,9 @@ void QQuickTextEditPrivate::init()
#if QT_CONFIG(clipboard)
qmlobject_connect(QGuiApplication::clipboard(), QClipboard, SIGNAL(dataChanged()), q, QQuickTextEdit, SLOT(q_canPasteChanged()));
#endif
- qmlobject_connect(document, QQuickTextDocumentWithImageResources, SIGNAL(undoAvailable(bool)), q, QQuickTextEdit, SIGNAL(canUndoChanged()));
- qmlobject_connect(document, QQuickTextDocumentWithImageResources, SIGNAL(redoAvailable(bool)), q, QQuickTextEdit, SIGNAL(canRedoChanged()));
- qmlobject_connect(document, QQuickTextDocumentWithImageResources, SIGNAL(imagesLoaded()), q, QQuickTextEdit, SLOT(updateSize()));
- QObject::connect(document, &QQuickTextDocumentWithImageResources::contentsChange, q, &QQuickTextEdit::q_contentsChange);
+ qmlobject_connect(document, QTextDocument, SIGNAL(undoAvailable(bool)), q, QQuickTextEdit, SIGNAL(canUndoChanged()));
+ qmlobject_connect(document, QTextDocument, SIGNAL(redoAvailable(bool)), q, QQuickTextEdit, SIGNAL(canRedoChanged()));
+ QObject::connect(document, &QTextDocument::contentsChange, q, &QQuickTextEdit::q_contentsChange);
QObject::connect(document->documentLayout(), &QAbstractTextDocumentLayout::updateBlock, q, &QQuickTextEdit::invalidateBlock);
QObject::connect(control, &QQuickTextControl::linkHovered, q, &QQuickTextEdit::q_linkHovered);
QObject::connect(control, &QQuickTextControl::markerHovered, q, &QQuickTextEdit::q_markerHovered);
@@ -2485,10 +2846,12 @@ void QQuickTextEditPrivate::init()
document->setUndoRedoEnabled(false); // flush undo buffer.
document->setUndoRedoEnabled(true);
updateDefaultTextOption();
+ document->setModified(false); // we merely changed some defaults: no edits worth saving yet
q->updateSize();
#if QT_CONFIG(cursor)
updateMouseCursorShape();
#endif
+ setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Preferred);
}
void QQuickTextEditPrivate::resetInputMethod()
@@ -2669,9 +3032,6 @@ void QQuickTextEdit::updateSize()
return;
}
- qreal naturalWidth = d->implicitWidth - leftPadding() - rightPadding();
-
- qreal newWidth = d->document->idealWidth();
// ### assumes that if the width is set, the text will fill to edges
// ### (unless wrap is false, then clipping will occur)
if (widthValid()) {
@@ -2683,8 +3043,7 @@ void QQuickTextEdit::updateSize()
}
if (d->requireImplicitWidth) {
d->document->setTextWidth(-1);
- naturalWidth = d->document->idealWidth();
-
+ const qreal naturalWidth = d->document->idealWidth();
const bool wasInLayout = d->inLayout;
d->inLayout = true;
if (d->isImplicitResizeEnabled())
@@ -2694,19 +3053,22 @@ void QQuickTextEdit::updateSize()
return; // get this far we'll get a warning to that effect.
}
const qreal newTextWidth = width() - leftPadding() - rightPadding();
- if (d->document->textWidth() != newTextWidth) {
+ if (d->document->textWidth() != newTextWidth)
+ d->document->setTextWidth(newTextWidth);
+ } else if (d->wrapMode == NoWrap) {
+ // normally, if explicit width is not set, we should call setTextWidth(-1) here,
+ // as we don't need to fit the text to any fixed width. But because of some bug
+ // in QTextDocument it also breaks RTL text alignment, so we use "idealWidth" instead.
+ const qreal newTextWidth = d->document->idealWidth();
+ if (d->document->textWidth() != newTextWidth)
d->document->setTextWidth(newTextWidth);
- newWidth = d->document->idealWidth();
- }
- //### need to confirm cost of always setting these
- } else if (d->wrapMode == NoWrap && d->document->textWidth() != newWidth) {
- d->document->setTextWidth(newWidth); // ### Text does not align if width is not set or the idealWidth exceeds the textWidth (QTextDoc bug)
} else {
d->document->setTextWidth(-1);
}
QFontMetricsF fm(d->font);
- qreal newHeight = d->document->isEmpty() ? qCeil(fm.height()) : d->document->size().height();
+ const qreal newHeight = d->document->isEmpty() ? qCeil(fm.height()) : d->document->size().height();
+ const qreal newWidth = d->document->idealWidth();
if (d->isImplicitResizeEnabled()) {
// ### Setting the implicitWidth triggers another updateSize(), and unless there are bindings nothing has changed.
@@ -2718,12 +3080,26 @@ void QQuickTextEdit::updateSize()
d->xoff = leftPadding() + qMax(qreal(0), QQuickTextUtil::alignedX(d->document->size().width(), width() - leftPadding() - rightPadding(), effectiveHAlign()));
d->yoff = topPadding() + QQuickTextUtil::alignedY(d->document->size().height(), height() - topPadding() - bottomPadding(), d->vAlign);
- setBaselineOffset(fm.ascent() + d->yoff + d->textMargin);
+
+ qreal baseline = fm.ascent();
+ QTextBlock firstBlock = d->document->firstBlock();
+ if (firstBlock.isValid() && firstBlock.layout() != nullptr && firstBlock.lineCount() > 0) {
+ QTextLine firstLine = firstBlock.layout()->lineAt(0);
+ if (firstLine.isValid())
+ baseline = firstLine.ascent();
+ }
+
+ setBaselineOffset(baseline + d->yoff + d->textMargin);
QSizeF size(newWidth, newHeight);
if (d->contentSize != size) {
d->contentSize = size;
- emit contentSizeChanged();
+ // Note: inResize is a bitfield so QScopedValueRollback can't be used here
+ const bool wasInResize = d->inResize;
+ d->inResize = true;
+ if (!wasInResize)
+ emit contentSizeChanged();
+ d->inResize = wasInResize;
updateTotalLines();
}
}
@@ -2861,6 +3237,34 @@ void QQuickTextEditPrivate::updateDefaultTextOption()
}
}
+void QQuickTextEditPrivate::onDocumentStatusChanged()
+{
+ Q_ASSERT(quickDocument);
+ switch (quickDocument->status()) {
+ case QQuickTextDocument::Status::Loaded:
+ case QQuickTextDocument::Status::Saved:
+ switch (QQuickTextDocumentPrivate::get(quickDocument)->detectedFormat) {
+ case Qt::RichText:
+ richText = (format == QQuickTextEdit::RichText || format == QQuickTextEdit::AutoText);
+ markdownText = false;
+ break;
+ case Qt::MarkdownText:
+ richText = false;
+ markdownText = (format == QQuickTextEdit::MarkdownText || format == QQuickTextEdit::AutoText);
+ break;
+ case Qt::PlainText:
+ richText = false;
+ markdownText = false;
+ break;
+ case Qt::AutoText: // format not detected
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
void QQuickTextEdit::focusInEvent(QFocusEvent *event)
{
Q_D(QQuickTextEdit);
@@ -2905,19 +3309,21 @@ void QQuickTextEditPrivate::handleFocusEvent(QFocusEvent *event)
}
}
-void QQuickTextEditPrivate::addCurrentTextNodeToRoot(QQuickTextNodeEngine *engine, QSGTransformNode *root, QQuickTextNode *node, TextNodeIterator &it, int startPos)
+void QQuickTextEditPrivate::addCurrentTextNodeToRoot(QQuickTextNodeEngine *engine, QSGTransformNode *root, QSGInternalTextNode *node, TextNodeIterator &it, int startPos)
{
engine->addToSceneGraph(node, QQuickText::Normal, QColor());
it = textNodeMap.insert(it, TextNode(startPos, node));
++it;
root->appendChildNode(node);
+ ++renderedBlockCount;
}
-QQuickTextNode *QQuickTextEditPrivate::createTextNode()
+QSGInternalTextNode *QQuickTextEditPrivate::createTextNode()
{
Q_Q(QQuickTextEdit);
- QQuickTextNode* node = new QQuickTextNode(q);
- node->setUseNativeRenderer(renderType == QQuickTextEdit::NativeRendering);
+ QSGInternalTextNode* node = sceneGraphContext()->createInternalTextNode(sceneGraphRenderContext());
+ node->setRenderType(QSGTextNode::RenderType(renderType));
+ node->setFiltering(q->smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
return node;
}
@@ -3047,8 +3453,9 @@ void QQuickTextEdit::remove(int start, int end)
\since 5.1
Returns the QQuickTextDocument of this TextEdit.
- It can be used to implement syntax highlighting using
- \l QSyntaxHighlighter.
+ Since Qt 6.7, it has features for loading and saving files.
+ It can also be used in C++ as a means of accessing the underlying QTextDocument
+ instance, for example to install a \l QSyntaxHighlighter.
\sa QQuickTextDocument
*/
@@ -3056,8 +3463,11 @@ void QQuickTextEdit::remove(int start, int end)
QQuickTextDocument *QQuickTextEdit::textDocument()
{
Q_D(QQuickTextEdit);
- if (!d->quickDocument)
+ if (!d->quickDocument) {
d->quickDocument = new QQuickTextDocument(this);
+ connect(d->quickDocument, &QQuickTextDocument::statusChanged, d->quickDocument,
+ [d]() { d->onDocumentStatusChanged(); } );
+ }
return d->quickDocument;
}
@@ -3399,4 +3809,3 @@ QQuickPre64TextEdit::QQuickPre64TextEdit(QQuickItem *parent)
QT_END_NAMESPACE
#include "moc_qquicktextedit_p.cpp"
-
diff --git a/src/quick/items/qquicktextedit_p.h b/src/quick/items/qquicktextedit_p.h
index c98d062c6b..ef09457327 100644
--- a/src/quick/items/qquicktextedit_p.h
+++ b/src/quick/items/qquicktextedit_p.h
@@ -22,11 +22,21 @@
QT_BEGIN_NAMESPACE
+class QTextDocument;
class QQuickTextDocument;
class QQuickTextEditPrivate;
+class QQuickTextSelection;
class QTextBlock;
-class Q_QUICK_PRIVATE_EXPORT QQuickTextEdit : public QQuickImplicitSizeItem, public QQuickTextInterface
+class QQuickTextBlockForeign
+{
+ Q_GADGET
+ QML_ANONYMOUS
+ QML_FOREIGN(QTextBlock)
+ QML_EXTENDED(QQuickTextBlockForeign)
+};
+
+class Q_QUICK_EXPORT QQuickTextEdit : public QQuickImplicitSizeItem, public QQuickTextInterface
{
Q_OBJECT
Q_INTERFACES(QQuickTextInterface)
@@ -78,6 +88,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickTextEdit : public QQuickImplicitSizeItem, pub
Q_PROPERTY(qreal bottomPadding READ bottomPadding WRITE setBottomPadding RESET resetBottomPadding NOTIFY bottomPaddingChanged REVISION(2, 6))
Q_PROPERTY(QString preeditText READ preeditText NOTIFY preeditTextChanged REVISION(2, 7))
Q_PROPERTY(qreal tabStopDistance READ tabStopDistance WRITE setTabStopDistance NOTIFY tabStopDistanceChanged REVISION(2, 10))
+ Q_PROPERTY(QQuickTextSelection* cursorSelection READ cursorSelection REVISION(6, 7) CONSTANT FINAL)
QML_NAMED_ELEMENT(TextEdit)
#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
QML_ADDED_IN_VERSION(6, 4)
@@ -85,6 +96,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickTextEdit : public QQuickImplicitSizeItem, pub
public:
QQuickTextEdit(QQuickItem *parent=nullptr);
+ ~QQuickTextEdit() override;
enum HAlignment {
AlignLeft = Qt::AlignLeft,
@@ -124,7 +136,8 @@ public:
Q_ENUM(SelectionMode)
enum RenderType { QtRendering,
- NativeRendering
+ NativeRendering,
+ CurveRendering
};
Q_ENUM(RenderType)
@@ -208,6 +221,8 @@ public:
void componentComplete() override;
+ int resourcesLoading() const; // mainly for testing
+
/* FROM EDIT */
void setReadOnly(bool);
bool isReadOnly() const;
@@ -231,6 +246,8 @@ public:
Q_INVOKABLE void moveCursorSelection(int pos);
Q_INVOKABLE void moveCursorSelection(int pos, SelectionMode mode);
+ QQuickTextSelection *cursorSelection() const;
+
QRectF boundingRect() const override;
QRectF clipRect() const override;
@@ -353,11 +370,15 @@ private Q_SLOTS:
void q_updateAlignment();
void updateSize();
void triggerPreprocess();
+ Q_REVISION(6, 7) QVariant loadResource(int type, const QUrl &source);
+ void resourceRequestFinished();
private:
void markDirtyNodesForRange(int start, int end, int charDelta);
void updateTotalLines();
void invalidateFontCaches();
+ QTextDocument* document() const;
+ void setDocument(QTextDocument *doc);
protected:
QQuickTextEdit(QQuickTextEditPrivate &dd, QQuickItem *parent = nullptr);
@@ -366,6 +387,7 @@ protected:
#endif
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
+ void itemChange(ItemChange change, const ItemChangeData &value) override;
bool event(QEvent *) override;
void keyPressEvent(QKeyEvent *) override;
@@ -389,7 +411,7 @@ protected:
void updatePolish() override;
friend class QQuickTextUtil;
- friend class QQuickTextDocument;
+ friend class QQuickTextDocumentPrivate;
private:
Q_DISABLE_COPY(QQuickTextEdit)
@@ -411,6 +433,4 @@ Q_DECLARE_MIXED_ENUM_OPERATORS_SYMMETRIC(int, QQuickTextEdit::HAlignment, QQuick
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickTextEdit)
-
#endif // QQUICKTEXTEDIT_P_H
diff --git a/src/quick/items/qquicktextedit_p_p.h b/src/quick/items/qquicktextedit_p_p.h
index db07462a5a..da01be47c2 100644
--- a/src/quick/items/qquicktextedit_p_p.h
+++ b/src/quick/items/qquicktextedit_p_p.h
@@ -19,20 +19,30 @@
#include "qquickimplicitsizeitem_p_p.h"
#include "qquicktextutil_p.h"
+#include <QtQuick/private/qquicktextselection_p.h>
+
#include <QtQml/qqml.h>
#include <QtCore/qlist.h>
#include <private/qlazilyallocated_p.h>
+#include <private/qquicktextdocument_p.h>
+
+#if QT_CONFIG(accessibility)
+#include <QtGui/qaccessible.h>
+#endif
#include <limits>
QT_BEGIN_NAMESPACE
class QTextLayout;
-class QQuickTextDocumentWithImageResources;
+class QQuickPixmap;
class QQuickTextControl;
-class QQuickTextNode;
+class QSGInternalTextNode;
class QQuickTextNodeEngine;
-class Q_QUICK_PRIVATE_EXPORT QQuickTextEditPrivate : public QQuickImplicitSizeItemPrivate
+class Q_QUICK_EXPORT QQuickTextEditPrivate : public QQuickImplicitSizeItemPrivate
+#if QT_CONFIG(accessibility)
+ , public QAccessible::ActivationObserver
+#endif
{
public:
Q_DECLARE_PUBLIC(QQuickTextEdit)
@@ -41,21 +51,21 @@ public:
struct Node {
explicit Node(int startPos = std::numeric_limits<int>::max(),
- QQuickTextNode *node = nullptr)
- : m_startPos(startPos), m_node(node), m_dirty(false) { }
- QQuickTextNode* textNode() const { return m_node; }
+ QSGInternalTextNode *node = nullptr)
+ : m_node(node), m_startPos(startPos) { }
+ QSGInternalTextNode *textNode() const { return m_node; }
void moveStartPos(int delta) { Q_ASSERT(m_startPos + delta > 0); m_startPos += delta; }
int startPos() const { return m_startPos; }
void setDirty() { m_dirty = true; }
bool dirty() const { return m_dirty; }
private:
+ QSGInternalTextNode *m_node;
int m_startPos;
- QQuickTextNode* m_node;
- bool m_dirty;
+ bool m_dirty = false;
#ifndef QT_NO_DEBUG_STREAM
- friend QDebug Q_QUICK_PRIVATE_EXPORT operator<<(QDebug, const Node &);
+ friend QDebug Q_QUICK_EXPORT operator<<(QDebug, const Node &);
#endif
};
typedef QList<Node>::iterator TextNodeIterator;
@@ -63,11 +73,11 @@ public:
struct ExtraData {
ExtraData();
- qreal padding;
- qreal topPadding;
- qreal leftPadding;
- qreal rightPadding;
- qreal bottomPadding;
+ qreal padding = 0;
+ qreal topPadding = 0;
+ qreal leftPadding = 0;
+ qreal rightPadding = 0;
+ qreal bottomPadding = 0;
bool explicitTopPadding : 1;
bool explicitLeftPadding : 1;
bool explicitRightPadding : 1;
@@ -78,25 +88,22 @@ public:
QQuickTextEditPrivate()
- : color(QRgb(0xFF000000)), selectionColor(QRgb(0xFF000080)), selectedTextColor(QRgb(0xFFFFFFFF))
- , textMargin(0.0), xoff(0), yoff(0)
- , font(sourceFont), cursorComponent(nullptr), cursorItem(nullptr), document(nullptr), control(nullptr)
- , quickDocument(nullptr), lastSelectionStart(0), lastSelectionEnd(0), lineCount(0)
- , hAlign(QQuickTextEdit::AlignLeft), vAlign(QQuickTextEdit::AlignTop)
- , format(QQuickTextEdit::PlainText), wrapMode(QQuickTextEdit::NoWrap)
- , renderType(QQuickTextUtil::textRenderType<QQuickTextEdit>())
- , contentDirection(Qt::LayoutDirectionAuto)
- , mouseSelectionMode(QQuickTextEdit::SelectCharacters)
-#if QT_CONFIG(im)
- , inputMethodHints(Qt::ImhNone)
-#endif
- , updateType(UpdatePaintNode)
- , dirty(false), richText(false), cursorVisible(false), cursorPending(false)
+ : dirty(false), richText(false), cursorVisible(false), cursorPending(false)
, focusOnPress(true), persistentSelection(false), requireImplicitWidth(false)
, selectByMouse(true), canPaste(false), canPasteValid(false), hAlignImplicit(true)
, textCached(true), inLayout(false), selectByKeyboard(false), selectByKeyboardSet(false)
- , hadSelection(false), markdownText(false)
+ , hadSelection(false), markdownText(false), inResize(false), ownsDocument(false)
{
+#if QT_CONFIG(accessibility)
+ QAccessible::installActivationObserver(this);
+#endif
+ }
+
+ ~QQuickTextEditPrivate()
+ {
+#if QT_CONFIG(accessibility)
+ QAccessible::removeActivationObserver(this);
+#endif
}
static QQuickTextEditPrivate *get(QQuickTextEdit *item) {
@@ -106,6 +113,7 @@ public:
void resetInputMethod();
void updateDefaultTextOption();
+ void onDocumentStatusChanged();
void relayoutDocument();
bool determineHorizontalAlignment();
bool setHAlign(QQuickTextEdit::HAlignment, bool forceAlign = false);
@@ -121,13 +129,18 @@ public:
void setNativeCursorEnabled(bool) {}
void handleFocusEvent(QFocusEvent *event);
- void addCurrentTextNodeToRoot(QQuickTextNodeEngine *, QSGTransformNode *, QQuickTextNode*, TextNodeIterator&, int startPos);
- QQuickTextNode* createTextNode();
+ void addCurrentTextNodeToRoot(QQuickTextNodeEngine *, QSGTransformNode *, QSGInternalTextNode *, TextNodeIterator&, int startPos);
+ QSGInternalTextNode* createTextNode();
#if QT_CONFIG(im)
Qt::InputMethodHints effectiveInputMethodHints() const;
#endif
+#if QT_CONFIG(accessibility)
+ void accessibilityActiveChanged(bool active) override;
+ QAccessible::Role accessibleRole() const override;
+#endif
+
inline qreal padding() const { return extra.isAllocated() ? extra->padding : 0.0; }
void setTopPadding(qreal value, bool reset = false);
void setLeftPadding(qreal value, bool reset = false);
@@ -137,33 +150,36 @@ public:
bool isImplicitResizeEnabled() const;
void setImplicitResizeEnabled(bool enabled);
- QColor color;
- QColor selectionColor;
- QColor selectedTextColor;
+ QColor color = QRgb(0xFF000000);
+ QColor selectionColor = QRgb(0xFF000080);
+ QColor selectedTextColor = QRgb(0xFFFFFFFF);
QSizeF contentSize;
- qreal textMargin;
- qreal xoff;
- qreal yoff;
+ qreal textMargin = 0;
+ qreal xoff = 0;
+ qreal yoff = 0;
QString text;
QUrl baseUrl;
QFont sourceFont;
QFont font;
- QQmlComponent* cursorComponent;
- QQuickItem* cursorItem;
- QQuickTextDocumentWithImageResources *document;
- QQuickTextControl *control;
- QQuickTextDocument *quickDocument;
+ QQmlComponent* cursorComponent = nullptr;
+ QQuickItem* cursorItem = nullptr;
+ QTextDocument *document = nullptr;
+ QQuickTextControl *control = nullptr;
+ QQuickTextDocument *quickDocument = nullptr;
+ mutable QQuickTextSelection *cursorSelection = nullptr;
QList<Node> textNodeMap;
+ QList<QQuickPixmap *> pixmapsInProgress;
- int lastSelectionStart;
- int lastSelectionEnd;
- int lineCount;
- int firstBlockInViewport = -1; // only for the autotest; can be wrong after scrolling sometimes
+ int lastSelectionStart = 0;
+ int lastSelectionEnd = 0;
+ int lineCount = 0;
+ int firstBlockInViewport = -1; // can be wrong after scrolling sometimes
int firstBlockPastViewport = -1; // only for the autotest
+ int renderedBlockCount = -1; // only for the autotest
QRectF renderedRegion;
enum UpdateType {
@@ -173,17 +189,17 @@ public:
UpdateAll
};
- QQuickTextEdit::HAlignment hAlign;
- QQuickTextEdit::VAlignment vAlign;
- QQuickTextEdit::TextFormat format;
- QQuickTextEdit::WrapMode wrapMode;
- QQuickTextEdit::RenderType renderType;
- Qt::LayoutDirection contentDirection;
- QQuickTextEdit::SelectionMode mouseSelectionMode;
+ QQuickTextEdit::HAlignment hAlign = QQuickTextEdit::AlignLeft;
+ QQuickTextEdit::VAlignment vAlign = QQuickTextEdit::AlignTop;
+ QQuickTextEdit::TextFormat format = QQuickTextEdit::PlainText;
+ QQuickTextEdit::WrapMode wrapMode = QQuickTextEdit::NoWrap;
+ QQuickTextEdit::RenderType renderType = QQuickTextUtil::textRenderType<QQuickTextEdit>();
+ Qt::LayoutDirection contentDirection = Qt::LayoutDirectionAuto;
+ QQuickTextEdit::SelectionMode mouseSelectionMode = QQuickTextEdit::SelectCharacters;
#if QT_CONFIG(im)
- Qt::InputMethodHints inputMethodHints;
+ Qt::InputMethodHints inputMethodHints = Qt::ImhNone;
#endif
- UpdateType updateType;
+ UpdateType updateType = UpdatePaintNode;
bool dirty : 1;
bool richText : 1;
@@ -202,12 +218,14 @@ public:
bool selectByKeyboardSet:1;
bool hadSelection : 1;
bool markdownText : 1;
+ bool inResize : 1;
+ bool ownsDocument : 1;
static const int largeTextSizeThreshold;
};
#ifndef QT_NO_DEBUG_STREAM
-QDebug Q_QUICK_PRIVATE_EXPORT operator<<(QDebug debug, const QQuickTextEditPrivate::Node &);
+QDebug Q_QUICK_EXPORT operator<<(QDebug debug, const QQuickTextEditPrivate::Node &);
#endif
QT_END_NAMESPACE
diff --git a/src/quick/items/qquicktextinput.cpp b/src/quick/items/qquicktextinput.cpp
index 1df764f79b..0826011a54 100644
--- a/src/quick/items/qquicktextinput.cpp
+++ b/src/quick/items/qquicktextinput.cpp
@@ -13,7 +13,7 @@
#include <QtQml/qqmlinfo.h>
#include <QtGui/qevent.h>
#include <QTextBoundaryFinder>
-#include "qquicktextnode_p.h"
+#include "qsginternaltextnode_p.h"
#include <QtQuick/qsgsimplerectnode.h>
#include <QtGui/qstylehints.h>
@@ -90,7 +90,13 @@ void QQuickTextInput::componentComplete()
The text in the TextInput.
- \sa clear()
+ Note that some keyboards use a predictive function. In this case,
+ the text being composed by the input method is not part of this property.
+ The part of the text related to the predictions is underlined and stored in
+ the \l preeditText property. To get whole text displayed in the TextInput
+ use \l displayText property.
+
+ \sa clear(), displayText, preeditText
*/
QString QQuickTextInput::text() const
{
@@ -127,16 +133,23 @@ void QQuickTextInput::setText(const QString &s)
Override the default rendering type for this component.
Supported render types are:
- \list
- \li Text.QtRendering
- \li Text.NativeRendering
- \endlist
- Select Text.NativeRendering if you prefer text to look native on the target platform and do
+ \value TextInput.QtRendering Text is rendered using a scalable distance field for each glyph.
+ \value TextInput.NativeRendering Text is rendered using a platform-specific technique.
+ \value TextInput.CurveRendering Text is rendered using a curve rasterizer running directly on
+ the graphics hardware. (Introduced in Qt 6.7.0.)
+
+ Select \c TextInput.NativeRendering if you prefer text to look native on the target platform and do
not require advanced features such as transformation of the text. Using such features in
combination with the NativeRendering render type will lend poor and sometimes pixelated
results.
+ Both \c TextInput.QtRendering and \c TextInput.CurveRendering are hardware-accelerated techniques.
+ \c QtRendering is the faster of the two, but uses more memory and will exhibit rendering
+ artifacts at large sizes. \c CurveRendering should be considered as an alternative in cases
+ where \c QtRendering does not give good visual results or where reducing graphics memory
+ consumption is a priority.
+
The default rendering type is determined by \l QQuickWindow::textRenderType().
*/
QQuickTextInput::RenderType QQuickTextInput::renderType() const
@@ -231,17 +244,16 @@ QString QQuickTextInputPrivate::realText() const
The requested weight of the font. The weight requested must be an integer
between 1 and 1000, or one of the predefined values:
- \list
- \li Font.Thin
- \li Font.Light
- \li Font.ExtraLight
- \li Font.Normal - the default
- \li Font.Medium
- \li Font.DemiBold
- \li Font.Bold
- \li Font.ExtraBold
- \li Font.Black
- \endlist
+
+ \value Font.Thin 100
+ \value Font.ExtraLight 200
+ \value Font.Light 300
+ \value Font.Normal 400 (default)
+ \value Font.Medium 500
+ \value Font.DemiBold 600
+ \value Font.Bold 700
+ \value Font.ExtraBold 800
+ \value Font.Black 900
\qml
TextInput { text: "Hello"; font.weight: Font.DemiBold }
@@ -305,13 +317,12 @@ QString QQuickTextInputPrivate::realText() const
Sets the capitalization for the text.
- \list
- \li Font.MixedCase - This is the normal text rendering option where no capitalization change is applied.
- \li Font.AllUppercase - This alters the text to be rendered in all uppercase type.
- \li Font.AllLowercase - This alters the text to be rendered in all lowercase type.
- \li Font.SmallCaps - This alters the text to be rendered in small-caps type.
- \li Font.Capitalize - This alters the text to be rendered with the first character of each word as an uppercase character.
- \endlist
+ \value Font.MixedCase the normal case: no capitalization change is applied
+ \value Font.AllUppercase alters the text to be rendered in all uppercase type
+ \value Font.AllLowercase alters the text to be rendered in all lowercase type
+ \value Font.SmallCaps alters the text to be rendered in small-caps type
+ \value Font.Capitalize alters the text to be rendered with the first character of
+ each word as an uppercase character
\qml
TextInput { text: "Hello"; font.capitalization: Font.AllLowercase }
@@ -328,23 +339,21 @@ QString QQuickTextInputPrivate::realText() const
\note This property only has an effect when used together with render type TextInput.NativeRendering.
- \list
- \value Font.PreferDefaultHinting - Use the default hinting level for the target platform.
- \value Font.PreferNoHinting - If possible, render text without hinting the outlines
+ \value Font.PreferDefaultHinting Use the default hinting level for the target platform.
+ \value Font.PreferNoHinting If possible, render text without hinting the outlines
of the glyphs. The text layout will be typographically accurate, using the same metrics
as are used e.g. when printing.
- \value Font.PreferVerticalHinting - If possible, render text with no horizontal hinting,
+ \value Font.PreferVerticalHinting If possible, render text with no horizontal hinting,
but align glyphs to the pixel grid in the vertical direction. The text will appear
crisper on displays where the density is too low to give an accurate rendering
of the glyphs. But since the horizontal metrics of the glyphs are unhinted, the text's
layout will be scalable to higher density devices (such as printers) without impacting
details such as line breaks.
- \value Font.PreferFullHinting - If possible, render text with hinting in both horizontal and
+ \value Font.PreferFullHinting If possible, render text with hinting in both horizontal and
vertical directions. The text will be altered to optimize legibility on the target
device, but since the metrics will depend on the target size of the text, the positions
of glyphs, line breaks, and other typographical detail will not scale, meaning that a
text layout may look different on devices with different pixel densities.
- \endlist
\qml
TextInput { text: "Hello"; renderType: TextInput.NativeRendering; font.hintingPreference: Font.PreferVerticalHinting }
@@ -380,6 +389,34 @@ QString QQuickTextInputPrivate::realText() const
TextInput { text: "Some text"; font.preferShaping: false }
\endqml
*/
+
+/*!
+ \qmlproperty object QtQuick::TextInput::font.variableAxes
+ \since 6.7
+
+ \include qquicktext.cpp qml-font-variable-axes
+*/
+
+/*!
+ \qmlproperty object QtQuick::TextInput::font.features
+ \since 6.6
+
+ \include qquicktext.cpp qml-font-features
+*/
+
+/*!
+ \qmlproperty bool QtQuick::TextInput::font.contextFontMerging
+ \since 6.8
+
+ \include qquicktext.cpp qml-font-context-font-merging
+*/
+
+/*!
+ \qmlproperty bool QtQuick::TextInput::font.preferTypoLineMetrics
+ \since 6.8
+
+ \include qquicktext.cpp qml-font-prefer-typo-line-metrics
+*/
QFont QQuickTextInput::font() const
{
Q_D(const QQuickTextInput);
@@ -532,9 +569,8 @@ QQuickTextInput::HAlignment QQuickTextInput::hAlign() const
void QQuickTextInput::setHAlign(HAlignment align)
{
Q_D(QQuickTextInput);
- bool forceAlign = d->hAlignImplicit && d->effectiveLayoutMirror;
- d->hAlignImplicit = false;
- if (d->setHAlign(align, forceAlign) && isComponentComplete()) {
+
+ if (d->setHAlign(align, true) && isComponentComplete()) {
d->updateLayout();
updateCursorRectangle();
}
@@ -569,17 +605,34 @@ QQuickTextInput::HAlignment QQuickTextInput::effectiveHAlign() const
return effectiveAlignment;
}
-bool QQuickTextInputPrivate::setHAlign(QQuickTextInput::HAlignment alignment, bool forceAlign)
+bool QQuickTextInputPrivate::setHAlign(QQuickTextInput::HAlignment align, bool forceAlign)
{
Q_Q(QQuickTextInput);
- if ((hAlign != alignment || forceAlign) && alignment <= QQuickTextInput::AlignHCenter) { // justify not supported
- QQuickTextInput::HAlignment oldEffectiveHAlign = q->effectiveHAlign();
- hAlign = alignment;
- emit q->horizontalAlignmentChanged(alignment);
- if (oldEffectiveHAlign != q->effectiveHAlign())
- emit q->effectiveHorizontalAlignmentChanged();
+ if (align > QQuickTextInput::AlignHCenter)
+ return false; // justify is not supported
+
+ if (hAlign == align && !forceAlign)
+ return false;
+
+ const bool wasImplicit = hAlignImplicit;
+ const auto oldEffectiveHAlign = q->effectiveHAlign();
+
+ hAlignImplicit = !forceAlign;
+ if (hAlign != align) {
+ hAlign = align;
+ emit q->horizontalAlignmentChanged(align);
+ }
+
+ if (q->effectiveHAlign() != oldEffectiveHAlign) {
+ emit q->effectiveHorizontalAlignmentChanged();
return true;
}
+
+ if (forceAlign && wasImplicit) {
+ // QTBUG-120052 - when horizontal text alignment is set explicitly,
+ // we need notify any other controls that may depend on it, like QQuickPlaceholderText
+ emit q->effectiveHorizontalAlignmentChanged();
+ }
return false;
}
@@ -623,16 +676,19 @@ Qt::LayoutDirection QQuickTextInputPrivate::layoutDirection() const
bool QQuickTextInputPrivate::determineHorizontalAlignment()
{
- if (hAlignImplicit) {
- // if no explicit alignment has been set, follow the natural layout direction of the text
- Qt::LayoutDirection direction = textDirection();
+ if (!hAlignImplicit)
+ return false;
+
+ // if no explicit alignment has been set, follow the natural layout direction of the text
+ Qt::LayoutDirection direction = textDirection();
#if QT_CONFIG(im)
- if (direction == Qt::LayoutDirectionAuto)
- direction = QGuiApplication::inputMethod()->inputDirection();
+ if (direction == Qt::LayoutDirectionAuto)
+ direction = QGuiApplication::inputMethod()->inputDirection();
#endif
- return setHAlign(direction == Qt::RightToLeft ? QQuickTextInput::AlignRight : QQuickTextInput::AlignLeft);
- }
- return false;
+
+ const auto implicitHAlign = direction == Qt::RightToLeft ?
+ QQuickTextInput::AlignRight : QQuickTextInput::AlignLeft;
+ return setHAlign(implicitHAlign);
}
QQuickTextInput::VAlignment QQuickTextInput::vAlign() const
@@ -660,12 +716,17 @@ void QQuickTextInput::setVAlign(QQuickTextInput::VAlignment alignment)
Set this property to wrap the text to the TextInput item's width.
The text will only wrap if an explicit width has been set.
- \list
- \li TextInput.NoWrap - no wrapping will be performed. If the text contains insufficient newlines, then implicitWidth will exceed a set width.
- \li TextInput.WordWrap - wrapping is done on word boundaries only. If a word is too long, implicitWidth will exceed a set width.
- \li TextInput.WrapAnywhere - wrapping is done at any point on a line, even if it occurs in the middle of a word.
- \li TextInput.Wrap - if possible, wrapping occurs at a word boundary; otherwise it will occur at the appropriate point on the line, even in the middle of a word.
- \endlist
+ \value TextInput.NoWrap
+ (default) no wrapping will be performed. If the text contains
+ insufficient newlines, then \l contentWidth will exceed a set width.
+ \value TextInput.WordWrap
+ wrapping is done on word boundaries only. If a word is too long,
+ \l contentWidth will exceed a set width.
+ \value TextInput.WrapAnywhere
+ wrapping is done at any point on a line, even if it occurs in the middle of a word.
+ \value TextInput.Wrap
+ if possible, wrapping occurs at a word boundary; otherwise it will occur
+ at the appropriate point on the line, even in the middle of a word.
The default is TextInput.NoWrap. If you set a width, consider using TextInput.Wrap.
*/
@@ -1199,14 +1260,13 @@ Qt::InputMethodHints QQuickTextInputPrivate::effectiveInputMethodHints() const
\qmlproperty enumeration QtQuick::TextInput::echoMode
Specifies how the text should be displayed in the TextInput.
- \list
- \li TextInput.Normal - Displays the text as it is. (Default)
- \li TextInput.Password - Displays platform-dependent password mask
- characters instead of the actual characters.
- \li TextInput.NoEcho - Displays nothing.
- \li TextInput.PasswordEchoOnEdit - Displays characters as they are entered
- while editing, otherwise identical to \c TextInput.Password.
- \endlist
+
+ \value TextInput.Normal Displays the text as it is. (Default)
+ \value TextInput.Password Displays platform-dependent password mask
+ characters instead of the actual characters.
+ \value TextInput.NoEcho Displays nothing.
+ \value TextInput.PasswordEchoOnEdit Displays characters as they are entered
+ while editing, otherwise identical to \c TextInput.Password.
*/
QQuickTextInput::EchoMode QQuickTextInput::echoMode() const
{
@@ -1247,40 +1307,31 @@ void QQuickTextInput::setEchoMode(QQuickTextInput::EchoMode echo)
Flags that alter behaviour are:
- \list
- \li Qt.ImhHiddenText - Characters should be hidden, as is typically used when entering passwords.
- This is automatically set when setting echoMode to \c TextInput.Password.
- \li Qt.ImhSensitiveData - Typed text should not be stored by the active input method
- in any persistent storage like predictive user dictionary.
- \li Qt.ImhNoAutoUppercase - The input method should not try to automatically switch to upper case
- when a sentence ends.
- \li Qt.ImhPreferNumbers - Numbers are preferred (but not required).
- \li Qt.ImhPreferUppercase - Upper case letters are preferred (but not required).
- \li Qt.ImhPreferLowercase - Lower case letters are preferred (but not required).
- \li Qt.ImhNoPredictiveText - Do not use predictive text (i.e. dictionary lookup) while typing.
-
- \li Qt.ImhDate - The text editor functions as a date field.
- \li Qt.ImhTime - The text editor functions as a time field.
- \li Qt.ImhMultiLine - The text editor doesn't close software input keyboard when Return or Enter key is pressed (since QtQuick 2.4).
- \endlist
+ \value Qt.ImhHiddenText Characters should be hidden, as is typically used when entering passwords.
+ \value Qt.ImhSensitiveData Typed text should not be stored by the active input method
+ in any persistent storage like predictive user dictionary.
+ \value Qt.ImhNoAutoUppercase The input method should not try to automatically switch to
+ upper case when a sentence ends.
+ \value Qt.ImhPreferNumbers Numbers are preferred (but not required).
+ \value Qt.ImhPreferUppercase Upper case letters are preferred (but not required).
+ \value Qt.ImhPreferLowercase Lower case letters are preferred (but not required).
+ \value Qt.ImhNoPredictiveText Do not use predictive text (i.e. dictionary lookup) while typing.
+ \value Qt.ImhDate The text editor functions as a date field.
+ \value Qt.ImhTime The text editor functions as a time field.
Flags that restrict input (exclusive flags) are:
- \list
- \li Qt.ImhDigitsOnly - Only digits are allowed.
- \li Qt.ImhFormattedNumbersOnly - Only number input is allowed. This includes decimal point and minus sign.
- \li Qt.ImhUppercaseOnly - Only upper case letter input is allowed.
- \li Qt.ImhLowercaseOnly - Only lower case letter input is allowed.
- \li Qt.ImhDialableCharactersOnly - Only characters suitable for phone dialing are allowed.
- \li Qt.ImhEmailCharactersOnly - Only characters suitable for email addresses are allowed.
- \li Qt.ImhUrlCharactersOnly - Only characters suitable for URLs are allowed.
- \endlist
+ \value Qt.ImhDigitsOnly Only digits are allowed.
+ \value Qt.ImhFormattedNumbersOnly Only number input is allowed. This includes decimal point and minus sign.
+ \value Qt.ImhUppercaseOnly Only upper case letter input is allowed.
+ \value Qt.ImhLowercaseOnly Only lower case letter input is allowed.
+ \value Qt.ImhDialableCharactersOnly Only characters suitable for phone dialing are allowed.
+ \value Qt.ImhEmailCharactersOnly Only characters suitable for email addresses are allowed.
+ \value Qt.ImhUrlCharactersOnly Only characters suitable for URLs are allowed.
Masks:
- \list
- \li Qt.ImhExclusiveInputMask - This mask yields nonzero if any of the exclusive flags are used.
- \endlist
+ \value Qt.ImhExclusiveInputMask This mask yields nonzero if any of the exclusive flags are used.
*/
Qt::InputMethodHints QQuickTextInput::inputMethodHints() const
@@ -1397,7 +1448,7 @@ QRectF QQuickTextInput::positionToRectangle(int pos) const
Returns the position before the character that is nearest x.
*/
-void QQuickTextInput::positionAt(QQmlV4Function *args) const
+void QQuickTextInput::positionAt(QQmlV4FunctionPtr args) const
{
Q_D(const QQuickTextInput);
@@ -1571,7 +1622,11 @@ void QQuickTextInput::mousePressEvent(QMouseEvent *event)
}
}
- if (isMouse) {
+ if (isMouse
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
+ || d->selectByTouchDrag
+#endif
+ ) {
bool mark = (event->modifiers() & Qt::ShiftModifier) && d->selectByMouse;
int cursor = d->positionAt(event->position());
d->moveCursor(cursor, mark);
@@ -1640,9 +1695,11 @@ void QQuickTextInput::mouseReleaseEvent(QMouseEvent *event)
}
}
#endif
+
// On a touchscreen or with a stylus, set cursor position and focus on release, not on press;
// if Flickable steals the grab in the meantime, the cursor won't move.
- if (!isMouse)
+ // Check d->hasSelectedText() to keep touch-and-hold word selection working.
+ if (!isMouse && !d->hasSelectedText())
d->moveCursor(d->positionAt(event->position()), false);
if (d->focusOnPress && qGuiApp->styleHints()->setFocusOnTouchRelease())
@@ -1751,6 +1808,26 @@ void QQuickTextInput::geometryChange(const QRectF &newGeometry,
QQuickImplicitSizeItem::geometryChange(newGeometry, oldGeometry);
}
+void QQuickTextInput::itemChange(ItemChange change, const ItemChangeData &value)
+{
+ Q_D(QQuickTextInput);
+ Q_UNUSED(value);
+ switch (change) {
+ case ItemDevicePixelRatioHasChanged:
+ if (d->renderType == NativeRendering) {
+ // Native rendering optimizes for a given pixel grid, so its results must not be scaled.
+ // Text layout code respects the current device pixel ratio automatically, we only need
+ // to rerun layout after the ratio changed.
+ d->updateLayout();
+ }
+ break;
+
+ default:
+ break;
+ }
+ QQuickImplicitSizeItem::itemChange(change, value);
+}
+
void QQuickTextInputPrivate::ensureVisible(int position, int preeditCursor, int preeditLength)
{
Q_Q(QQuickTextInput);
@@ -1911,9 +1988,9 @@ QSGNode *QQuickTextInput::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData
d->updateType = QQuickTextInputPrivate::UpdateNone;
- QQuickTextNode *node = static_cast<QQuickTextNode *>(oldNode);
+ QSGInternalTextNode *node = static_cast<QSGInternalTextNode *>(oldNode);
if (node == nullptr)
- node = new QQuickTextNode(this);
+ node = d->sceneGraphContext()->createInternalTextNode(d->sceneGraphRenderContext());
d->textNode = node;
const bool showCursor = !isReadOnly() && d->cursorItem == nullptr && d->cursorVisible && d->m_blinkStatus;
@@ -1924,9 +2001,19 @@ QSGNode *QQuickTextInput::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData
else
node->clearCursor();
} else {
- node->setUseNativeRenderer(d->renderType == NativeRendering);
- node->deleteContent();
+ node->setRenderType(QSGTextNode::RenderType(d->renderType));
+ node->clear();
node->setMatrix(QMatrix4x4());
+ node->setTextStyle(QSGInternalTextNode::Normal);
+ node->setColor(d->color);
+ node->setSelectionTextColor(d->selectedTextColor);
+ node->setSelectionColor(d->selectionColor);
+ node->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
+
+ if (flags().testFlag(ItemObservesViewport))
+ node->setViewport(clipRect());
+ else
+ node->setViewport(QRectF{});
QPointF offset(leftPadding(), topPadding());
if (d->autoScroll && d->m_textLayout.lineCount() > 0) {
@@ -1942,16 +2029,14 @@ QSGNode *QQuickTextInput::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData
|| !d->m_textLayout.preeditAreaText().isEmpty()
#endif
) {
- node->addTextLayout(offset, &d->m_textLayout, d->color,
- QQuickText::Normal, QColor(), QColor(),
- d->selectionColor, d->selectedTextColor,
+ node->addTextLayout(offset, &d->m_textLayout,
d->selectionStart(),
d->selectionEnd() - 1); // selectionEnd() returns first char after
- // selection
+ // selection
}
if (showCursor)
- node->setCursor(cursorRectangle(), d->color);
+ node->setCursor(cursorRectangle(), d->color);
d->textLayoutDirty = false;
}
@@ -1974,8 +2059,14 @@ QVariant QQuickTextInput::inputMethodQuery(Qt::InputMethodQuery property) const
|| d->extra->enterKeyAttached->type() == Qt::EnterKeyDefault) {
QQuickItem *next = const_cast<QQuickTextInput*>(this)->nextItemInFocusChain();
- while (next && next != this && !next->activeFocusOnTab())
+ QQuickItem *originalNext = next;
+ while (next && next != this && !next->activeFocusOnTab()) {
next = next->nextItemInFocusChain();
+ if (next == originalNext) {
+ // There seems to be no suitable element in the focus chain
+ next = nullptr;
+ }
+ }
if (next) {
const auto nextYPos = next->mapToGlobal(QPoint(0, 0)).y();
const auto currentYPos = this->mapToGlobal(QPoint(0, 0)).y();
@@ -2411,7 +2502,10 @@ QString QQuickTextInput::displayText() const
This property contains partial text input from an input method.
- \sa displayText
+ To turn off partial text that results from predictions, set the \c Qt.ImhNoPredictiveText
+ flag in inputMethodHints.
+
+ \sa displayText, inputMethodHints
*/
QString QQuickTextInput::preeditText() const
{
@@ -2457,10 +2551,8 @@ void QQuickTextInput::setSelectByMouse(bool on)
Specifies how text should be selected using a mouse.
- \list
- \li TextInput.SelectCharacters - The selection is updated with individual characters. (Default)
- \li TextInput.SelectWords - The selection is updated with whole words.
- \endlist
+ \value TextInput.SelectCharacters (default) The selection is updated with individual characters.
+ \value TextInput.SelectWords The selection is updated with whole words.
This property only applies when \l selectByMouse is true.
*/
@@ -2515,7 +2607,7 @@ bool QQuickTextInput::canPaste() const
Q_D(const QQuickTextInput);
if (!d->canPasteValid) {
if (const QMimeData *mimeData = QGuiApplication::clipboard()->mimeData())
- const_cast<QQuickTextInputPrivate *>(d)->canPaste = !d->m_readOnly && mimeData->hasText();
+ const_cast<QQuickTextInputPrivate *>(d)->canPaste = !d->m_readOnly && mimeData->hasText() && !mimeData->text().isEmpty();
const_cast<QQuickTextInputPrivate *>(d)->canPasteValid = true;
}
return d->canPaste;
@@ -2600,13 +2692,12 @@ void QQuickTextInput::moveCursorSelection(int position)
The selection mode specifies whether the selection is updated on a per character or a per word
basis. If not specified the selection mode will default to \c {TextInput.SelectCharacters}.
- \list
- \li TextInput.SelectCharacters - Sets either the selectionStart or selectionEnd (whichever was at
- the previous cursor position) to the specified position.
- \li TextInput.SelectWords - Sets the selectionStart and selectionEnd to include all
- words between the specified position and the previous cursor position. Words partially in the
- range are included.
- \endlist
+ \value TextInput.SelectCharacters
+ Sets either the selectionStart or selectionEnd (whichever was at the previous cursor position)
+ to the specified position.
+ \value TextInput.SelectWords
+ Sets the selectionStart and selectionEnd to include all words between the specified position
+ and the previous cursor position. Words partially in the range are included.
For example, take this sequence of calls:
@@ -2796,6 +2887,7 @@ void QQuickTextInputPrivate::init()
}
m_inputControl = new QInputControl(QInputControl::LineEdit, q);
+ setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Fixed);
}
void QQuickTextInputPrivate::cancelInput()
@@ -3526,6 +3618,7 @@ void QQuickTextInputPrivate::processInputMethodEvent(QInputMethodEvent *event)
}
selectionChange = true;
} else {
+ selectionChange = m_selstart != m_selend;
m_selstart = m_selend = 0;
}
cursorPositionChanged = true;
@@ -3604,7 +3697,7 @@ void QQuickTextInputPrivate::selectWordAtPos(int cursor)
moveCursor(c, false);
// ## text layout should support end of words.
int end = m_textLayout.nextCursorPosition(c, QTextLayout::SkipWords);
- while (end > cursor && m_text[end-1].isSpace())
+ while (end > cursor && m_text.at(end - 1).isSpace())
--end;
moveCursor(end, true);
}
diff --git a/src/quick/items/qquicktextinput_p.h b/src/quick/items/qquicktextinput_p.h
index 433eed4d78..5212e6117e 100644
--- a/src/quick/items/qquicktextinput_p.h
+++ b/src/quick/items/qquicktextinput_p.h
@@ -25,7 +25,7 @@
QT_BEGIN_NAMESPACE
class QQuickTextInputPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickTextInput : public QQuickImplicitSizeItem, public QQuickTextInterface
+class Q_QUICK_EXPORT QQuickTextInput : public QQuickImplicitSizeItem, public QQuickTextInterface
{
Q_OBJECT
Q_INTERFACES(QQuickTextInterface)
@@ -138,12 +138,13 @@ public:
Q_ENUM(CursorPosition)
enum RenderType { QtRendering,
- NativeRendering
+ NativeRendering,
+ CurveRendering
};
Q_ENUM(RenderType)
//Auxilliary functions needed to control the TextInput from QML
- Q_INVOKABLE void positionAt(QQmlV4Function *args) const;
+ Q_INVOKABLE void positionAt(QQmlV4FunctionPtr args) const;
Q_INVOKABLE QRectF positionToRectangle(int pos) const;
Q_INVOKABLE void moveCursorSelection(int pos);
Q_INVOKABLE void moveCursorSelection(int pos, SelectionMode mode);
@@ -349,6 +350,7 @@ protected:
#endif
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
+ void itemChange(ItemChange change, const ItemChangeData &value) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
@@ -415,6 +417,4 @@ public:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickTextInput)
-
#endif // QQUICKTEXTINPUT_P_H
diff --git a/src/quick/items/qquicktextinput_p_p.h b/src/quick/items/qquicktextinput_p_p.h
index 01d90f6b10..6bc83d318b 100644
--- a/src/quick/items/qquicktextinput_p_p.h
+++ b/src/quick/items/qquicktextinput_p_p.h
@@ -36,10 +36,10 @@
QT_BEGIN_NAMESPACE
-class QQuickTextNode;
+class QSGInternalTextNode;
class QInputControl;
-class Q_QUICK_PRIVATE_EXPORT QQuickTextInputPrivate : public QQuickImplicitSizeItemPrivate
+class Q_QUICK_EXPORT QQuickTextInputPrivate : public QQuickImplicitSizeItemPrivate
{
public:
Q_DECLARE_PUBLIC(QQuickTextInput)
@@ -199,7 +199,7 @@ public:
QFont sourceFont;
QQuickItem *cursorItem;
- QQuickTextNode *textNode;
+ QSGInternalTextNode *textNode;
std::unique_ptr<MaskInputData[]> m_maskData;
QInputControl *m_inputControl;
diff --git a/src/quick/items/qquicktextinterface_p.h b/src/quick/items/qquicktextinterface_p.h
index 4210c6c0ad..3a8a31be10 100644
--- a/src/quick/items/qquicktextinterface_p.h
+++ b/src/quick/items/qquicktextinterface_p.h
@@ -17,7 +17,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QQuickTextInterface
+class Q_QUICK_EXPORT QQuickTextInterface
{
public:
virtual void invalidate() = 0;
diff --git a/src/quick/items/qquicktextnode_p.h b/src/quick/items/qquicktextnode_p.h
deleted file mode 100644
index 0538336311..0000000000
--- a/src/quick/items/qquicktextnode_p.h
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#ifndef QQUICKTEXTNODE_P_H
-#define QQUICKTEXTNODE_P_H
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists purely as an
-// implementation detail. This header file may change from version to
-// version without notice, or even be removed.
-//
-// We mean it.
-//
-
-#include <QtQuick/qsgnode.h>
-#include "qquicktext_p.h"
-#include <qglyphrun.h>
-
-#include <QtGui/qcolor.h>
-#include <QtGui/qtextlayout.h>
-#include <QtCore/qvarlengtharray.h>
-#include <QtCore/qscopedpointer.h>
-
-QT_BEGIN_NAMESPACE
-
-class QSGGlyphNode;
-class QTextBlock;
-class QColor;
-class QTextDocument;
-class QSGContext;
-class QRawFont;
-class QSGInternalRectangleNode;
-class QSGClipNode;
-class QSGTexture;
-
-class QQuickTextNodeEngine;
-
-class Q_QUICK_PRIVATE_EXPORT QQuickTextNode : public QSGTransformNode
-{
-public:
- QQuickTextNode(QQuickItem *ownerElement);
- ~QQuickTextNode();
-
- static bool isComplexRichText(QTextDocument *);
-
- void deleteContent();
- void addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color = QColor(),
- QQuickText::TextStyle style = QQuickText::Normal, const QColor &styleColor = QColor(),
- const QColor &anchorColor = QColor(),
- const QColor &selectionColor = QColor(), const QColor &selectedTextColor = QColor(),
- int selectionStart = -1, int selectionEnd = -1,
- int lineStart = 0, int lineCount = -1);
- void addTextDocument(const QPointF &position, QTextDocument *textDocument, const QColor &color = QColor(),
- QQuickText::TextStyle style = QQuickText::Normal, const QColor &styleColor = QColor(),
- const QColor &anchorColor = QColor(),
- const QColor &selectionColor = QColor(), const QColor &selectedTextColor = QColor(),
- int selectionStart = -1, int selectionEnd = -1);
-
- void setCursor(const QRectF &rect, const QColor &color);
- void clearCursor();
- QSGInternalRectangleNode *cursorNode() const { return m_cursorNode; }
-
- QSGGlyphNode *addGlyphs(const QPointF &position, const QGlyphRun &glyphs, const QColor &color,
- QQuickText::TextStyle style = QQuickText::Normal, const QColor &styleColor = QColor(),
- QSGNode *parentNode = 0);
- void addImage(const QRectF &rect, const QImage &image);
- void addRectangleNode(const QRectF &rect, const QColor &color);
-
- bool useNativeRenderer() const { return m_useNativeRenderer; }
- void setUseNativeRenderer(bool on) { m_useNativeRenderer = on; }
-
- void setRenderTypeQuality(int renderTypeQuality) { m_renderTypeQuality = renderTypeQuality; }
- int renderTypeQuality() const { return m_renderTypeQuality; }
-
- QPair<int, int> renderedLineRange() const { return { m_firstLineInViewport, m_firstLinePastViewport }; }
-
-private:
- QSGInternalRectangleNode *m_cursorNode;
- QList<QSGTexture *> m_textures;
- QQuickItem *m_ownerElement;
- bool m_useNativeRenderer;
- int m_renderTypeQuality;
- int m_firstLineInViewport = -1;
- int m_firstLinePastViewport = -1;
-
- friend class QQuickTextEdit;
- friend class QQuickTextEditPrivate;
-};
-
-QT_END_NAMESPACE
-
-#endif // QQUICKTEXTNODE_P_H
diff --git a/src/quick/items/qquicktextnodeengine.cpp b/src/quick/items/qquicktextnodeengine.cpp
index d2256128cf..d6d31b4621 100644
--- a/src/quick/items/qquicktextnodeengine.cpp
+++ b/src/quick/items/qquicktextnodeengine.cpp
@@ -13,17 +13,15 @@
#include <QtGui/qtextlist.h>
#include <private/qquicktext_p.h>
-#include <private/qquicktextdocument_p.h>
#include <private/qtextdocumentlayout_p.h>
#include <private/qtextimagehandler_p.h>
#include <private/qrawfont_p.h>
#include <private/qglyphrun_p.h>
#include <private/qquickitem_p.h>
+#include <private/qsgdistancefieldglyphnode_p.h>
QT_BEGIN_NAMESPACE
-Q_DECLARE_LOGGING_CATEGORY(lcSgText)
-
QQuickTextNodeEngine::BinaryTreeNodeKey::BinaryTreeNodeKey(BinaryTreeNode *node)
: fontEngine(QRawFontPrivate::get(node->glyphRun.rawFont())->fontEngine)
, clipNode(node->clipNode)
@@ -429,15 +427,8 @@ void QQuickTextNodeEngine::addTextObject(const QTextBlock &block, const QPointF
if (format.objectType() == QTextFormat::ImageObject) {
QTextImageFormat imageFormat = format.toImageFormat();
- if (QQuickTextDocumentWithImageResources *imageDoc = qobject_cast<QQuickTextDocumentWithImageResources *>(textDocument)) {
- image = imageDoc->image(imageFormat);
-
- if (image.isNull())
- return;
- } else {
- QTextImageHandler *imageHandler = static_cast<QTextImageHandler *>(handler);
- image = imageHandler->image(textDocument, imageFormat);
- }
+ QTextImageHandler *imageHandler = static_cast<QTextImageHandler *>(handler);
+ image = imageHandler->image(textDocument, imageFormat);
}
if (image.isNull()) {
@@ -668,10 +659,14 @@ void QQuickTextNodeEngine::addFrameDecorations(QTextDocument *document, QTextFra
if (borderStyle == QTextFrameFormat::BorderStyle_None)
return;
- addBorder(boundingRect.adjusted(frameFormat.leftMargin(), frameFormat.topMargin(),
- -frameFormat.rightMargin() - borderWidth,
- -frameFormat.bottomMargin() - borderWidth),
- borderWidth, borderStyle, borderBrush);
+ const auto collapsed = table->format().borderCollapse();
+
+ if (!collapsed) {
+ addBorder(boundingRect.adjusted(frameFormat.leftMargin(), frameFormat.topMargin(),
+ -frameFormat.rightMargin() - borderWidth,
+ -frameFormat.bottomMargin() - borderWidth),
+ borderWidth, borderStyle, borderBrush);
+ }
if (table != nullptr) {
int rows = table->rows();
int columns = table->columns();
@@ -681,7 +676,7 @@ void QQuickTextNodeEngine::addFrameDecorations(QTextDocument *document, QTextFra
QTextTableCell cell = table->cellAt(row, column);
QRectF cellRect = documentLayout->tableCellBoundingRect(table, cell);
- addBorder(cellRect.adjusted(-borderWidth, -borderWidth, 0, 0), borderWidth,
+ addBorder(cellRect.adjusted(-borderWidth, -borderWidth, collapsed ? -borderWidth : 0, collapsed ? -borderWidth : 0), borderWidth,
borderStyle, borderBrush);
}
}
@@ -702,6 +697,9 @@ void QQuickTextNodeEngine::mergeProcessedNodes(QList<BinaryTreeNode *> *regularN
BinaryTreeNode *node = m_processedNodes.data() + i;
if (node->image.isNull()) {
+ if (node->glyphRun.isEmpty())
+ continue;
+
BinaryTreeNodeKey key(node);
QList<BinaryTreeNode *> &nodes = map[key];
@@ -756,7 +754,7 @@ void QQuickTextNodeEngine::mergeProcessedNodes(QList<BinaryTreeNode *> *regularN
}
}
-void QQuickTextNodeEngine::addToSceneGraph(QQuickTextNode *parentNode,
+void QQuickTextNodeEngine::addToSceneGraph(QSGInternalTextNode *parentNode,
QQuickText::TextStyle style,
const QColor &styleColor)
{
@@ -801,7 +799,7 @@ void QQuickTextNodeEngine::addToSceneGraph(QQuickTextNode *parentNode,
? m_selectedTextColor
: textDecoration.color;
- parentNode->addRectangleNode(textDecoration.rect, color);
+ parentNode->addDecorationNode(textDecoration.rect, color);
}
// Finally add the selected text on top of everything
@@ -1040,6 +1038,12 @@ void QQuickTextNodeEngine::addTextBlock(QTextDocument *textDocument, const QText
line.setPosition(QPointF(0, 0));
layout.endLayout();
+ // set the color for the bullets, instead of using the previous QTextBlock's color.
+ if (charFormat.foreground().style() == Qt::NoBrush)
+ setTextColor(textColor);
+ else
+ setTextColor(charFormat.foreground().color());
+
QList<QGlyphRun> glyphRuns = layout.glyphRuns();
for (int i=0; i<glyphRuns.size(); ++i)
addUnselectedGlyphs(glyphRuns.at(i));
@@ -1068,7 +1072,7 @@ void QQuickTextNodeEngine::addTextBlock(QTextDocument *textDocument, const QText
else
setPosition(blockBoundingRect.topLeft());
- if (text.contains(QChar::ObjectReplacementCharacter)) {
+ if (text.contains(QChar::ObjectReplacementCharacter) && charFormat.objectType() != QTextFormat::NoObject) {
QTextFrame *frame = qobject_cast<QTextFrame *>(textDocument->objectForFormat(charFormat));
if (!frame || frame->frameFormat().position() == QTextFrameFormat::InFlow) {
int blockRelativePosition = textPos - block.position();
diff --git a/src/quick/items/qquicktextnodeengine_p.h b/src/quick/items/qquicktextnodeengine_p.h
index 1fbbbc2d93..1ed98ce208 100644
--- a/src/quick/items/qquicktextnodeengine_p.h
+++ b/src/quick/items/qquicktextnodeengine_p.h
@@ -9,7 +9,7 @@
#include <QtGui/qtextdocument.h>
#include <QtGui/qtextlayout.h>
#include "qquickclipnode_p.h"
-#include "qquicktextnode_p.h"
+#include "qsginternaltextnode_p.h"
#ifndef QQUICKTEXTNODEENGINE_P_H
#define QQUICKTEXTNODEENGINE_P_H
@@ -160,7 +160,7 @@ public:
void mergeProcessedNodes(QList<BinaryTreeNode *> *regularNodes,
QList<BinaryTreeNode *> *imageNodes);
- void addToSceneGraph(QQuickTextNode *parent,
+ void addToSceneGraph(QSGInternalTextNode *parent,
QQuickText::TextStyle style = QQuickText::Normal,
const QColor &styleColor = QColor());
@@ -191,7 +191,6 @@ public:
-
private:
struct TextDecoration
{
@@ -234,7 +233,7 @@ private:
bool m_hasSelection : 1;
bool m_hasContents : 1;
- friend class QQuickTextNode;
+ friend class QSGInternalTextNode;
};
diff --git a/src/quick/items/qquicktextutil_p.h b/src/quick/items/qquicktextutil_p.h
index 212c488c73..9f8e2ae393 100644
--- a/src/quick/items/qquicktextutil_p.h
+++ b/src/quick/items/qquicktextutil_p.h
@@ -99,6 +99,8 @@ typename T::RenderType QQuickTextUtil::textRenderType()
return T::QtRendering;
case QQuickWindow::NativeTextRendering:
return T::NativeRendering;
+ case QQuickWindow::CurveTextRendering:
+ return T::CurveRendering;
}
Q_UNREACHABLE_RETURN(T::QtRendering);
diff --git a/src/quick/items/qquicktranslate_p.h b/src/quick/items/qquicktranslate_p.h
index 3dc3310da1..496a115d3f 100644
--- a/src/quick/items/qquicktranslate_p.h
+++ b/src/quick/items/qquicktranslate_p.h
@@ -24,7 +24,7 @@
QT_BEGIN_NAMESPACE
class QQuickTranslatePrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickTranslate : public QQuickTransform
+class Q_QUICK_EXPORT QQuickTranslate : public QQuickTransform
{
Q_OBJECT
@@ -53,7 +53,7 @@ private:
};
class QQuickScalePrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickScale : public QQuickTransform
+class Q_QUICK_EXPORT QQuickScale : public QQuickTransform
{
Q_OBJECT
@@ -92,7 +92,7 @@ private:
};
class QQuickRotationPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickRotation : public QQuickTransform
+class Q_QUICK_EXPORT QQuickRotation : public QQuickTransform
{
Q_OBJECT
@@ -126,7 +126,7 @@ private:
};
class QQuickMatrix4x4Private;
-class Q_QUICK_PRIVATE_EXPORT QQuickMatrix4x4 : public QQuickTransform
+class Q_QUICK_EXPORT QQuickMatrix4x4 : public QQuickTransform
{
Q_OBJECT
@@ -151,6 +151,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickTranslate)
-
#endif
diff --git a/src/quick/items/qquicktreeview.cpp b/src/quick/items/qquicktreeview.cpp
index e73eb459d4..033884d58d 100644
--- a/src/quick/items/qquicktreeview.cpp
+++ b/src/quick/items/qquicktreeview.cpp
@@ -42,7 +42,7 @@
a set of properties that can be used to position and render each node
in the tree correctly.
- An example of a custom delegate is shown below:
+ An example of a custom delegate with an animating indicator is shown below:
\snippet qml/treeview/qml-customdelegate.qml 0
@@ -81,16 +81,26 @@
By default, TreeView \l {toggleExpanded()}{toggles} the expanded state
of a row when you double tap on it. Since this is in conflict with
- double tapping to edit a cell, TreeView sets \l editTriggers to
+ double tapping to edit a cell, TreeView sets \l {TableView::}{editTriggers} to
\c TableView.EditKeyPressed by default (which is different from TableView,
which uses \c {TableView.EditKeyPressed | TableView.DoubleTapped}.
- If you change \l editTriggers to also contain \c TableView.DoubleTapped,
+ If you change \l {TableView::}{editTriggers} to also contain \c TableView.DoubleTapped,
toggling the expanded state with a double tap will be disabled.
\note A TreeView only accepts a model that inherits \l QAbstractItemModel.
*/
/*!
+ \qmlproperty QModelIndex QtQuick::TreeView::rootIndex
+ \since 6.6
+
+ This property holds the model index of the root item in the tree.
+ By default, this is the same as the root index in the model, but you can
+ set it to be a child index instead, to show only a branch of the tree.
+ Set it to \c undefined to show the whole model.
+*/
+
+/*!
\qmlmethod int QtQuick::TreeView::depth(row)
Returns the depth (the number of parents up to the root) of the given \a row.
@@ -268,9 +278,6 @@ void QQuickTreeViewPrivate::setModelImpl(const QVariant &newModel)
{
Q_Q(QQuickTreeView);
- if (newModel == m_assignedModel)
- return;
-
m_assignedModel = newModel;
QVariant effectiveModel = m_assignedModel;
if (effectiveModel.userType() == qMetaTypeId<QJSValue>())
@@ -337,55 +344,39 @@ void QQuickTreeViewPrivate::updateSelection(const QRect &oldSelection, const QRe
{
Q_Q(QQuickTreeView);
- const QRect oldRect = oldSelection.normalized();
- const QRect newRect = newSelection.normalized();
-
if (oldSelection == newSelection)
return;
- // Select the rows inside newRect that doesn't overlap with oldRect
- for (int row = newRect.y(); row <= newRect.y() + newRect.height(); ++row) {
- if (oldRect.y() != -1 && oldRect.y() <= row && row <= oldRect.y() + oldRect.height())
- continue;
- const QModelIndex startIndex = q->modelIndex(row, newRect.x());
- const QModelIndex endIndex = q->modelIndex(row, newRect.x() + newRect.width());
- selectionModel->select(QItemSelection(startIndex, endIndex), QItemSelectionModel::Select);
+ QItemSelection select;
+ QItemSelection deselect;
+
+ // Because each row can have a different parent, we need to create separate QItemSelections
+ // per row. But all the cells in a given row have the same parent, so they can be combined.
+ // As a result, the final QItemSelection can end up more fragmented compared to a selection
+ // in QQuickTableView, where all cells have the same parent. In the end, if TreeView has
+ // a lot of columns and the selection mode is "SelectCells", using the mouse to adjust
+ // a selection containing a _large_ number of columns can be slow.
+ const QRect cells = newSelection.normalized();
+ for (int row = cells.y(); row <= cells.y() + cells.height(); ++row) {
+ const QModelIndex startIndex = q->index(row, cells.x());
+ const QModelIndex endIndex = q->index(row, cells.x() + cells.width());
+ select.merge(QItemSelection(startIndex, endIndex), QItemSelectionModel::Select);
}
- if (oldRect.x() != -1) {
- // Since oldRect is valid, this update is a continuation of an already existing selection!
-
- // Select the columns inside newRect that don't overlap with oldRect
- for (int column = newRect.x(); column <= newRect.x() + newRect.width(); ++column) {
- if (oldRect.x() <= column && column <= oldRect.x() + oldRect.width())
- continue;
- for (int row = newRect.y(); row <= newRect.y() + newRect.height(); ++row)
- selectionModel->select(q->modelIndex(row, column), QItemSelectionModel::Select);
- }
-
- // Unselect the rows inside oldRect that don't overlap with newRect
- for (int row = oldRect.y(); row <= oldRect.y() + oldRect.height(); ++row) {
- if (newRect.y() <= row && row <= newRect.y() + newRect.height())
- continue;
- const QModelIndex startIndex = q->modelIndex(row, oldRect.x());
- const QModelIndex endIndex = q->modelIndex(row, oldRect.x() + oldRect.width());
- selectionModel->select(QItemSelection(startIndex, endIndex), QItemSelectionModel::Deselect);
- }
+ const QModelIndexList indexes = selectionModel->selection().indexes();
+ for (const QModelIndex &index : indexes) {
+ if (!select.contains(index) && !existingSelection.contains(index))
+ deselect.merge(QItemSelection(index, index), QItemSelectionModel::Select);
+ }
- // Unselect the columns inside oldRect that don't overlap with newRect
- for (int column = oldRect.x(); column <= oldRect.x() + oldRect.width(); ++column) {
- if (newRect.x() <= column && column <= newRect.x() + newRect.width())
- continue;
- // Since we're not allowed to call select/unselect on the selectionModel with
- // indices from different parents, and since indicies from different parents are
- // expected when working with trees, we need to unselect the indices in the column
- // one by one, rather than the whole column in one go. This, however, can cause a
- // lot of selection fragments in the selectionModel, which eventually can hurt
- // performance. But large selections containing a lot of columns is not normally
- // the case for a treeview, so accept this potential corner case for now.
- for (int row = newRect.y(); row <= newRect.y() + newRect.height(); ++row)
- selectionModel->select(q->modelIndex(row, column), QItemSelectionModel::Deselect);
- }
+ if (selectionFlag == QItemSelectionModel::Select) {
+ selectionModel->select(deselect, QItemSelectionModel::Deselect);
+ selectionModel->select(select, QItemSelectionModel::Select);
+ } else {
+ QItemSelection oldSelection = existingSelection;
+ oldSelection.merge(select, QItemSelectionModel::Deselect);
+ selectionModel->select(oldSelection, QItemSelectionModel::Select);
+ selectionModel->select(select, QItemSelectionModel::Deselect);
}
}
@@ -403,6 +394,8 @@ QQuickTreeView::QQuickTreeView(QQuickItem *parent)
d->QQuickTableViewPrivate::setModelImpl(modelAsVariant);
QObjectPrivate::connect(&d->m_treeModelToTableModel, &QAbstractItemModel::dataChanged,
d, &QQuickTreeViewPrivate::dataChangedCallback);
+ QObject::connect(&d->m_treeModelToTableModel, &QQmlTreeModelToTableModel::rootIndexChanged,
+ this, &QQuickTreeView::rootIndexChanged);
auto tapHandler = new QQuickTapHandler(this);
tapHandler->setAcceptedModifiers(Qt::NoModifier);
@@ -421,6 +414,25 @@ QQuickTreeView::~QQuickTreeView()
{
}
+QModelIndex QQuickTreeView::rootIndex() const
+{
+ return d_func()->m_treeModelToTableModel.rootIndex();
+}
+
+void QQuickTreeView::setRootIndex(const QModelIndex &index)
+{
+ Q_D(QQuickTreeView);
+ d->m_treeModelToTableModel.setRootIndex(index);
+ positionViewAtCell({0, 0}, QQuickTableView::AlignTop | QQuickTableView::AlignLeft);
+}
+
+void QQuickTreeView::resetRootIndex()
+{
+ Q_D(QQuickTreeView);
+ d->m_treeModelToTableModel.resetRootIndex();
+ positionViewAtCell({0, 0}, QQuickTableView::AlignTop | QQuickTableView::AlignLeft);
+}
+
int QQuickTreeView::depth(int row) const
{
Q_D(const QQuickTreeView);
@@ -610,6 +622,7 @@ QPoint QQuickTreeView::cellAtIndex(const QModelIndex &index) const
return QPoint(tableIndex.column(), tableIndex.row());
}
+#if QT_DEPRECATED_SINCE(6, 4)
QModelIndex QQuickTreeView::modelIndex(int row, int column) const
{
static const bool compat6_4 = qEnvironmentVariable("QT_QUICK_TABLEVIEW_COMPAT_VERSION") == QStringLiteral("6.4");
@@ -621,9 +634,13 @@ QModelIndex QQuickTreeView::modelIndex(int row, int column) const
// to continue accepting calls to modelIndex(column, row).
return modelIndex({row, column});
} else {
+ qmlWarning(this) << "modelIndex(row, column) is deprecated. "
+ "Use index(row, column) instead. For more information, see "
+ "https://doc.qt.io/qt-6/qml-qtquick-tableview-obsolete.html";
return modelIndex({column, row});
}
}
+#endif
void QQuickTreeView::keyPressEvent(QKeyEvent *event)
{
diff --git a/src/quick/items/qquicktreeview_p.h b/src/quick/items/qquicktreeview_p.h
index b499f900cd..fe5fed6b95 100644
--- a/src/quick/items/qquicktreeview_p.h
+++ b/src/quick/items/qquicktreeview_p.h
@@ -22,9 +22,10 @@ QT_BEGIN_NAMESPACE
class QQuickTreeViewPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickTreeView : public QQuickTableView
+class Q_QUICK_EXPORT QQuickTreeView : public QQuickTableView
{
Q_OBJECT
+ Q_PROPERTY(QModelIndex rootIndex READ rootIndex WRITE setRootIndex RESET resetRootIndex NOTIFY rootIndexChanged REVISION(6, 6) FINAL)
QML_NAMED_ELEMENT(TreeView)
QML_ADDED_IN_VERSION(6, 3)
@@ -32,6 +33,10 @@ public:
QQuickTreeView(QQuickItem *parent = nullptr);
~QQuickTreeView() override;
+ QModelIndex rootIndex() const;
+ void setRootIndex(const QModelIndex &index);
+ void resetRootIndex();
+
Q_INVOKABLE int depth(int row) const;
Q_INVOKABLE bool isExpanded(int row) const;
@@ -44,12 +49,17 @@ public:
Q_REVISION(6, 4) Q_INVOKABLE void expandToIndex(const QModelIndex &index);
Q_INVOKABLE QModelIndex modelIndex(const QPoint &cell) const override;
- Q_INVOKABLE QModelIndex modelIndex(int row, int column) const override;
Q_INVOKABLE QPoint cellAtIndex(const QModelIndex &index) const override;
+#if QT_DEPRECATED_SINCE(6, 4)
+ QT_DEPRECATED_VERSION_X_6_4("Use index(row, column) instead")
+ Q_REVISION(6, 4) Q_INVOKABLE QModelIndex modelIndex(int row, int column) const override;
+#endif
+
Q_SIGNALS:
void expanded(int row, int depth);
void collapsed(int row, bool recursively);
+ Q_REVISION(6, 6) void rootIndexChanged();
protected:
void keyPressEvent(QKeyEvent *event) override;
@@ -61,6 +71,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickTreeView)
-
#endif // QQUICKTREEVIEW_P_H
diff --git a/src/quick/items/qquicktreeview_p_p.h b/src/quick/items/qquicktreeview_p_p.h
index c14d7c4107..bb6a8d7ce4 100644
--- a/src/quick/items/qquicktreeview_p_p.h
+++ b/src/quick/items/qquicktreeview_p_p.h
@@ -22,7 +22,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QQuickTreeViewPrivate : public QQuickTableViewPrivate
+class Q_QUICK_EXPORT QQuickTreeViewPrivate : public QQuickTableViewPrivate
{
public:
Q_DECLARE_PUBLIC(QQuickTreeView)
diff --git a/src/quick/items/qquickview.cpp b/src/quick/items/qquickview.cpp
index 7f0faa119e..cb9fab654c 100644
--- a/src/quick/items/qquickview.cpp
+++ b/src/quick/items/qquickview.cpp
@@ -35,7 +35,7 @@ void QQuickViewPrivate::init(QQmlEngine* e)
// The content item has CppOwnership policy (set in QQuickWindow). Ensure the presence of a JS
// wrapper so that the garbage collector can see the policy.
QV4::ExecutionEngine *v4 = engine.data()->handle();
- QV4::QObjectWrapper::wrap(v4, contentItem);
+ QV4::QObjectWrapper::ensureWrapper(v4, contentItem);
}
}
@@ -48,12 +48,11 @@ QQuickViewPrivate::~QQuickViewPrivate()
{
}
-void QQuickViewPrivate::execute()
+QQuickViewPrivate::ExecuteState QQuickViewPrivate::executeHelper()
{
- Q_Q(QQuickView);
if (!engine) {
qWarning() << "QQuickView: invalid qml engine.";
- return;
+ return Stop;
}
if (root)
@@ -62,6 +61,14 @@ void QQuickViewPrivate::execute()
delete component;
component = nullptr;
}
+ return ExecuteState::Continue;
+}
+
+void QQuickViewPrivate::execute()
+{
+ if (executeHelper() == Stop)
+ return;
+ Q_Q(QQuickView);
if (!source.isEmpty()) {
component = new QQmlComponent(engine.data(), source, q);
if (!component->isLoading()) {
@@ -73,6 +80,22 @@ void QQuickViewPrivate::execute()
}
}
+void QQuickViewPrivate::execute(QAnyStringView uri, QAnyStringView typeName)
+{
+ if (executeHelper() == Stop)
+ return;
+ Q_Q(QQuickView);
+
+ component = new QQmlComponent(engine.data(), uri, typeName, q);
+ if (!component->isLoading()) {
+ q->continueExecute();
+ } else {
+ QObject::connect(component, SIGNAL(statusChanged(QQmlComponent::Status)),
+ q, SLOT(continueExecute()));
+ }
+
+}
+
void QQuickViewPrivate::itemGeometryChanged(QQuickItem *resizeItem, QQuickGeometryChange change,
const QRectF &oldGeometry)
{
@@ -129,7 +152,7 @@ QQuickView::QQuickView(QWindow *parent)
/*!
Constructs a QQuickView with the given QML \a source and \a parent.
- The default value of \a parent is 0.
+ The default value of \a parent is \c{nullptr}.
*/
QQuickView::QQuickView(const QUrl &source, QWindow *parent)
@@ -139,6 +162,19 @@ QQuickView::QQuickView(const QUrl &source, QWindow *parent)
}
/*!
+ \since 6.7
+ Constructs a QQuickView with the element specified by \a uri and \a typeName
+ and parent \a parent.
+ The default value of \a parent is \c{nullptr}.
+ \sa loadFromModule
+ */
+QQuickView::QQuickView(QAnyStringView uri, QAnyStringView typeName, QWindow *parent)
+ : QQuickView(parent)
+{
+ loadFromModule(uri, typeName);
+}
+
+/*!
Constructs a QQuickView with the given QML \a engine and \a parent.
Note: In this case, the QQuickView does not own the given \a engine object;
@@ -203,6 +239,26 @@ void QQuickView::setSource(const QUrl& url)
}
/*!
+ \since 6.7
+ Loads the QML component identified by \a uri and \a typeName. If the component
+ is backed by a QML file, \l{source} will be set accordingly. For types defined
+ in \c{C++}, \c{source} will be empty.
+
+ If any \l{source} was set before this method was called, it will be cleared.
+
+ Calling this method multiple times with the same \a uri and \a typeName will result
+ in the QML component being reinstantiated.
+
+ \sa setSource, QQmlComponent::loadFromModule, QQmlApplicationEngine::loadFromModule
+ */
+void QQuickView::loadFromModule(QAnyStringView uri, QAnyStringView typeName)
+{
+ Q_D(QQuickView);
+ d->source = {}; // clear URL
+ d->execute(uri, typeName);
+}
+
+/*!
Sets the initial properties \a initialProperties with which the QML
component gets initialized after calling \l QQuickView::setSource().
@@ -468,6 +524,11 @@ void QQuickView::continueExecute()
return;
}
+ // If we used loadFromModule, we might not have a URL so far.
+ // Thus, query the component to retrieve the associated URL, if any
+ if (d->source.isEmpty())
+ d->source = d->component->url();
+
if (d->setRootObject(obj.get()))
Q_UNUSED(obj.release());
emit statusChanged(status());
diff --git a/src/quick/items/qquickview.h b/src/quick/items/qquickview.h
index aeff7cd88d..92bc495a98 100644
--- a/src/quick/items/qquickview.h
+++ b/src/quick/items/qquickview.h
@@ -26,6 +26,7 @@ public:
explicit QQuickView(QWindow *parent = nullptr);
QQuickView(QQmlEngine* engine, QWindow *parent);
explicit QQuickView(const QUrl &source, QWindow *parent = nullptr);
+ explicit QQuickView(QAnyStringView uri, QAnyStringView typeName, QWindow *parent = nullptr);
QQuickView(const QUrl &source, QQuickRenderControl *renderControl);
~QQuickView() override;
@@ -52,6 +53,7 @@ public:
public Q_SLOTS:
void setSource(const QUrl&);
+ void loadFromModule(QAnyStringView uri, QAnyStringView typeName);
void setInitialProperties(const QVariantMap &initialProperties);
void setContent(const QUrl& url, QQmlComponent *component, QObject *item);
diff --git a/src/quick/items/qquickview_p.h b/src/quick/items/qquickview_p.h
index 9e495d9139..1ad1897af3 100644
--- a/src/quick/items/qquickview_p.h
+++ b/src/quick/items/qquickview_p.h
@@ -35,7 +35,7 @@ class QQmlError;
class QQuickItem;
class QQmlComponent;
-class Q_QUICK_PRIVATE_EXPORT QQuickViewPrivate : public QQuickWindowPrivate,
+class Q_QUICK_EXPORT QQuickViewPrivate : public QQuickWindowPrivate,
public QQuickItemChangeListener
{
Q_DECLARE_PUBLIC(QQuickView)
@@ -46,7 +46,10 @@ public:
QQuickViewPrivate();
~QQuickViewPrivate();
+ enum ExecuteState { Continue, Stop };
+ ExecuteState executeHelper();
void execute();
+ void execute(QAnyStringView uri, QAnyStringView typeName);
void itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &) override;
void initResize();
void updateSize();
diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp
index 86a60fd89d..1f308c286e 100644
--- a/src/quick/items/qquickwindow.cpp
+++ b/src/quick/items/qquickwindow.cpp
@@ -8,10 +8,12 @@
#include "qquickitem_p.h"
#include "qquickevents_p_p.h"
#include "qquickgraphicsdevice_p.h"
+#include "qquickwindowcontainer_p.h"
#include <QtQuick/private/qsgrenderer_p.h>
#include <QtQuick/private/qsgplaintexture_p.h>
#include <QtQuick/private/qquickpointerhandler_p.h>
+#include <QtQuick/private/qquickpointerhandler_p_p.h>
#include <private/qsgrenderloop_p.h>
#include <private/qsgrhisupport_p.h>
#include <private/qquickrendercontrol_p.h>
@@ -36,7 +38,7 @@
#include <QtQml/qqmlinfo.h>
#include <QtQml/private/qqmlmetatype_p.h>
-#include <QtQuick/private/qquickpixmapcache_p.h>
+#include <QtQuick/private/qquickpixmap_p.h>
#include <private/qqmldebugserviceinterfaces_p.h>
#include <private/qqmldebugconnector_p.h>
@@ -48,17 +50,17 @@
#ifndef QT_NO_DEBUG_STREAM
#include <private/qdebug_p.h>
#endif
+#include <QtCore/qpointer.h>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
+
+#include <utility>
+#include <mutex>
QT_BEGIN_NAMESPACE
-Q_DECLARE_LOGGING_CATEGORY(lcHoverTrace)
-Q_DECLARE_LOGGING_CATEGORY(lcMouse)
-Q_DECLARE_LOGGING_CATEGORY(lcTouch)
-Q_DECLARE_LOGGING_CATEGORY(lcPtr)
-Q_LOGGING_CATEGORY(lcDirty, "qt.quick.dirty")
-Q_LOGGING_CATEGORY(lcTransient, "qt.quick.window.transient")
+Q_STATIC_LOGGING_CATEGORY(lcDirty, "qt.quick.dirty")
+Q_LOGGING_CATEGORY(lcQuickWindow, "qt.quick.window")
bool QQuickWindowPrivate::defaultAlphaBuffer = false;
@@ -81,8 +83,8 @@ public:
QAnimationDriver *animationDriver = m_renderLoop->animationDriver();
if (animationDriver) {
- connect(animationDriver, SIGNAL(stopped()), this, SLOT(animationStopped()));
- connect(m_renderLoop, SIGNAL(timeToIncubate()), this, SLOT(incubate()));
+ connect(animationDriver, &QAnimationDriver::stopped, this, &QQuickWindowIncubationController::animationStopped);
+ connect(m_renderLoop, &QSGRenderLoop::timeToIncubate, this, &QQuickWindowIncubationController::incubate);
}
}
@@ -198,6 +200,8 @@ void QQuickWindow::showEvent(QShowEvent *)
void QQuickWindow::hideEvent(QHideEvent *)
{
Q_D(QQuickWindow);
+ if (auto da = d->deliveryAgentPrivate())
+ da->handleWindowHidden(this);
if (d->windowManager)
d->windowManager->hide(this);
}
@@ -324,6 +328,22 @@ struct PolishLoopDetector
int numPolishLoopsInSequence = 0;
};
+static const QQuickItem *firstItemWithDirtyChildrenStacking(const QQuickItem *item)
+{
+ if (QQuickItemPrivate::get(item)->dirtyAttributes
+ & QQuickItemPrivate::ChildrenStackingChanged) {
+ return item;
+ }
+
+ const auto childItems = item->childItems();
+ for (const auto *childItem : childItems) {
+ if (auto *dirtyItem = firstItemWithDirtyChildrenStacking(childItem))
+ return dirtyItem;
+ }
+
+ return nullptr;
+}
+
void QQuickWindowPrivate::polishItems()
{
// An item can trigger polish on another item, or itself for that matter,
@@ -357,6 +377,11 @@ void QQuickWindowPrivate::polishItems()
deliveryAgentPrivate()->updateFocusItemTransform();
}
#endif
+
+ if (auto *dirtyItem = firstItemWithDirtyChildrenStacking(contentItem)) {
+ qCDebug(lcQuickWindow) << dirtyItem << "has dirty child stacking order";
+ updateChildWindowStackingOrder();
+ }
}
/*!
@@ -396,6 +421,7 @@ void QQuickWindow::physicalDpiChanged()
d->lastReportedItemDevicePixelRatio = newPixelRatio;
if (d->contentItem)
updatePixelRatioHelper(d->contentItem, newPixelRatio);
+ d->forcePolish();
}
void QQuickWindow::handleFontDatabaseChanged()
@@ -404,23 +430,6 @@ void QQuickWindow::handleFontDatabaseChanged()
d->pendingFontUpdate = true;
}
-void QQuickWindow::handleScreenChanged(QScreen *screen)
-{
- Q_D(QQuickWindow);
- // we connected to the initial screen in QQuickWindowPrivate::init, but the screen changed
- disconnect(d->physicalDpiChangedConnection);
- if (screen) {
- physicalDpiChanged();
- // When physical DPI changes on the same screen, either the resolution or the device pixel
- // ratio changed. We must check what it is. Device pixel ratio does not have its own
- // ...Changed() signal. Reconnect, same as in QQuickWindowPrivate::init.
- d->physicalDpiChangedConnection = connect(screen, &QScreen::physicalDotsPerInchChanged,
- this, &QQuickWindow::physicalDpiChanged);
- }
-
- d->forcePolish();
-}
-
void forcePolishHelper(QQuickItem *item)
{
if (item->flags() & QQuickItem::ItemHasContents) {
@@ -432,6 +441,13 @@ void forcePolishHelper(QQuickItem *item)
forcePolishHelper(items.at(i));
}
+void QQuickWindow::handleScreenChanged(QScreen *screen)
+{
+ Q_D(QQuickWindow);
+ Q_UNUSED(screen);
+ d->forcePolish();
+}
+
/*!
Schedules polish events on all items in the scene.
*/
@@ -454,27 +470,37 @@ void forceUpdate(QQuickItem *item)
forceUpdate(items.at(i));
}
-void QQuickWindowRenderTarget::reset(QRhi *rhi)
+void QQuickWindowRenderTarget::reset(QRhi *rhi, ResetFlags flags)
{
- if (owns) {
- if (rhi) {
- delete renderTarget;
- delete rpDesc;
- delete texture;
- delete renderBuffer;
- delete depthStencil;
- }
+ if (rhi) {
+ if (rt.owns)
+ delete rt.renderTarget;
- delete paintDevice;
+ delete res.texture;
+ delete res.renderBuffer;
+ delete res.rpDesc;
}
- renderTarget = nullptr;
- rpDesc = nullptr;
- texture = nullptr;
- renderBuffer = nullptr;
- depthStencil = nullptr;
- paintDevice = nullptr;
- owns = false;
+ rt = {};
+ res = {};
+
+ if (!flags.testFlag(ResetFlag::KeepImplicitBuffers))
+ implicitBuffers.reset(rhi);
+
+ if (sw.owns)
+ delete sw.paintDevice;
+
+ sw = {};
+}
+
+void QQuickWindowRenderTarget::ImplicitBuffers::reset(QRhi *rhi)
+{
+ if (rhi) {
+ delete depthStencil;
+ delete depthStencilTexture;
+ delete multisampleTexture;
+ }
+ *this = {};
}
void QQuickWindowPrivate::invalidateFontData(QQuickItem *item)
@@ -491,19 +517,18 @@ void QQuickWindowPrivate::invalidateFontData(QQuickItem *item)
void QQuickWindowPrivate::ensureCustomRenderTarget()
{
// resolve() can be expensive when importing an existing native texture, so
- // it is important to only do it when the QQuickRenderTarget* was really changed
+ // it is important to only do it when the QQuickRenderTarget was really changed.
if (!redirect.renderTargetDirty)
return;
redirect.renderTargetDirty = false;
- redirect.rt.reset(rhi);
-
- // a default constructed QQuickRenderTarget means no redirection
- if (customRenderTarget.isNull())
- return;
+ redirect.rt.reset(rhi, QQuickWindowRenderTarget::ResetFlag::KeepImplicitBuffers);
- QQuickRenderTargetPrivate::get(&customRenderTarget)->resolve(rhi, &redirect.rt);
+ if (!QQuickRenderTargetPrivate::get(&customRenderTarget)->resolve(rhi, &redirect.rt)) {
+ qWarning("Failed to set up render target redirection for QQuickWindow");
+ redirect.rt.reset(rhi);
+ }
}
void QQuickWindowPrivate::setCustomCommandBuffer(QRhiCommandBuffer *cb)
@@ -553,13 +578,7 @@ void QQuickWindowPrivate::syncSceneGraph()
animationController->afterNodeSync();
- // Copy the current state of clearing from window into renderer.
renderer->setClearColor(clearColor);
- // Cannot skip clearing the color buffer in Qt 6 anymore.
- const QSGAbstractRenderer::ClearMode mode = QSGAbstractRenderer::ClearColorBuffer
- | QSGAbstractRenderer::ClearStencilBuffer
- | QSGAbstractRenderer::ClearDepthBuffer;
- renderer->setClearMode(mode);
renderer->setVisualizationMode(visualizationMode);
@@ -584,6 +603,30 @@ void QQuickWindowPrivate::emitAfterRenderPassRecording(void *ud)
emit w->afterRenderPassRecording();
}
+int QQuickWindowPrivate::multiViewCount()
+{
+ if (rhi) {
+ ensureCustomRenderTarget();
+ if (redirect.rt.rt.renderTarget)
+ return redirect.rt.rt.multiViewCount;
+ }
+
+ // Note that on QRhi level 0 and 1 are often used interchangeably, as both mean
+ // no-multiview. Here in Qt Quick let's always use 1 as the default
+ // (no-multiview), so that higher layers (effects, materials) do not need to
+ // handle both 0 and 1, only 1.
+ return 1;
+}
+
+QRhiRenderTarget *QQuickWindowPrivate::activeCustomRhiRenderTarget()
+{
+ if (rhi) {
+ ensureCustomRenderTarget();
+ return redirect.rt.rt.renderTarget;
+ }
+ return nullptr;
+}
+
void QQuickWindowPrivate::renderSceneGraph()
{
Q_Q(QQuickWindow);
@@ -597,8 +640,8 @@ void QQuickWindowPrivate::renderSceneGraph()
QRhiRenderTarget *rt;
QRhiRenderPassDescriptor *rp;
QRhiCommandBuffer *cb;
- if (redirect.rt.renderTarget) {
- rt = redirect.rt.renderTarget;
+ if (redirect.rt.rt.renderTarget) {
+ rt = redirect.rt.rt.renderTarget;
rp = rt->renderPassDescriptor();
if (!rp) {
qWarning("Custom render target is set but no renderpass descriptor has been provided.");
@@ -619,8 +662,9 @@ void QQuickWindowPrivate::renderSceneGraph()
cb = swapchain->currentFrameCommandBuffer();
}
sgRenderTarget = QSGRenderTarget(rt, rp, cb);
+ sgRenderTarget.multiViewCount = multiViewCount();
} else {
- sgRenderTarget = QSGRenderTarget(redirect.rt.paintDevice);
+ sgRenderTarget = QSGRenderTarget(redirect.rt.sw.paintDevice);
}
context->beginNextFrame(renderer,
@@ -633,19 +677,12 @@ void QQuickWindowPrivate::renderSceneGraph()
emit q->beforeRendering();
runAndClearJobs(&beforeRenderingJobs);
- QSGAbstractRenderer::MatrixTransformFlags matrixFlags;
- bool flipY = rhi ? !rhi->isYUpInNDC() : false;
- if (!customRenderTarget.isNull() && customRenderTarget.mirrorVertically())
- flipY = !flipY;
- if (flipY)
- matrixFlags |= QSGAbstractRenderer::MatrixTransformFlipY;
-
const qreal devicePixelRatio = q->effectiveDevicePixelRatio();
QSize pixelSize;
- if (redirect.rt.renderTarget)
- pixelSize = redirect.rt.renderTarget->pixelSize();
- else if (redirect.rt.paintDevice)
- pixelSize = QSize(redirect.rt.paintDevice->width(), redirect.rt.paintDevice->height());
+ if (redirect.rt.rt.renderTarget)
+ pixelSize = redirect.rt.rt.renderTarget->pixelSize();
+ else if (redirect.rt.sw.paintDevice)
+ pixelSize = QSize(redirect.rt.sw.paintDevice->width(), redirect.rt.sw.paintDevice->height());
else if (rhi)
pixelSize = swapchain->currentPixelSize();
else // software or other backend
@@ -654,7 +691,16 @@ void QQuickWindowPrivate::renderSceneGraph()
renderer->setDevicePixelRatio(devicePixelRatio);
renderer->setDeviceRect(QRect(QPoint(0, 0), pixelSize));
renderer->setViewportRect(QRect(QPoint(0, 0), pixelSize));
- renderer->setProjectionMatrixToRect(QRectF(QPointF(0, 0), pixelSize / devicePixelRatio), matrixFlags);
+
+ QSGAbstractRenderer::MatrixTransformFlags matrixFlags;
+ bool flipY = rhi ? !rhi->isYUpInNDC() : false;
+ if (!customRenderTarget.isNull() && customRenderTarget.mirrorVertically())
+ flipY = !flipY;
+ if (flipY)
+ matrixFlags |= QSGAbstractRenderer::MatrixTransformFlipY;
+
+ const QRectF rect(QPointF(0, 0), pixelSize / devicePixelRatio);
+ renderer->setProjectionMatrixToRect(rect, matrixFlags, rhi && !rhi->isYUpInNDC());
context->renderNextFrame(renderer);
@@ -682,7 +728,6 @@ QQuickWindowPrivate::QQuickWindowPrivate()
, clearColor(Qt::white)
, persistentGraphics(true)
, persistentSceneGraph(true)
- , componentCompleted(true)
, inDestructor(false)
, incubationController(nullptr)
, hasActiveSwapchain(false)
@@ -701,6 +746,24 @@ QQuickWindowPrivate::~QQuickWindowPrivate()
deliveryAgent = nullptr;
}
+void QQuickWindowPrivate::setPalette(QQuickPalette* palette)
+{
+ if (windowPaletteRef == palette)
+ return;
+
+ if (windowPaletteRef)
+ disconnect(windowPaletteRef, &QQuickPalette::changed, this, &QQuickWindowPrivate::updateWindowPalette);
+ windowPaletteRef = palette;
+ updateWindowPalette();
+ if (windowPaletteRef)
+ connect(windowPaletteRef, &QQuickPalette::changed, this, &QQuickWindowPrivate::updateWindowPalette);
+}
+
+void QQuickWindowPrivate::updateWindowPalette()
+{
+ QQuickPaletteProviderPrivateBase::setPalette(windowPaletteRef);
+}
+
void QQuickWindowPrivate::updateChildrenPalettes(const QPalette &parentPalette)
{
Q_Q(QQuickWindow);
@@ -744,12 +807,8 @@ void QQuickWindowPrivate::init(QQuickWindow *c, QQuickRenderControl *control)
q,
&QQuickWindow::handleFontDatabaseChanged);
- if (QScreen *screen = q->screen()) {
+ if (q->screen()) {
lastReportedItemDevicePixelRatio = q->effectiveDevicePixelRatio();
- // if the screen changes, then QQuickWindow::handleScreenChanged disconnects
- // and connects to the new screen
- physicalDpiChangedConnection = QObject::connect(screen, &QScreen::physicalDotsPerInchChanged,
- q, &QQuickWindow::physicalDpiChanged);
}
QSGContext *sg;
@@ -772,15 +831,14 @@ void QQuickWindowPrivate::init(QQuickWindow *c, QQuickRenderControl *control)
animationController.reset(new QQuickAnimatorController(q));
- QObject::connect(context, SIGNAL(initialized()), q, SIGNAL(sceneGraphInitialized()), Qt::DirectConnection);
- QObject::connect(context, SIGNAL(invalidated()), q, SIGNAL(sceneGraphInvalidated()), Qt::DirectConnection);
- QObject::connect(context, SIGNAL(invalidated()), q, SLOT(cleanupSceneGraph()), Qt::DirectConnection);
+ QObject::connect(context, &QSGRenderContext::initialized, q, &QQuickWindow::sceneGraphInitialized, Qt::DirectConnection);
+ QObject::connect(context, &QSGRenderContext::invalidated, q, &QQuickWindow::sceneGraphInvalidated, Qt::DirectConnection);
+ QObject::connect(context, &QSGRenderContext::invalidated, q, &QQuickWindow::cleanupSceneGraph, Qt::DirectConnection);
- QObject::connect(q, SIGNAL(focusObjectChanged(QObject*)), q, SIGNAL(activeFocusItemChanged()));
- QObject::connect(q, SIGNAL(screenChanged(QScreen*)), q, SLOT(handleScreenChanged(QScreen*)));
- QObject::connect(qApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)),
- q, SLOT(handleApplicationStateChanged(Qt::ApplicationState)));
- QObject::connect(q, SIGNAL(frameSwapped()), q, SLOT(runJobsAfterSwap()), Qt::DirectConnection);
+ QObject::connect(q, &QQuickWindow::focusObjectChanged, q, &QQuickWindow::activeFocusItemChanged);
+ QObject::connect(q, &QQuickWindow::screenChanged, q, &QQuickWindow::handleScreenChanged);
+ QObject::connect(qApp, &QGuiApplication::applicationStateChanged, q, &QQuickWindow::handleApplicationStateChanged);
+ QObject::connect(q, &QQuickWindow::frameSwapped, q, &QQuickWindow::runJobsAfterSwap, Qt::DirectConnection);
if (QQmlInspectorService *service = QQmlDebugConnector::service<QQmlInspectorService>())
service->addWindow(q);
@@ -861,13 +919,15 @@ void QQuickWindowPrivate::cleanup(QSGNode *n)
The Window object creates a new top-level window for a Qt Quick scene. It automatically sets up the
window for use with \c {QtQuick} graphical types.
- A Window can be declared inside an Item or inside another Window; in that
+ A Window can be declared inside an Item or inside another Window, in which
case the inner Window will automatically become "transient for" the outer
- Window: that is, most platforms will show it centered upon the outer window
- by default, and there may be other platform-dependent behaviors, depending
- also on the \l flags. If the nested window is intended to be a dialog in
- your application, you should also set \l flags to Qt.Dialog, because some
- window managers will not provide the centering behavior without that flag.
+ Window, with the outer Window as its \l transientParent. Most platforms will
+ show the Window centered upon the outer window in this case, and there may be
+ other platform-dependent behaviors, depending also on the \l flags. If the nested
+ window is intended to be a dialog in your application, you should also set \l flags
+ to \c Qt.Dialog, because some window managers will not provide the centering behavior
+ without that flag.
+
You can also declare multiple windows inside a top-level \l QtObject, in which
case the windows will have no transient relationship.
@@ -891,6 +951,22 @@ void QQuickWindowPrivate::cleanup(QSGNode *n)
// The confirmExitPopup allows user to save or discard the document,
// or to cancel the closing.
\endcode
+
+ \section1 Styling
+
+ As with all visual types in Qt Quick, Window supports
+ \l {palette}{palettes}. However, as with types like \l Text, Window does
+ not use palettes by default. For example, to change the background color
+ of the window when the operating system's theme changes, the \l color must
+ be set:
+
+ \snippet qml/windowPalette.qml declaration-and-color
+ \codeline
+ \snippet qml/windowPalette.qml text-item
+ \snippet qml/windowPalette.qml closing-brace
+
+ Use \l {ApplicationWindow} (and \l {Label}) from \l {Qt Quick Controls}
+ instead of Window to get automatic styling.
*/
/*!
@@ -1121,18 +1197,15 @@ QQuickWindow::~QQuickWindow()
delete root;
d->deliveryAgent = nullptr; // avoid forwarding events there during destruction
- d->renderJobMutex.lock();
- qDeleteAll(d->beforeSynchronizingJobs);
- d->beforeSynchronizingJobs.clear();
- qDeleteAll(d->afterSynchronizingJobs);
- d->afterSynchronizingJobs.clear();
- qDeleteAll(d->beforeRenderingJobs);
- d->beforeRenderingJobs.clear();
- qDeleteAll(d->afterRenderingJobs);
- d->afterRenderingJobs.clear();
- qDeleteAll(d->afterSwapJobs);
- d->afterSwapJobs.clear();
- d->renderJobMutex.unlock();
+
+ {
+ const std::lock_guard locker(d->renderJobMutex);
+ qDeleteAll(std::exchange(d->beforeSynchronizingJobs, {}));
+ qDeleteAll(std::exchange(d->afterSynchronizingJobs, {}));
+ qDeleteAll(std::exchange(d->beforeRenderingJobs, {}));
+ qDeleteAll(std::exchange(d->afterRenderingJobs, {}));;
+ qDeleteAll(std::exchange(d->afterSwapJobs, {}));
+ }
// It is important that the pixmap cache is cleaned up during shutdown.
// Besides playing nice, this also solves a practical problem that
@@ -1336,6 +1409,41 @@ QObject *QQuickWindow::focusObject() const
return const_cast<QQuickWindow*>(this);
}
+/*!
+ \internal
+
+ Clears all exclusive and passive grabs for the points in \a pointerEvent.
+
+ We never allow any kind of grab to persist after release, unless we're waiting
+ for a synth event from QtGui (as with most tablet events), so for points that
+ are fully released, the grab is cleared.
+
+ Called when QQuickWindow::event dispatches events, or when the QQuickOverlay
+ has filtered an event so that it bypasses normal delivery.
+*/
+void QQuickWindowPrivate::clearGrabbers(QPointerEvent *pointerEvent)
+{
+ if (pointerEvent->isEndEvent()
+ && !(QQuickDeliveryAgentPrivate::isTabletEvent(pointerEvent)
+ && (qApp->testAttribute(Qt::AA_SynthesizeMouseForUnhandledTabletEvents)
+ || QWindowSystemInterfacePrivate::TabletEvent::platformSynthesizesMouse))) {
+ if (pointerEvent->isSinglePointEvent()) {
+ if (static_cast<QSinglePointEvent *>(pointerEvent)->buttons() == Qt::NoButton) {
+ auto &firstPt = pointerEvent->point(0);
+ pointerEvent->setExclusiveGrabber(firstPt, nullptr);
+ pointerEvent->clearPassiveGrabbers(firstPt);
+ }
+ } else {
+ for (auto &point : pointerEvent->points()) {
+ if (point.state() == QEventPoint::State::Released) {
+ pointerEvent->setExclusiveGrabber(point, nullptr);
+ pointerEvent->clearPassiveGrabbers(point);
+ }
+ }
+ }
+ }
+}
+
/*! \reimp */
bool QQuickWindow::event(QEvent *event)
{
@@ -1478,26 +1586,7 @@ bool QQuickWindow::event(QEvent *event)
// or fix QTBUG-90851 so that the event always has points?
bool ret = (da && da->event(event));
- // failsafe: never allow any kind of grab to persist after release,
- // unless we're waiting for a synth event from QtGui (as with most tablet events)
- if (pe->isEndEvent() && !(QQuickDeliveryAgentPrivate::isTabletEvent(pe) &&
- (qApp->testAttribute(Qt::AA_SynthesizeMouseForUnhandledTabletEvents) ||
- QWindowSystemInterfacePrivate::TabletEvent::platformSynthesizesMouse))) {
- if (pe->isSinglePointEvent()) {
- if (static_cast<QSinglePointEvent *>(pe)->buttons() == Qt::NoButton) {
- auto &firstPt = pe->point(0);
- pe->setExclusiveGrabber(firstPt, nullptr);
- pe->clearPassiveGrabbers(firstPt);
- }
- } else {
- for (auto &point : pe->points()) {
- if (point.state() == QEventPoint::State::Released) {
- pe->setExclusiveGrabber(point, nullptr);
- pe->clearPassiveGrabbers(point);
- }
- }
- }
- }
+ d->clearGrabbers(pe);
if (ret)
return true;
@@ -1553,6 +1642,27 @@ bool QQuickWindow::event(QEvent *event)
d->inheritPalette(QGuiApplication::palette());
if (d->contentItem)
QCoreApplication::sendEvent(d->contentItem, event);
+ break;
+ case QEvent::DevicePixelRatioChange:
+ physicalDpiChanged();
+ break;
+ case QEvent::ChildWindowAdded: {
+ auto *childEvent = static_cast<QChildWindowEvent*>(event);
+ auto *childWindow = childEvent->child();
+ qCDebug(lcQuickWindow) << "Child window" << childWindow << "added to" << this;
+ if (childWindow->handle()) {
+ // The reparenting has already resulted in the native window
+ // being added to its parent, on top of all other windows. We need
+ // to do a synchronous re-stacking of the windows here, to avoid
+ // leaving the window in the wrong position while waiting for the
+ // asynchronous callback to QQuickWindow::polishItems().
+ d->updateChildWindowStackingOrder();
+ } else {
+ qCDebug(lcQuickWindow) << "No platform window yet."
+ << "Deferring child window stacking until surface creation";
+ }
+ break;
+ }
default:
break;
}
@@ -1568,6 +1678,35 @@ bool QQuickWindow::event(QEvent *event)
return QWindow::event(event);
}
+void QQuickWindowPrivate::updateChildWindowStackingOrder(QQuickItem *item)
+{
+ Q_Q(QQuickWindow);
+
+ if (!item) {
+ qCDebug(lcQuickWindow) << "Updating child window stacking order for" << q;
+ item = contentItem;
+ }
+ auto *itemPrivate = QQuickItemPrivate::get(item);
+ const auto paintOrderChildItems = itemPrivate->paintOrderChildItems();
+ for (auto *child : paintOrderChildItems) {
+ if (auto *windowContainer = qobject_cast<QQuickWindowContainer*>(child)) {
+ auto *window = windowContainer->containedWindow();
+ if (!window) {
+ qCDebug(lcQuickWindow) << windowContainer << "has no contained window yet";
+ continue;
+ }
+ if (window->parent() != q) {
+ qCDebug(lcQuickWindow) << window << "is not yet child of this window";
+ continue;
+ }
+ qCDebug(lcQuickWindow) << "Raising" << window << "owned by" << windowContainer;
+ window->raise();
+ }
+
+ updateChildWindowStackingOrder(child);
+ }
+}
+
/*! \reimp */
void QQuickWindow::keyPressEvent(QKeyEvent *e)
{
@@ -1664,11 +1803,14 @@ void QQuickWindowPrivate::updateCursor(const QPointF &scenePos, QQuickItem *root
if (!rootItem)
rootItem = contentItem;
auto cursorItemAndHandler = findCursorItemAndHandler(rootItem, scenePos);
- if (cursorItem != cursorItemAndHandler.first || cursorHandler != cursorItemAndHandler.second) {
+ if (cursorItem != cursorItemAndHandler.first || cursorHandler != cursorItemAndHandler.second ||
+ (cursorItemAndHandler.second && QQuickPointerHandlerPrivate::get(cursorItemAndHandler.second)->cursorDirty)) {
QWindow *renderWindow = QQuickRenderControl::renderWindowFor(q);
QWindow *window = renderWindow ? renderWindow : q;
cursorItem = cursorItemAndHandler.first;
cursorHandler = cursorItemAndHandler.second;
+ if (cursorHandler)
+ QQuickPointerHandlerPrivate::get(cursorItemAndHandler.second)->cursorDirty = false;
if (cursorItem) {
const auto cursor = QQuickItemPrivate::get(cursorItem)->effectiveCursor(cursorHandler);
qCDebug(lcHoverTrace) << "setting cursor" << cursor << "from" << cursorHandler << "or" << cursorItem;
@@ -1722,6 +1864,41 @@ void QQuickWindowPrivate::clearFocusObject()
da->clearFocusObject();
}
+void QQuickWindowPrivate::setFocusToTarget(FocusTarget target, Qt::FocusReason reason)
+{
+ if (!contentItem)
+ return;
+
+ QQuickItem *newFocusItem = nullptr;
+ switch (target) {
+ case FocusTarget::First:
+ case FocusTarget::Last: {
+ const bool forward = (target == FocusTarget::First);
+ newFocusItem = QQuickItemPrivate::nextPrevItemInTabFocusChain(contentItem, forward);
+ if (newFocusItem) {
+ const auto *itemPriv = QQuickItemPrivate::get(newFocusItem);
+ if (itemPriv->subFocusItem && itemPriv->flags & QQuickItem::ItemIsFocusScope)
+ clearFocusInScope(newFocusItem, itemPriv->subFocusItem, reason);
+ }
+ break;
+ }
+ case FocusTarget::Next:
+ case FocusTarget::Prev: {
+ const auto da = deliveryAgentPrivate();
+ Q_ASSERT(da);
+ QQuickItem *focusItem = da->focusTargetItem() ? da->focusTargetItem() : contentItem;
+ bool forward = (target == FocusTarget::Next);
+ newFocusItem = QQuickItemPrivate::nextPrevItemInTabFocusChain(focusItem, forward);
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (newFocusItem)
+ newFocusItem->forceActiveFocus(reason);
+}
+
/*!
\qmlproperty list<QtObject> Window::data
\qmldefault
@@ -1751,10 +1928,6 @@ void QQuickWindowPrivate::data_append(QQmlListProperty<QObject> *property, QObje
if (!o)
return;
QQuickWindow *that = static_cast<QQuickWindow *>(property->object);
- if (QQuickWindow *window = qmlobject_cast<QQuickWindow *>(o)) {
- qCDebug(lcTransient) << window << "is transient for" << that;
- window->setTransientParent(that);
- }
QQmlListProperty<QObject> itemProperty = QQuickItemPrivate::get(that->contentItem())->data();
itemProperty.append(&itemProperty, o);
}
@@ -1814,9 +1987,7 @@ void QQuickWindowPrivate::rhiCreationFailureMessage(const QString &backendName,
void QQuickWindowPrivate::cleanupNodes()
{
- for (int ii = 0; ii < cleanupNodeList.size(); ++ii)
- delete cleanupNodeList.at(ii);
- cleanupNodeList.clear();
+ qDeleteAll(std::exchange(cleanupNodeList, {}));
}
void QQuickWindowPrivate::cleanupNodesOnShutdown(QQuickItem *item)
@@ -1963,19 +2134,14 @@ void QQuickWindowPrivate::updateDirtyNode(QQuickItem *item)
itemPriv->itemNode()->setMatrix(matrix);
}
- bool clipEffectivelyChanged = (dirty & (QQuickItemPrivate::Clip | QQuickItemPrivate::Window)) &&
- ((item->clip() == false) != (itemPriv->clipNode() == nullptr));
- int effectRefCount = itemPriv->extra.isAllocated()?itemPriv->extra->effectRefCount:0;
- bool effectRefEffectivelyChanged = (dirty & (QQuickItemPrivate::EffectReference | QQuickItemPrivate::Window)) &&
- ((effectRefCount == 0) != (itemPriv->rootNode() == nullptr));
-
+ const bool clipEffectivelyChanged = dirty & (QQuickItemPrivate::Clip | QQuickItemPrivate::Window);
if (clipEffectivelyChanged) {
- QSGNode *parent = itemPriv->opacityNode() ? (QSGNode *) itemPriv->opacityNode() :
- (QSGNode *) itemPriv->itemNode();
+ QSGNode *parent = itemPriv->opacityNode() ? (QSGNode *)itemPriv->opacityNode()
+ : (QSGNode *)itemPriv->itemNode();
QSGNode *child = itemPriv->rootNode();
- if (item->clip()) {
- Q_ASSERT(itemPriv->clipNode() == nullptr);
+ if (bool initializeClipNode = item->clip() && itemPriv->clipNode() == nullptr;
+ initializeClipNode) {
QQuickDefaultClipNode *clip = new QQuickDefaultClipNode(item->clipRect());
itemPriv->extra.value().clipNode = clip;
clip->update();
@@ -1989,9 +2155,14 @@ void QQuickWindowPrivate::updateDirtyNode(QQuickItem *item)
parent->appendChildNode(clip);
}
- } else {
+ } else if (bool updateClipNode = item->clip() && itemPriv->clipNode() != nullptr;
+ updateClipNode) {
+ QQuickDefaultClipNode *clip = itemPriv->clipNode();
+ clip->setClipRect(item->clipRect());
+ clip->update();
+ } else if (bool removeClipNode = !item->clip() && itemPriv->clipNode() != nullptr;
+ removeClipNode) {
QQuickDefaultClipNode *clip = itemPriv->clipNode();
- Q_ASSERT(clip);
parent->removeChildNode(clip);
if (child) {
clip->removeChildNode(child);
@@ -2005,6 +2176,10 @@ void QQuickWindowPrivate::updateDirtyNode(QQuickItem *item)
}
}
+ const int effectRefCount = itemPriv->extra.isAllocated() ? itemPriv->extra->effectRefCount : 0;
+ const bool effectRefEffectivelyChanged =
+ (dirty & (QQuickItemPrivate::EffectReference | QQuickItemPrivate::Window))
+ && ((effectRefCount == 0) != (itemPriv->rootNode() == nullptr));
if (effectRefEffectivelyChanged) {
if (dirty & QQuickItemPrivate::ChildrenUpdateMask)
itemPriv->childContainerNode()->removeAllChildNodes();
@@ -2216,14 +2391,6 @@ void QQuickWindow::cleanupSceneGraph()
d->runAndClearJobs(&d->afterSwapJobs);
}
-void QQuickWindow::setTransientParent_helper(QQuickWindow *window)
-{
- qCDebug(lcTransient) << this << "is transient for" << window;
- setTransientParent(window);
- disconnect(sender(), SIGNAL(windowChanged(QQuickWindow*)),
- this, SLOT(setTransientParent_helper(QQuickWindow*)));
-}
-
QOpenGLContext *QQuickWindowPrivate::openglContext()
{
#if QT_CONFIG(opengl)
@@ -2258,7 +2425,7 @@ bool QQuickWindow::isSceneGraphInitialized() const
*/
/*!
- \qmlsignal QtQuick.Window::Window::frameSwapped()
+ \qmlsignal QtQuick::Window::frameSwapped()
This signal is emitted when a frame has been queued for presenting. With
vertical synchronization enabled the signal is emitted at most once per
@@ -2274,7 +2441,7 @@ bool QQuickWindow::isSceneGraphInitialized() const
*/
/*!
- \qmlsignal QtQuick.Window::Window::sceneGraphInitialized()
+ \qmlsignal QtQuick::Window::sceneGraphInitialized()
\internal
*/
@@ -2296,7 +2463,7 @@ bool QQuickWindow::isSceneGraphInitialized() const
*/
/*!
- \qmlsignal QtQuick.Window::Window::sceneGraphInvalidated()
+ \qmlsignal QtQuick::Window::sceneGraphInvalidated()
\internal
*/
@@ -2316,7 +2483,7 @@ bool QQuickWindow::isSceneGraphInitialized() const
*/
/*!
- \qmlsignal QtQuick.Window::Window::sceneGraphError(SceneGraphError error, QString message)
+ \qmlsignal QtQuick::Window::sceneGraphError(SceneGraphError error, QString message)
This signal is emitted when an \a error occurred during scene graph initialization.
@@ -2373,7 +2540,7 @@ bool QQuickWindow::isSceneGraphInitialized() const
*/
/*!
- \qmlsignal QtQuick.Window::Window::closing(CloseEvent close)
+ \qmlsignal QtQuick::Window::closing(CloseEvent close)
\since 5.1
This signal is emitted when the user tries to close the window.
@@ -2653,9 +2820,16 @@ QQmlIncubationController *QQuickWindow::incubationController() const
text. Using such features in combination with the NativeTextRendering
render type will lend poor and sometimes pixelated results.
- \value QtTextRendering Use Qt's own rasterization algorithm.
+ Both \c QtTextRendering and \c CurveTextRendering are hardware-accelerated techniques.
+ \c QtTextRendering is the faster of the two, but uses more memory and will exhibit rendering
+ artifacts at large sizes. \c CurveTextRendering should be considered as an alternative in cases
+ where \c QtTextRendering does not give good visual results or where reducing graphics memory
+ consumption is a priority.
+ \value QtTextRendering Use Qt's own rasterization algorithm.
\value NativeTextRendering Use the operating system's native rasterizer for text.
+ \value CurveTextRendering Text is rendered using a curve rasterizer running directly on
+ the graphics hardware. (Introduced in Qt 6.7.0.)
*/
/*!
@@ -2685,7 +2859,7 @@ QQmlIncubationController *QQuickWindow::incubationController() const
*/
/*!
- \qmlsignal QtQuick.Window::Window::beforeSynchronizing()
+ \qmlsignal QtQuick::Window::beforeSynchronizing()
\internal
*/
@@ -2712,7 +2886,7 @@ QQmlIncubationController *QQuickWindow::incubationController() const
*/
/*!
- \qmlsignal QtQuick.Window::Window::afterSynchronizing()
+ \qmlsignal QtQuick::Window::afterSynchronizing()
\internal
\since 5.3
*/
@@ -2732,23 +2906,23 @@ QQmlIncubationController *QQuickWindow::incubationController() const
to this signal is still important if the recording of copy type of commands
is desired since those cannot be enqueued within a render pass.
- When using OpenGL, the QOpenGLContext used for rendering by the scene graph
- will be bound at this point.
-
\warning This signal is emitted from the scene graph rendering thread. If your
slot function needs to finish before execution continues, you must make sure that
the connection is direct (see Qt::ConnectionType).
- \warning When using OpenGL, be aware that setting OpenGL 3.x or 4.x specific
- states and leaving these enabled or set to non-default values when returning
- from the connected slot can interfere with the scene graph's rendering.
+ \note When using OpenGL, be aware that setting OpenGL 3.x or 4.x specific
+ states and leaving these enabled or set to non-default values when
+ returning from the connected slot can interfere with the scene graph's
+ rendering. The QOpenGLContext used for rendering by the scene graph will be
+ bound when the signal is emitted.
- \sa rendererInterface(), {Scene Graph - OpenGL Under QML}, {Scene Graph - Metal Under QML},
- {Scene Graph - Vulkan Under QML}, {Scene Graph - Direct3D 11 Under QML}
+ \sa rendererInterface(), {Scene Graph - RHI Under QML}, {Scene Graph -
+ OpenGL Under QML}, {Scene Graph - Metal Under QML}, {Scene Graph - Vulkan
+ Under QML}, {Scene Graph - Direct3D 11 Under QML}
*/
/*!
- \qmlsignal QtQuick.Window::Window::beforeRendering()
+ \qmlsignal QtQuick::Window::beforeRendering()
\internal
*/
@@ -2767,23 +2941,23 @@ QQmlIncubationController *QQuickWindow::incubationController() const
and afterRenderPassRecording(), that is typically used to achieve under- or
overlaying of the custom rendering.
- When using OpenGL, the QOpenGLContext used for rendering by the scene graph
- will be bound at this point.
-
\warning This signal is emitted from the scene graph rendering thread. If your
slot function needs to finish before execution continues, you must make sure that
the connection is direct (see Qt::ConnectionType).
- \warning When using OpenGL, be aware that setting OpenGL 3.x or 4.x specific
- states and leaving these enabled or set to non-default values when returning
- from the connected slot can interfere with the scene graph's rendering.
+ \note When using OpenGL, be aware that setting OpenGL 3.x or 4.x specific
+ states and leaving these enabled or set to non-default values when
+ returning from the connected slot can interfere with the scene graph's
+ rendering. The QOpenGLContext used for rendering by the scene graph will be
+ bound when the signal is emitted.
- \sa rendererInterface(), {Scene Graph - OpenGL Under QML}, {Scene Graph - Metal Under QML},
- {Scene Graph - Vulkan Under QML}, {Scene Graph - Direct3D 11 Under QML}
+ \sa rendererInterface(), {Scene Graph - RHI Under QML}, {Scene Graph -
+ OpenGL Under QML}, {Scene Graph - Metal Under QML}, {Scene Graph - Vulkan
+ Under QML}, {Scene Graph - Direct3D 11 Under QML}
*/
/*!
- \qmlsignal QtQuick.Window::Window::afterRendering()
+ \qmlsignal QtQuick::Window::afterRendering()
\internal
*/
@@ -2813,10 +2987,12 @@ QQmlIncubationController *QQuickWindow::incubationController() const
\sa rendererInterface()
\since 5.14
+
+ \sa {Scene Graph - RHI Under QML}
*/
/*!
- \qmlsignal QtQuick.Window::Window::beforeRenderPassRecording()
+ \qmlsignal QtQuick::Window::beforeRenderPassRecording()
\internal
\since 5.14
*/
@@ -2846,6 +3022,8 @@ QQmlIncubationController *QQuickWindow::incubationController() const
\sa rendererInterface()
\since 5.14
+
+ \sa {Scene Graph - RHI Under QML}
*/
/*!
@@ -2871,7 +3049,7 @@ QQmlIncubationController *QQuickWindow::incubationController() const
*/
/*!
- \qmlsignal QtQuick.Window::Window::beforeFrameBegin()
+ \qmlsignal QtQuick::Window::beforeFrameBegin()
\internal
*/
@@ -2896,12 +3074,12 @@ QQmlIncubationController *QQuickWindow::incubationController() const
*/
/*!
- \qmlsignal QtQuick.Window::Window::afterFrameEnd()
+ \qmlsignal QtQuick::Window::afterFrameEnd()
\internal
*/
/*!
- \qmlsignal QtQuick.Window::Window::afterRenderPassRecording()
+ \qmlsignal QtQuick::Window::afterRenderPassRecording()
\internal
\since 5.14
*/
@@ -2921,7 +3099,7 @@ QQmlIncubationController *QQuickWindow::incubationController() const
*/
/*!
- \qmlsignal QtQuick.Window::Window::afterAnimating()
+ \qmlsignal QtQuick::Window::afterAnimating()
This signal is emitted on the GUI thread before requesting the render thread to
perform the synchronization of the scene graph.
@@ -2955,7 +3133,7 @@ QQmlIncubationController *QQuickWindow::incubationController() const
*/
/*!
- \qmlsignal QtQuick.Window::Window::sceneGraphAboutToStop()
+ \qmlsignal QtQuick::Window::sceneGraphAboutToStop()
\internal
\since 5.3
*/
@@ -2989,9 +3167,12 @@ QSGTexture *QQuickWindow::createTextureFromImage(const QImage &image) const
textures will in most cases be faster to render. When this flag is not set,
the texture will have an alpha channel based on the image's format.
- When \a options contains TextureHasMipmaps, the engine will create a
- texture which can use mipmap filtering. Mipmapped textures can not be in
- an atlas.
+ When \a options contains TextureHasMipmaps, the engine will create a texture
+ which can use mipmap filtering. Mipmapped textures can not be in an atlas.
+
+ Setting TextureHasAlphaChannel in \a options serves no purpose for this
+ function since assuming an alpha channel and blending is the default. To opt
+ out, set TextureIsOpaque.
When the scene graph uses OpenGL, the returned texture will be using \c
GL_TEXTURE_2D as texture target and \c GL_RGBA as internal format. With
@@ -3002,12 +3183,12 @@ QSGTexture *QQuickWindow::createTextureFromImage(const QImage &image) const
initialized.
\warning The returned texture is not memory managed by the scene graph and
- must be explicitly deleted by the caller on the rendering thread.
- This is achieved by deleting the texture from a QSGNode destructor
- or by using deleteLater() in the case where the texture already has affinity
- to the rendering thread.
+ must be explicitly deleted by the caller on the rendering thread. This is
+ achieved by deleting the texture from a QSGNode destructor or by using
+ deleteLater() in the case where the texture already has affinity to the
+ rendering thread.
- This function can be called from any thread.
+ This function can be called from both the main and the render thread.
\sa sceneGraphInitialized(), QSGTexture
*/
@@ -3024,8 +3205,58 @@ QSGTexture *QQuickWindow::createTextureFromImage(const QImage &image, CreateText
return d->context->createTexture(image, flags);
}
+/*!
+ Creates a new QSGTexture from the supplied \a texture.
+
+ Use \a options to customize the texture attributes. Only the
+ TextureHasAlphaChannel flag is taken into account by this function. When
+ set, the resulting QSGTexture is always treated by the scene graph renderer
+ as needing blending. For textures that are fully opaque, not setting the
+ flag can save the cost of performing alpha blending during rendering. The
+ flag has no direct correspondence to the \l{QRhiTexture::format()}{format}
+ of the QRhiTexture, i.e. not setting the flag while having a texture format
+ such as the commonly used \l QRhiTexture::RGBA8 is perfectly normal.
+
+ Mipmapping is not controlled by \a options since \a texture is already
+ created and has the presence or lack of mipmaps baked in.
+
+ The returned QSGTexture owns the QRhiTexture, meaning \a texture is
+ destroyed together with the returned QSGTexture.
+
+ If \a texture owns its underlying native graphics resources (OpenGL texture
+ object, Vulkan image, etc.), that depends on how the QRhiTexture was created
+ (\l{QRhiTexture::create()} or \l{QRhiTexture::createFrom()}), and that is
+ not controlled or changed by this function.
+
+ \note This is only functional when the scene graph has already initialized
+ and is using the default, \l{QRhi}-based \l{Scene Graph
+ Adaptations}{adaptation}. The return value is \nullptr otherwise.
+
+ \note This function can only be called on the scene graph render thread.
+
+ \since 6.6
+
+ \sa createTextureFromImage(), sceneGraphInitialized(), QSGTexture
+ */
+QSGTexture *QQuickWindow::createTextureFromRhiTexture(QRhiTexture *texture, CreateTextureOptions options) const
+{
+ Q_D(const QQuickWindow);
+ if (!d->rhi)
+ return nullptr;
+
+ QSGPlainTexture *t = new QSGPlainTexture;
+ t->setOwnsTexture(true);
+ t->setTexture(texture);
+ t->setHasAlphaChannel(options & QQuickWindow::TextureHasAlphaChannel);
+ t->setTextureSize(texture->pixelSize());
+ return t;
+}
+
+// Legacy, private alternative to createTextureFromRhiTexture() that internally
+// creates a QRhiTexture wrapping the existing native graphics resource.
+// New code should prefer using the public API.
QSGTexture *QQuickWindowPrivate::createTextureFromNativeTexture(quint64 nativeObjectHandle,
- int nativeLayout,
+ int nativeLayoutOrState,
uint nativeFormat,
const QSize &size,
QQuickWindow::CreateTextureOptions options,
@@ -3035,7 +3266,7 @@ QSGTexture *QQuickWindowPrivate::createTextureFromNativeTexture(quint64 nativeOb
return nullptr;
QSGPlainTexture *texture = new QSGPlainTexture;
- texture->setTextureFromNativeTexture(rhi, nativeObjectHandle, nativeLayout, nativeFormat,
+ texture->setTextureFromNativeTexture(rhi, nativeObjectHandle, nativeLayoutOrState, nativeFormat,
size, options, flags);
texture->setHasAlphaChannel(options & QQuickWindow::TextureHasAlphaChannel);
// note that the QRhiTexture does not (and cannot) own the native object
@@ -3050,6 +3281,10 @@ QSGTexture *QQuickWindowPrivate::createTextureFromNativeTexture(quint64 nativeOb
The background color for the window.
Setting this property is more efficient than using a separate Rectangle.
+
+ \note If you set the color to \c "transparent" or to a color with alpha translucency,
+ you should also set suitable \l flags such as \c {flags: Qt.FramelessWindowHint}.
+ Otherwise, window translucency may not be enabled consistently on all platforms.
*/
/*!
@@ -3136,7 +3371,7 @@ void QQuickWindow::setDefaultAlphaBuffer(bool useAlpha)
buffering of resources, such as buffers, is up to the graphics API client
to manage. Most commonly, a uniform buffer where the data changes between
frames cannot simply change its contents when submitting a frame, given
- that that frame may still be active ("in flight") when starting to record
+ that the frame may still be active ("in flight") when starting to record
the next frame. To avoid stalling the pipeline, one way is to have multiple
buffers (and memory allocations) under the hood, thus realizing at least a
double buffered scheme for such resources.
@@ -3302,10 +3537,12 @@ void QQuickWindow::endExternalCommands()
whether it's a dialog, popup, or a regular window, and whether it should
have a title bar, etc.
- The flags which you read from this property might differ from the ones
+ The flags that you read from this property might differ from the ones
that you set if the requested flags could not be fulfilled.
- \sa Qt::WindowFlags
+ \snippet qml/splashWindow.qml entire
+
+ \sa Qt::WindowFlags, {Qt Quick Examples - Window and Screen}
*/
/*!
@@ -3336,6 +3573,11 @@ void QQuickWindow::endExternalCommands()
The (x,y) position is relative to the \l Screen if there is only one,
or to the virtual desktop (arrangement of multiple screens).
+ \note Not all windowing systems support setting or querying top level
+ window positions. On such a system, programmatically moving windows
+ may not have any effect, and artificial values may be returned for
+ the current positions, such as \c QPoint(0, 0).
+
\qml
Window { x: 100; y: 100; width: 100; height: 100 }
\endqml
@@ -3372,10 +3614,13 @@ void QQuickWindow::endExternalCommands()
Setting visible to false is the same as setting \l visibility to \l {QWindow::}{Hidden}.
+ The default value is \c false, unless overridden by setting \l visibility.
+
\sa visibility
*/
/*!
+ \keyword qml-window-visibility-prop
\qmlproperty QWindow::Visibility Window::visibility
The screen-occupation state of the window.
@@ -3389,15 +3634,18 @@ void QQuickWindow::endExternalCommands()
visibility property you will always get the actual state, never
\c AutomaticVisibility.
- When a window is not visible its visibility is Hidden, and setting
+ When a window is not visible, its visibility is \c Hidden, and setting
visibility to \l {QWindow::}{Hidden} is the same as setting \l visible to \c false.
- \sa visible
+ \snippet qml/windowVisibility.qml entire
+
+ \sa visible, {Qt Quick Examples - Window and Screen}
\since 5.1
*/
/*!
\qmlattachedproperty QWindow::Visibility Window::visibility
+ \readonly
\since 5.4
This attached property holds whether the window is currently shown
@@ -3405,7 +3653,7 @@ void QQuickWindow::endExternalCommands()
hidden. The \c Window attached property can be attached to any Item. If the
item is not shown in any window, the value will be \l {QWindow::}{Hidden}.
- \sa visible, visibility
+ \sa visible, {qml-window-visibility-prop}{visibility}
*/
/*!
@@ -3486,33 +3734,32 @@ void QQuickWindow::endExternalCommands()
shown, that minimizing the parent window will also minimize the transient
window, and so on; however results vary somewhat from platform to platform.
- Normally if you declare a Window inside an Item or inside another Window,
- this relationship is deduced automatically. In that case, if you declare
- this window's \l visible property \c true, it will not actually be shown
- until the \c transientParent window is shown.
-
- However if you set this property, then Qt Quick will no longer wait until
- the \c transientParent window is shown before showing this window. If you
- want to to be able to show a transient window independently of the "parent"
- Item or Window within which it was declared, you can remove that
- relationship by setting \c transientParent to \c null:
-
- \qml
- import QtQuick.Window 2.13
-
- Window {
- // visible is false by default
- Window {
- transientParent: null
- visible: true
- }
- }
- \endqml
+ Declaring a Window inside an Item or another Window, either via the
+ \l{Window::data}{default property} or a dedicated property, will automatically
+ set up a transient parent relationship to the containing window,
+ unless the \l transientParent property is explicitly set. This applies
+ when creating Window items via \l [QML] {QtQml::Qt::createComponent()}
+ {Qt.createComponent} or \l [QML] {QtQml::Qt::createQmlObject()}
+ {Qt.createQmlObject} as well, as long as an Item or Window is passed
+ as the \c parent argument.
+
+ A Window with a transient parent will not be shown until its transient
+ parent is shown, even if the \l visible property is \c true. This also
+ applies for the automatic transient parent relationship described above.
+ In particular, if the Window's containing element is an Item, the window
+ will not be shown until the containing item is added to a scene, via its
+ \l{Concepts - Visual Parent in Qt Quick}{visual parent hierarchy}. Setting
+ the \l transientParent to \c null will override this behavior:
+
+ \snippet qml/nestedWindowTransientParent.qml 0
+ \snippet qml/nestedWindowTransientParent.qml 1
In order to cause the window to be centered above its transient parent by
default, depending on the window manager, it may also be necessary to set
the \l Window::flags property with a suitable \l Qt::WindowType (such as
\c Qt::Dialog).
+
+ \sa QtQuick::Window::parent
*/
/*!
@@ -3553,6 +3800,9 @@ void QQuickWindow::endExternalCommands()
The active status of the window.
+ \snippet qml/windowPalette.qml declaration-and-color
+ \snippet qml/windowPalette.qml closing-brace
+
\sa requestActivate()
*/
@@ -3566,14 +3816,7 @@ void QQuickWindow::endExternalCommands()
Here is an example which changes a label to show the active state of the
window in which it is shown:
- \qml
- import QtQuick 2.4
- import QtQuick.Window 2.2
-
- Text {
- text: Window.active ? "active" : "inactive"
- }
- \endqml
+ \snippet qml/windowActiveAttached.qml entire
*/
/*!
@@ -3838,6 +4081,48 @@ QSGRendererInterface *QQuickWindow::rendererInterface() const
}
/*!
+ \return the QRhi object used by this window for rendering.
+
+ Available only when the window is using Qt's 3D API and shading language
+ abstractions, meaning the result is always null when using the \c software
+ adaptation.
+
+ The result is valid only when rendering has been initialized, which is
+ indicated by the emission of the sceneGraphInitialized() signal. Before
+ that point, the returned value is null. With a regular, on-screen
+ QQuickWindow scenegraph initialization typically happens when the native
+ window gets exposed (shown) the first time. When using QQuickRenderControl,
+ initialization is done in the explicit
+ \l{QQuickRenderControl::initialize()}{initialize()} call.
+
+ In practice this function is a shortcut to querying the QRhi via the
+ QSGRendererInterface.
+
+ \since 6.6
+ */
+QRhi *QQuickWindow::rhi() const
+{
+ Q_D(const QQuickWindow);
+ return d->rhi;
+}
+
+/*!
+ \return the QRhiSwapChain used by this window, if there is one.
+
+ \note Only on-screen windows backed by one of the standard render loops
+ (such as, \c basic or \c threaded) will have a swapchain. Otherwise the
+ returned value is null. For example, the result is always null when the
+ window is used with QQuickRenderControl.
+
+ \since 6.6
+ */
+QRhiSwapChain *QQuickWindow::swapChain() const
+{
+ Q_D(const QQuickWindow);
+ return d->swapchain;
+}
+
+/*!
Requests the specified graphics \a api.
When the built-in, default graphics adaptation is used, \a api specifies
@@ -4114,6 +4399,18 @@ QQuickGraphicsConfiguration QQuickWindow::graphicsConfiguration() const
}
/*!
+ Creates a text node. When the scenegraph is not initialized, the return value is null.
+
+ \since 6.7
+ \sa QSGTextNode
+ */
+QSGTextNode *QQuickWindow::createTextNode() const
+{
+ Q_D(const QQuickWindow);
+ return isSceneGraphInitialized() ? d->context->sceneGraphContext()->createTextNode(d->context) : nullptr;
+}
+
+/*!
Creates a simple rectangle node. When the scenegraph is not initialized, the return value is null.
This is cross-backend alternative to constructing a QSGSimpleRectNode directly.
@@ -4191,10 +4488,11 @@ void QQuickWindow::setTextRenderType(QQuickWindow::TextRenderType renderType)
palette which serves as a default for all application windows. You can also set the default palette
for windows by passing a custom palette to QGuiApplication::setPalette(), before loading any QML.
- ApplicationWindow propagates explicit palette properties to child controls. If you change a specific
- property on the window's palette, that property propagates to all child controls in the window,
+ Window propagates explicit palette properties to child items and controls,
overriding any system defaults for that property.
+ \snippet qml/windowPalette.qml entire
+
\sa Item::palette, Popup::palette, ColorGroup, SystemPalette
//! internal \sa QQuickAbstractPaletteProvider, QQuickPalette
*/
diff --git a/src/quick/items/qquickwindow.h b/src/quick/items/qquickwindow.h
index 3f3c82a4b8..17c4bb6fd4 100644
--- a/src/quick/items/qquickwindow.h
+++ b/src/quick/items/qquickwindow.h
@@ -33,6 +33,10 @@ class QQuickPalette;
class QQuickRenderTarget;
class QQuickGraphicsDevice;
class QQuickGraphicsConfiguration;
+class QRhi;
+class QRhiSwapChain;
+class QRhiTexture;
+class QSGTextNode;
class Q_QUICK_EXPORT QQuickWindow : public QWindow
{
@@ -78,7 +82,8 @@ public:
enum TextRenderType {
QtTextRendering,
- NativeTextRendering
+ NativeTextRendering,
+ CurveTextRendering
};
Q_ENUM(TextRenderType)
@@ -115,6 +120,7 @@ public:
// Scene graph specific functions
QSGTexture *createTextureFromImage(const QImage &image) const;
QSGTexture *createTextureFromImage(const QImage &image, CreateTextureOptions options) const;
+ QSGTexture *createTextureFromRhiTexture(QRhiTexture *texture, CreateTextureOptions options = {}) const;
void setColor(const QColor &color);
QColor color() const;
@@ -151,10 +157,14 @@ public:
QSGRectangleNode *createRectangleNode() const;
QSGImageNode *createImageNode() const;
QSGNinePatchNode *createNinePatchNode() const;
+ QSGTextNode *createTextNode() const;
static TextRenderType textRenderType();
static void setTextRenderType(TextRenderType renderType);
+ QRhi *rhi() const;
+ QRhiSwapChain *swapChain() const;
+
Q_SIGNALS:
void frameSwapped();
void sceneGraphInitialized();
@@ -220,7 +230,6 @@ private Q_SLOTS:
void cleanupSceneGraph();
void physicalDpiChanged();
void handleScreenChanged(QScreen *screen);
- void setTransientParent_helper(QQuickWindow *window);
void runJobsAfterSwap();
void handleApplicationStateChanged(Qt::ApplicationState state);
void handleFontDatabaseChanged();
@@ -234,6 +243,7 @@ private:
#endif
friend class QQuickItem;
+ friend class QQuickItemPrivate;
friend class QQuickWidget;
friend class QQuickRenderControl;
friend class QQuickAnimatorController;
diff --git a/src/quick/items/qquickwindow_p.h b/src/quick/items/qquickwindow_p.h
index 57020eaa3e..8ba4e56515 100644
--- a/src/quick/items/qquickwindow_p.h
+++ b/src/quick/items/qquickwindow_p.h
@@ -56,8 +56,10 @@ class QRhiRenderBuffer;
class QRhiRenderPassDescriptor;
class QRhiTexture;
+Q_DECLARE_LOGGING_CATEGORY(lcQuickWindow)
+
//Make it easy to identify and customize the root item if needed
-class Q_QUICK_PRIVATE_EXPORT QQuickRootItem : public QQuickItem
+class Q_QUICK_EXPORT QQuickRootItem : public QQuickItem
{
Q_OBJECT
QML_ANONYMOUS
@@ -70,20 +72,39 @@ public Q_SLOTS:
void setHeight(int h) {QQuickItem::setHeight(qreal(h));}
};
-class QQuickWindowRenderTarget
+struct QQuickWindowRenderTarget
{
-public:
- void reset(QRhi *rhi);
- QRhiRenderTarget *renderTarget = nullptr;
- QRhiRenderPassDescriptor *rpDesc = nullptr;
- QRhiTexture *texture = nullptr;
- QRhiRenderBuffer *renderBuffer = nullptr;
- QRhiRenderBuffer *depthStencil = nullptr;
- QPaintDevice *paintDevice = nullptr;
- bool owns = false;
+ enum class ResetFlag {
+ KeepImplicitBuffers = 0x01
+ };
+ Q_DECLARE_FLAGS(ResetFlags, ResetFlag)
+ void reset(QRhi *rhi, ResetFlags flags = {});
+
+ struct {
+ QRhiRenderTarget *renderTarget = nullptr;
+ bool owns = false;
+ int multiViewCount = 1;
+ } rt;
+ struct {
+ QRhiTexture *texture = nullptr;
+ QRhiRenderBuffer *renderBuffer = nullptr;
+ QRhiRenderPassDescriptor *rpDesc = nullptr;
+ } res;
+ struct ImplicitBuffers {
+ QRhiRenderBuffer *depthStencil = nullptr;
+ QRhiTexture *depthStencilTexture = nullptr;
+ QRhiTexture *multisampleTexture = nullptr;
+ void reset(QRhi *rhi);
+ } implicitBuffers;
+ struct {
+ QPaintDevice *paintDevice = nullptr;
+ bool owns = false;
+ } sw;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickWindowPrivate
+Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickWindowRenderTarget::ResetFlags)
+
+class Q_QUICK_EXPORT QQuickWindowPrivate
: public QWindowPrivate
, public QQuickPaletteProviderPrivateBase<QQuickWindow, QQuickWindowPrivate>
{
@@ -101,6 +122,8 @@ public:
QQuickWindowPrivate();
~QQuickWindowPrivate() override;
+ void setPalette(QQuickPalette *p) override;
+ void updateWindowPalette();
void updateChildrenPalettes(const QPalette &parentPalette) override;
void init(QQuickWindow *, QQuickRenderControl *control = nullptr);
@@ -123,6 +146,7 @@ public:
#endif
void clearFocusObject() override;
+ void setFocusToTarget(FocusTarget, Qt::FocusReason) override;
void dirtyItem(QQuickItem *);
void cleanup(QSGNode *);
@@ -146,17 +170,17 @@ public:
Q_DECLARE_FLAGS(TextureFromNativeTextureFlags, TextureFromNativeTextureFlag)
QSGTexture *createTextureFromNativeTexture(quint64 nativeObjectHandle,
- int nativeLayout,
+ int nativeLayoutOrState,
uint nativeFormat,
const QSize &size,
QQuickWindow::CreateTextureOptions options,
TextureFromNativeTextureFlags flags = {}) const;
QSGTexture *createTextureFromNativeTexture(quint64 nativeObjectHandle,
- int nativeLayout,
+ int nativeLayoutOrState,
const QSize &size,
QQuickWindow::CreateTextureOptions options,
TextureFromNativeTextureFlags flags = {}) const {
- return createTextureFromNativeTexture(nativeObjectHandle, nativeLayout, 0, size, options, flags);
+ return createTextureFromNativeTexture(nativeObjectHandle, nativeLayoutOrState, 0, size, options, flags);
}
QQuickItem::UpdatePaintNodeData updatePaintNodeData;
@@ -179,6 +203,13 @@ public:
void fireFrameSwapped() { Q_EMIT q_func()->frameSwapped(); }
void fireAboutToStop() { Q_EMIT q_func()->sceneGraphAboutToStop(); }
+ void clearGrabbers(QPointerEvent *event);
+
+ void updateChildWindowStackingOrder(QQuickItem *item = nullptr);
+
+ int multiViewCount();
+ QRhiRenderTarget *activeCustomRhiRenderTarget();
+
QSGRenderContext *context;
QSGRenderer *renderer;
QByteArray visualizationMode; // Default renderer supports "clip", "overdraw", "changes", "batches" and blank.
@@ -191,7 +222,6 @@ public:
uint persistentGraphics : 1;
uint persistentSceneGraph : 1;
- uint componentCompleted : 1;
uint inDestructor : 1;
// Storage for setRenderTarget(QQuickRenderTarget).
@@ -258,6 +288,7 @@ public:
uint updatesEnabled : 1;
bool pendingFontUpdate = false;
bool windowEventDispatch = false;
+ QPointer<QQuickPalette> windowPaletteRef;
private:
static void cleanupNodesOnShutdown(QQuickItem *);
diff --git a/src/quick/items/qquickwindowattached_p.h b/src/quick/items/qquickwindowattached_p.h
index 8f11e6f4df..6cb0dd6433 100644
--- a/src/quick/items/qquickwindowattached_p.h
+++ b/src/quick/items/qquickwindowattached_p.h
@@ -24,17 +24,17 @@ QT_BEGIN_NAMESPACE
class QQuickItem;
class QQuickWindow;
-class Q_QUICK_PRIVATE_EXPORT QQuickWindowAttached : public QObject
+class Q_QUICK_EXPORT QQuickWindowAttached : public QObject
{
Q_OBJECT
- Q_PROPERTY(QWindow::Visibility visibility READ visibility NOTIFY visibilityChanged)
- Q_PROPERTY(bool active READ isActive NOTIFY activeChanged)
- Q_PROPERTY(QQuickItem* activeFocusItem READ activeFocusItem NOTIFY activeFocusItemChanged)
- Q_PROPERTY(QQuickItem* contentItem READ contentItem NOTIFY contentItemChanged)
- Q_PROPERTY(int width READ width NOTIFY widthChanged)
- Q_PROPERTY(int height READ height NOTIFY heightChanged)
- Q_PROPERTY(QQuickWindow *window READ window NOTIFY windowChanged)
+ Q_PROPERTY(QWindow::Visibility visibility READ visibility NOTIFY visibilityChanged FINAL)
+ Q_PROPERTY(bool active READ isActive NOTIFY activeChanged FINAL)
+ Q_PROPERTY(QQuickItem* activeFocusItem READ activeFocusItem NOTIFY activeFocusItemChanged FINAL)
+ Q_PROPERTY(QQuickItem* contentItem READ contentItem NOTIFY contentItemChanged FINAL)
+ Q_PROPERTY(int width READ width NOTIFY widthChanged FINAL)
+ Q_PROPERTY(int height READ height NOTIFY heightChanged FINAL)
+ Q_PROPERTY(QQuickWindow *window READ window NOTIFY windowChanged FINAL)
QML_ANONYMOUS
QML_ADDED_IN_VERSION(2, 0)
diff --git a/src/quick/items/qquickwindowcontainer.cpp b/src/quick/items/qquickwindowcontainer.cpp
new file mode 100644
index 0000000000..cf4209a36b
--- /dev/null
+++ b/src/quick/items/qquickwindowcontainer.cpp
@@ -0,0 +1,590 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qquickwindowcontainer_p.h"
+
+#include <QtQuick/qquickrendercontrol.h>
+
+#include <QtQuick/private/qquickitem_p.h>
+#include <QtQuick/private/qquickrectangle_p.h>
+#include <QtQuick/private/qquickwindowmodule_p.h>
+#include <QtQuick/private/qquickimplicitsizeitem_p_p.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_STATIC_LOGGING_CATEGORY(lcWindowContainer, "qt.quick.window.container")
+
+using namespace Qt::StringLiterals;
+
+/*!
+ \qmltype WindowContainer
+ \inqmlmodule QtQuick
+ \ingroup qtquick-visual
+ \inherits Item
+ \since 6.8
+
+ \brief Allows embedding arbitrary QWindows into a Qt Quick scene.
+
+ The window will become a child of the item's window,
+ with its position, size, z-order, etc. managed by the item.
+
+ Sibling items with a higher z-order than the window container
+ will not automatically overlap the embedded window, as the
+ window lives on top of the Qt Quick scene. To work around this,
+ place the sibling items inside their own dedicated child window:
+
+ \code
+ Item {
+ id: someItem
+ WindowContainer {
+ window: foreignWindow
+ }
+ WindowContainer {
+ window: Window {
+ Item {
+ id: siblingItem
+ }
+ }
+ }
+ }
+ \endcode
+
+ Similarly, child Items of the window container will not automatically
+ overlap the embedded window. To work around this, place the child
+ item inside a dedicated child window.
+
+ \code
+ Item {
+ id: someItem
+ WindowContainer {
+ id: windowContainer
+ window: foreignWindow
+ WindowContainer {
+ window: Window {
+ Item {
+ id: childItem
+ }
+ }
+ }
+ }
+ }
+ \endcode
+
+ \note The window container does not interoperate with QQuickWidget,
+ QQuickWindow::setRenderTarget(), QQuickRenderControl, or similar
+ functionality.
+
+ \sa {QtQuick::Window::parent}
+*/
+
+/*!
+ \qmlproperty QWindow QtQuick::WindowContainer::window
+
+ This property holds the window to embed.
+*/
+
+class QQuickWindowContainerPrivate : public QQuickImplicitSizeItemPrivate
+{
+ Q_DECLARE_PUBLIC(QQuickWindowContainer)
+protected:
+ bool transformChanged(QQuickItem *transformedItem) override;
+
+public:
+ QWindow *window = nullptr;
+ QQuickWindowContainer::ContainerMode containerMode;
+};
+
+/*!
+ \internal
+
+ Creates a new window container.
+
+ The container mode determines who has the last word in what the state
+ of the contained window should be. If the window container is explicitly
+ requested by the user via WindowContainer, the properties are set on the
+ item, and the embedded window should match that. If the window container
+ is implicitly created by setting a visual parent on a Window, the properties
+ are set on the Window, and the window container should respect that.
+*/
+QQuickWindowContainer::QQuickWindowContainer(QQuickItem *parent, ContainerMode containerMode)
+ : QQuickImplicitSizeItem(*(new QQuickWindowContainerPrivate), parent)
+{
+ Q_D(QQuickWindowContainer);
+
+ qCDebug(lcWindowContainer).verbosity(1) << "Creating window container"
+ << this << "with parent" << parent << "and" << containerMode;
+
+ d->containerMode = containerMode;
+
+ setFlag(QQuickItem::ItemObservesViewport); // For clipping
+
+ connect(this, &QQuickItem::windowChanged,
+ this, &QQuickWindowContainer::parentWindowChanged);
+
+ if (lcWindowContainer().isDebugEnabled()) {
+ auto *debugRectangle = new QQuickRectangle(this);
+ debugRectangle->setColor(QColor(255, 0, 255, 20));
+ auto *border = debugRectangle->border();
+ border->setColor(Qt::magenta);
+ border->setWidth(1.0);
+ QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(debugRectangle);
+ rectPrivate->anchors()->setFill(this);
+ }
+}
+
+QQuickWindowContainer::~QQuickWindowContainer()
+{
+ Q_D(const QQuickWindowContainer);
+ qCDebug(lcWindowContainer) << "Destructing window container" << this;
+
+ disconnect(this);
+ if (d->window) {
+ auto ownership = QJSEngine::objectOwnership(d->window);
+ qCDebug(lcWindowContainer) << "Contained window" << d->window
+ << "has" << (ownership == QQmlEngine::JavaScriptOwnership ?
+ "JavaScript" : "C++") << "ownership";
+ if (ownership == QQmlEngine::JavaScriptOwnership) {
+ delete d->window;
+ } else {
+ d->window->destroy();
+ d->window->setParent(nullptr);
+ }
+ }
+}
+
+void QQuickWindowContainer::releaseResources()
+{
+ Q_D(const QQuickWindowContainer);
+ qCDebug(lcWindowContainer) << "Destroying" << d->window
+ << "with platform window" << (d->window ? d->window->handle() : nullptr);
+ if (d->window)
+ d->window->destroy();
+}
+
+void QQuickWindowContainer::classBegin()
+{
+ qCDebug(lcWindowContainer) << "Class begin for" << this;
+
+ QQuickImplicitSizeItem::classBegin();
+}
+
+void QQuickWindowContainer::componentComplete()
+{
+ Q_D(const QQuickWindowContainer);
+
+ qCDebug(lcWindowContainer) << "Component completed for" << this;
+ QQuickImplicitSizeItem::componentComplete();
+
+ if (d->window)
+ initializeContainedWindow();
+}
+
+QWindow *QQuickWindowContainer::containedWindow() const
+{
+ Q_D(const QQuickWindowContainer);
+ return d->window;
+}
+
+void QQuickWindowContainer::setContainedWindow(QWindow *window)
+{
+ qCDebug(lcWindowContainer) << "Setting contained window for" << this << "to" << window;
+
+ Q_D(QQuickWindowContainer);
+
+ if (window == d->window)
+ return;
+
+ if (auto *previousWindow = d->window) {
+ qCDebug(lcWindowContainer) << "Decoupling container from" << d->window;
+ previousWindow->disconnect(this);
+ previousWindow->removeEventFilter(this);
+ previousWindow->setParent(nullptr);
+ }
+
+ d->window = window;
+
+ if (d->window) {
+ if (d->containerMode == ItemControlsWindow) {
+ if (auto *quickWindow = qobject_cast<QQuickWindowQmlImpl*>(d->window)) {
+ // Make sure the Window reflects the window container as its visual parent
+ quickWindow->setVisualParent(this);
+ }
+ }
+
+ // When the window controls the container, we need to reflect any changes
+ // in the window back to the container, so they stay in sync. And when the
+ // container controls the window, we still want to reflect width/height as
+ // new implicit size, and override any other changes with the item state.
+ connect(d->window, &QWindow::xChanged, this, &QQuickWindowContainer::windowUpdated);
+ connect(d->window, &QWindow::yChanged, this, &QQuickWindowContainer::windowUpdated);
+ connect(d->window, &QWindow::widthChanged, this, &QQuickWindowContainer::windowUpdated);
+ connect(d->window, &QWindow::heightChanged, this, &QQuickWindowContainer::windowUpdated);
+ connect(d->window, &QWindow::visibleChanged, this, &QQuickWindowContainer::windowUpdated);
+
+ connect(d->window, &QObject::destroyed, this, &QQuickWindowContainer::windowDestroyed);
+
+ d->window->installEventFilter(this);
+
+ if (d->componentComplete)
+ initializeContainedWindow();
+ } else {
+ // Reset state based on not having a window
+ syncWindowToItem();
+ }
+
+ emit containedWindowChanged(d->window);
+}
+
+void QQuickWindowContainer::initializeContainedWindow()
+{
+ Q_D(const QQuickWindowContainer);
+ Q_ASSERT(d->componentComplete);
+ Q_ASSERT(d->window);
+
+ qCDebug(lcWindowContainer) << "Doing initial sync between" << d->window << "and" << this;
+
+ syncWindowToItem();
+ polish();
+}
+
+static QTransform sanitizeTransform(const QTransform &transform)
+{
+ if (transform.isRotating()) {
+ // FIXME: Can we keep more here?
+ return QTransform::fromTranslate(transform.dx(), transform.dy());
+ }
+
+ return transform;
+}
+
+void QQuickWindowContainer::syncWindowToItem()
+{
+ Q_D(const QQuickWindowContainer);
+
+ const auto windowGeometry = d->window ? d->window->geometry() : QRect();
+
+ qCDebug(lcWindowContainer) << "Syncing window state from" << d->window
+ << "with geometry" << windowGeometry << "to" << this
+ << "with mode" << d->containerMode;
+
+ const auto transform = sanitizeTransform(d->windowToItemTransform());
+
+ // The window might have a larger size than the item's natural
+ // size, if there's a scale applied somewhere in the hierarchy.
+ auto itemSize = d->window ? transform.mapRect(windowGeometry).size()
+ : QSize();
+
+ if (d->containerMode == WindowControlsItem) {
+ // When the Window controls the window container the position is
+ // set up front, when creating the window container, and from that
+ // point on set exclusively via the window container, so we skip
+ // setting the position here, and only set the size.
+ setSize(itemSize);
+ setVisible(d->window ? d->window->isVisible() : false);
+ } else {
+ // Position defined by item, so don't sync from window
+ // Visible defined by item, so don't sync from window
+ setImplicitWidth(itemSize.width());
+ setImplicitHeight(itemSize.height());
+ }
+}
+
+/*!
+ \internal
+
+ updatePolish() should perform any layout as required for this item.
+
+ For us, that means propagating the item's state to the window.
+*/
+void QQuickWindowContainer::updatePolish()
+{
+ Q_D(QQuickWindowContainer);
+
+ qCDebug(lcWindowContainer) << "Propagating" << this << "state"
+ << "to" << d->window;
+
+ auto *parentWindow = window();
+
+ // FIXME: If we are part of a QQuickWidget, we have a QQuickRenderControl,
+ // and should look up the parent window via that, and apply the offset we
+ // get to the item transform below. But at the moment it's not possible
+ // to observe changes to the offset, which is critical to support this
+ // for child windows.
+
+ if (!d->window || !parentWindow)
+ return;
+
+ if (d->window->parent() != parentWindow) {
+ qCDebug(lcWindowContainer) << "Updating window parent to" << parentWindow;
+ d->window->setParent(parentWindow);
+ }
+
+ auto transform = sanitizeTransform(d->itemToWindowTransform());
+
+ // Find the window's geometry, based on the item's bounding rect,
+ // mapped to the scene. The mapping includes any x/y position set
+ // on the item itself, as well as any transforms applied to the item
+ // or its ancestor (scale, translation).
+ const QRectF itemSceneRect = transform.mapRect(boundingRect());
+ // FIXME: Rounding to a QRect here means we'll have some jitter or off
+ // placement when the underlying item is not on a integer coordinate.
+ QRect windowGeometry = itemSceneRect.toRect();
+ if (windowGeometry != d->window->geometry()) {
+ QRectF itemRect(position(), size());
+ qCDebug(lcWindowContainer) << "Updating window geometry to" << windowGeometry
+ << "based on item rect" << itemRect << "and scene rect" << itemSceneRect;
+ d->window->setGeometry(windowGeometry);
+ }
+
+ // Clip the container to its own and ancestor clip rects, by setting
+ // a mask on the window. This does not necessarily clip native windows,
+ // as QWindow::setMask() is not guaranteed to visually clip the window,
+ // only to mask input, but in most cases we should be good. For the
+ // cases where this fails, we can potentially use an intermediate window
+ // as parent of the contained window, if the platform allows clipping
+ // child windows to parent window geometry. We do not want to resize the
+ // contained window, as that will just fill the content into a smaller
+ // area.
+ const auto clipMask = [&]{
+ if (clipRect() == boundingRect())
+ return QRect();
+
+ // The clip rect has all the relevant transforms applied to it,
+ // except for the item's own scale. As the mask is in window
+ // local coordinates in the possibly scaled window, we need
+ // to apply the scale manually.
+ auto scaleTransform = QTransform::fromScale(transform.m11(), transform.m22());
+ auto rect = scaleTransform.mapRect(clipRect()).toRect();
+
+ // An empty clip rect means clip away everything, while for a
+ // window, an empty mask means mask nothing. Fake the former
+ // by setting a mask outside of the window's bounds. We have
+ // to do this check after rounding the clip rect to a QRect.
+ // FIXME: Verify this works on all platforms
+ if (rect.isEmpty())
+ return QRect(-1, -1, 1, 1);
+
+ return rect;
+ }();
+
+ if (clipMask != d->window->mask().boundingRect()) {
+ qCDebug(lcWindowContainer) << "Updating window clip mask to" << clipMask
+ << "based on clip rect" << clipRect();
+ d->window->setMask(clipMask);
+ }
+
+ // FIXME: Opacity support. Need to calculate effective opacity ourselves,
+ // and there doesn't seem to be any existing observer for opacity changes.
+ // Not all platforms implement opacity for child windows yet.
+
+ // FIXME: If a scale is applied to the item or its parents, we end up
+ // with a bigger item, and window, but we don't translate the scale to
+ // an increase device-pixel-ratio of the window. As a result, the window
+ // will likely just render more content, instead of the same content at
+ // a potentially higher density.
+
+ if (d->window->isVisible() != isVisible()) {
+ qCDebug(lcWindowContainer) << "Updating window visibility"
+ << "based on item visible" << isVisible();
+ d->window->setVisible(isVisible());
+ }
+}
+
+/*!
+ \internal
+
+ QQuickItem::clipRect() doesn't take ItemClipsChildrenToShape into
+ account, so a parent item that has clip:false, but ItemIsViewport
+ will still result in affecting the clip.
+
+ We want to stay consistent with the clipping in the scene graph,
+ which is based on QQuickItem::clip(), so we override the clipRect
+ to take ItemClipsChildrenToShape into account.
+*/
+QRectF QQuickWindowContainer::clipRect() const
+{
+ QRectF rect = boundingRect();
+
+ for (auto *viewport = viewportItem(); viewport; viewport = viewport->viewportItem()) {
+ if (viewport == this)
+ break;
+
+ if (viewport->flags().testFlag(QQuickItem::ItemClipsChildrenToShape)) {
+ // FIXME: This fails to take into account viewports that override clipRect()
+ const auto mappedViewportRect = mapRectFromItem(viewport, viewport->boundingRect());
+ rect = mappedViewportRect.intersected(rect);
+ }
+
+ if (viewport->viewportItem() == viewport)
+ break; // Content item returns itself as viewport
+ }
+
+ return rect;
+}
+
+// ----------------------- Window updates -----------------------
+
+/*!
+ \internal
+
+ Called when the contained QWindow is changed.
+
+ Depending on the sync mode we need to reflect these changes
+ to the item, or override them by applying the item state.
+*/
+void QQuickWindowContainer::windowUpdated()
+{
+ Q_D(const QQuickWindowContainer);
+
+ if (lcWindowContainer().isDebugEnabled()) {
+ auto metaMethod = sender()->metaObject()->method(senderSignalIndex());
+ auto signalName = QString::fromUtf8(metaMethod.name());
+ qCDebug(lcWindowContainer).noquote() << d->window << signalName;
+ }
+
+ syncWindowToItem();
+
+ if (d->containerMode == ItemControlsWindow) {
+ qCDebug(lcWindowContainer) << "Overriding window state by polishing";
+ // Ideally we'd always call ensurePolished() here, to synchronously
+ // override the window state ASAP, rather than wait for polish to
+ // trigger it asynchronously, but due to QWindowPrivate::setVisible
+ // emitting visibleChanged before updating the platform window, we
+ // end up applying our override temporarily, only to have QWindowPrivate
+ // follow up with the original change to the platform window.
+ if (d->window->isVisible() != isVisible())
+ polish();
+ else
+ ensurePolished();
+ }
+}
+
+bool QQuickWindowContainer::eventFilter(QObject *object, QEvent *event)
+{
+ Q_D(const QQuickWindowContainer);
+ Q_ASSERT(object == d->window);
+
+ if (event->type() == QEvent::PlatformSurface) {
+ auto type = static_cast<QPlatformSurfaceEvent*>(event)->surfaceEventType();
+ if (type == QPlatformSurfaceEvent::SurfaceCreated) {
+ qCDebug(lcWindowContainer) << "Surface created for" << object;
+ syncWindowToItem();
+ // The surface creation has already resulted in the native window
+ // being added to its parent, on top of all other windows. We need
+ // to do a synchronous re-stacking of the windows here, to avoid
+ // leaving the window in the wrong position while waiting for the
+ // asynchronous callback to QQuickWindow::polishItems().
+ if (auto *quickWindow = qobject_cast<QQuickWindow*>(window()))
+ QQuickWindowPrivate::get(quickWindow)->updateChildWindowStackingOrder();
+ }
+ }
+
+ return QQuickImplicitSizeItem::eventFilter(object, event);
+}
+
+void QQuickWindowContainer::windowDestroyed()
+{
+ Q_D(QQuickWindowContainer);
+ qCDebug(lcWindowContainer) << "Window" << (void*)d->window << "destroyed";
+
+ d->window->removeEventFilter(this);
+ d->window = nullptr;
+
+ syncWindowToItem(); // Reset state based on not having a window
+ emit containedWindowChanged(d->window);
+}
+
+// ----------------------- Item updates -----------------------
+
+/*!
+ \internal
+
+ Called when the item's geometry has changed
+*/
+void QQuickWindowContainer::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
+{
+ qCDebug(lcWindowContainer) << this << "geometry changed from"
+ << oldGeometry << "to" << newGeometry;
+
+ QQuickImplicitSizeItem::geometryChange(newGeometry, oldGeometry);
+ if (newGeometry.isValid())
+ polish();
+}
+
+/*!
+ \internal
+
+ Called when the item's (effective) state has changed
+*/
+void QQuickWindowContainer::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data)
+{
+ switch (change) {
+ case ItemVisibleHasChanged:
+ qCDebug(lcWindowContainer) << "Visible changed for" << this << "to" << isVisible();
+ polish();
+ break;
+ default:
+ break;
+ }
+
+ QQuickImplicitSizeItem::itemChange(change, data);
+}
+
+/*!
+ \internal
+
+ Called when the window container item is moved to another window
+*/
+void QQuickWindowContainer::parentWindowChanged(QQuickWindow *parentWindow)
+{
+ qCDebug(lcWindowContainer) << this << "parent window changed to" << parentWindow;
+
+ Q_D(QQuickWindowContainer);
+
+ if (!parentWindow) {
+ // We have been removed from the window we were part of,
+ // possibly because the window is going away. We need to
+ // make sure the contained window is no longer a child of
+ // former window, as otherwise it will be wiped out along
+ // with it. We can't wait for updatePolish() to do that
+ // as polish has no effect when an item is not part of a
+ // window.
+ if (d->window) {
+ // The window should already be destroyed from the
+ // call to releaseResources(), which is part of the
+ // removal of an item from a scene, but just in case
+ // we do it here as well.
+ d->window->destroy();
+
+ d->window->setParent(nullptr);
+ }
+ } else {
+ polish();
+ }
+}
+
+bool QQuickWindowContainerPrivate::transformChanged(QQuickItem *transformedItem)
+{
+ Q_Q(QQuickWindowContainer);
+
+ if (this->window) {
+ auto *transformedItemPrivate = QQuickItemPrivate::get(transformedItem);
+ qCDebug(lcWindowContainer) << "Transform changed for" << transformedItem
+ << "with dirty state" << transformedItemPrivate->dirtyToString();
+
+ if (transformedItemPrivate->dirtyAttributes
+ & QQuickItemPrivate::BasicTransform) {
+ // For some reason scale transforms, which result in the window
+ // being resized, end up with the window lagging a frame or two
+ // behind the item. Polish synchronously instead, to mitigate
+ // this, even if it may result in the opposite situation.
+ q->ensurePolished();
+ } else {
+ q->polish();
+ }
+ }
+
+ return QQuickItemPrivate::transformChanged(transformedItem);
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/items/qquickwindowcontainer_p.h b/src/quick/items/qquickwindowcontainer_p.h
new file mode 100644
index 0000000000..5da0c6ba27
--- /dev/null
+++ b/src/quick/items/qquickwindowcontainer_p.h
@@ -0,0 +1,79 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQUICKWINDOWCONTAINER_P_H
+#define QQUICKWINDOWCONTAINER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtQuick/private/qtquickglobal_p.h>
+
+#include <QtCore/private/qobject_p.h>
+
+#include <QtQuick/private/qquickimplicitsizeitem_p.h>
+#include <QtQuick/qquickwindow.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQuickWindowContainerPrivate;
+class Q_QUICK_EXPORT QQuickWindowContainer : public QQuickImplicitSizeItem
+{
+ Q_OBJECT
+ QML_NAMED_ELEMENT(WindowContainer)
+ Q_PROPERTY(QWindow *window READ containedWindow WRITE setContainedWindow
+ NOTIFY containedWindowChanged DESIGNABLE false FINAL)
+
+ QML_ADDED_IN_VERSION(6, 7)
+
+public:
+ enum ContainerMode {
+ WindowControlsItem,
+ ItemControlsWindow
+ };
+
+ explicit QQuickWindowContainer(QQuickItem *parent = nullptr,
+ ContainerMode containerMode = ItemControlsWindow);
+ ~QQuickWindowContainer();
+
+ QWindow *containedWindow() const;
+ void setContainedWindow(QWindow *window);
+ Q_SIGNAL void containedWindowChanged(QWindow *window);
+
+protected:
+ void classBegin() override;
+ void componentComplete() override;
+
+ void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
+ void itemChange(QQuickItem::ItemChange, const QQuickItem::ItemChangeData &) override;
+
+ void updatePolish() override;
+
+ bool eventFilter(QObject *object, QEvent *event) override;
+
+ QRectF clipRect() const override;
+
+ void releaseResources() override;
+
+private:
+ Q_DECLARE_PRIVATE(QQuickWindowContainer)
+ friend class QQuickWindowQmlImpl;
+
+ void initializeContainedWindow();
+ void windowUpdated();
+ void syncWindowToItem();
+ void parentWindowChanged(QQuickWindow *window);
+ void windowDestroyed();
+};
+
+QT_END_NAMESPACE
+
+#endif // QQUICKWINDOWCONTAINER_P_H
diff --git a/src/quick/items/qquickwindowmodule.cpp b/src/quick/items/qquickwindowmodule.cpp
index 8a815569fe..ddcb588c04 100644
--- a/src/quick/items/qquickwindowmodule.cpp
+++ b/src/quick/items/qquickwindowmodule.cpp
@@ -20,7 +20,9 @@
QT_BEGIN_NAMESPACE
-Q_DECLARE_LOGGING_CATEGORY(lcTransient)
+using namespace Qt::StringLiterals;
+
+Q_STATIC_LOGGING_CATEGORY(lcTransient, "qt.quick.window.transient")
QQuickWindowQmlImplPrivate::QQuickWindowQmlImplPrivate() = default;
@@ -29,30 +31,42 @@ QQuickWindowQmlImpl::QQuickWindowQmlImpl(QWindow *parent)
{
}
-void QQuickWindowQmlImpl::setVisible(bool visible)
+QQuickWindowQmlImpl::QQuickWindowQmlImpl(QQuickWindowQmlImplPrivate &dd, QWindow *parent)
+ : QQuickWindow(dd, parent)
{
- Q_D(QQuickWindowQmlImpl);
- d->visible = visible;
- if (d->complete && (!transientParent() || transientParentVisible()))
- QQuickWindow::setVisible(visible);
-}
+ connect(this, &QWindow::visibleChanged, this, &QQuickWindowQmlImpl::visibleChanged);
+ connect(this, &QWindow::visibilityChanged, this, [&]{
+ Q_D(QQuickWindowQmlImpl);
+ // Update the window's actual visibility and turn off visibilityExplicitlySet,
+ // so that future applyWindowVisibility() calls do not apply both window state
+ // and visible state, unless setVisibility() is called again by the user.
+ d->visibility = QWindow::visibility();
+ d->visibilityExplicitlySet = false;
+ emit QQuickWindowQmlImpl::visibilityChanged(d->visibility);
+ });
+ connect(this, &QWindow::screenChanged, this, &QQuickWindowQmlImpl::screenChanged);
-void QQuickWindowQmlImpl::setVisibility(Visibility visibility)
-{
- Q_D(QQuickWindowQmlImpl);
- d->visibility = visibility;
- if (d->complete)
- QQuickWindow::setVisibility(visibility);
+ // We shadow the x and y properties, so that we can re-map them in case
+ // we have an Item as our visual parent, which will result in creating an
+ // implicit window container that we control. Ensure that signals still work,
+ // and that they reflect the mapped values if a window container is used.
+ QObject::connect(this, &QWindow::xChanged, this, [this] { emit xChanged(x()); });
+ QObject::connect(this, &QWindow::yChanged, this, [this] { emit yChanged(y()); });
}
-QQuickWindowAttached *QQuickWindowQmlImpl::qmlAttachedProperties(QObject *object)
+QQuickWindowQmlImpl::~QQuickWindowQmlImpl()
{
- return new QQuickWindowAttached(object);
+ // Destroy the window while we are still alive, so that any signals
+ // emitted by the destruction can be delivered properly.
+ destroy();
}
void QQuickWindowQmlImpl::classBegin()
{
Q_D(QQuickWindowQmlImpl);
+ qCDebug(lcQuickWindow) << "Class begin for" << this;
+ d->componentComplete = false;
+
QQmlEngine* e = qmlEngine(this);
QQmlEngine::setContextForObject(contentItem(), e->rootContext());
@@ -66,89 +80,453 @@ void QQuickWindowQmlImpl::classBegin()
// The content item has CppOwnership policy (set in QQuickWindow). Ensure the presence of a JS
// wrapper so that the garbage collector can see the policy.
QV4::ExecutionEngine *v4 = e->handle();
- QV4::QObjectWrapper::wrap(v4, d->contentItem);
+ QV4::QObjectWrapper::ensureWrapper(v4, d->contentItem);
}
}
void QQuickWindowQmlImpl::componentComplete()
{
Q_D(QQuickWindowQmlImpl);
- d->complete = true;
- QQuickItem *itemParent = qmlobject_cast<QQuickItem *>(QObject::parent());
- const bool transientParentAlreadySet = QQuickWindowPrivate::get(this)->transientParentPropertySet;
- if (!transientParentAlreadySet && itemParent && !itemParent->window()) {
- qCDebug(lcTransient) << "window" << title() << "has invisible Item parent" << itemParent << "transientParent"
- << transientParent() << "declared visibility" << d->visibility << "; delaying show";
- connect(itemParent, &QQuickItem::windowChanged, this,
- &QQuickWindowQmlImpl::setWindowVisibility, Qt::QueuedConnection);
- } else if (transientParent() && !transientParent()->isVisible()) {
- connect(transientParent(), &QQuickWindow::visibleChanged, this,
- &QQuickWindowQmlImpl::setWindowVisibility, Qt::QueuedConnection);
- } else {
- setWindowVisibility();
- }
+ qCDebug(lcQuickWindow) << "Component completed for" << this;
+ d->componentComplete = true;
+
+ applyVisualParent();
+
+ // Apply automatic transient parent if needed, and opt in to future
+ // parent change events, so we can keep the transient parent in sync.
+ updateTransientParent();
+ d->receiveParentEvents = true;
+
+ applyWindowVisibility();
+
+ // If the transient parent changes, and we've deferred making
+ // the window visible, we need to re-evaluate our decision.
+ connect(this, &QWindow::transientParentChanged,
+ this, &QQuickWindowQmlImpl::applyWindowVisibility);
}
-QQuickWindowQmlImpl::QQuickWindowQmlImpl(QQuickWindowQmlImplPrivate &dd, QWindow *parent)
- : QQuickWindow(dd, parent)
+void QQuickWindowQmlImpl::setVisible(bool visible)
{
- // These two signals are called during QWindow's dtor, thus they have to be queued connections
- // or else our slots will be called instantly when our destructor has already run but our
- // connections haven't been removed yet.
- connect(this, &QWindow::visibleChanged, this, &QQuickWindowQmlImpl::visibleChanged,
- Qt::QueuedConnection);
- connect(this, &QWindow::visibilityChanged, this, &QQuickWindowQmlImpl::visibilityChanged,
- Qt::QueuedConnection);
+ Q_D(QQuickWindowQmlImpl);
+ d->visible = visible;
+ d->visibleExplicitlySet = true;
+ if (d->componentComplete)
+ applyWindowVisibility();
+}
- connect(this, &QWindow::screenChanged, this, &QQuickWindowQmlImpl::screenChanged);
+void QQuickWindowQmlImpl::setVisibility(Visibility visibility)
+{
+ Q_D(QQuickWindowQmlImpl);
+ d->visibility = visibility;
+ d->visibilityExplicitlySet = true;
+ if (d->componentComplete)
+ applyWindowVisibility();
+}
+
+bool QQuickWindowQmlImpl::event(QEvent *event)
+{
+ Q_D(QQuickWindowQmlImpl);
+
+ if (event->type() == QEvent::ParentWindowChange) {
+ qCDebug(lcQuickWindow) << "Parent of" << this << "changed to" << parent();
+ if (d->visualParent) {
+ // If the window parent changes, and we've deferred making
+ // the window visible, we need to re-evaluate our decision.
+ applyWindowVisibility();
+ } else {
+ QObject::disconnect(d->itemParentWindowChangeListener);
+ updateTransientParent();
+ }
+ }
+ return QQuickWindow::event(event);
}
-void QQuickWindowQmlImpl::setWindowVisibility()
+/*
+ Update the transient parent of the window based on its
+ QObject parent (Item or Window), unless the user has
+ set an explicit transient parent.
+*/
+void QQuickWindowQmlImpl::updateTransientParent()
{
Q_D(QQuickWindowQmlImpl);
- if (transientParent() && !transientParentVisible())
+
+ // We defer updating the transient parent until the component
+ // has been fully completed, and we know whether an explicit
+ // transient parent has been set.
+ if (!d->componentComplete)
return;
- if (QQuickItem *senderItem = qmlobject_cast<QQuickItem *>(sender())) {
- disconnect(senderItem, &QQuickItem::windowChanged, this, &QQuickWindowQmlImpl::setWindowVisibility);
- } else if (sender()) {
- disconnect(transientParent(), &QWindow::visibleChanged, this, &QQuickWindowQmlImpl::setWindowVisibility);
+ // If an explicit transient parent has been set,
+ // we don't want to apply our magic.
+ if (d->transientParentPropertySet)
+ return;
+
+ // Nor if we have a visual parent that makes this a true child window
+ if (d->visualParent)
+ return;
+
+ auto *objectParent = QObject::parent();
+ qCDebug(lcTransient) << "Applying transient parent magic to"
+ << this << "based on object parent" << objectParent << "🪄";
+
+ QWindow *transientParent = nullptr;
+ if (auto *windowParent = qmlobject_cast<QWindow *>(objectParent)) {
+ transientParent = windowParent;
+ } else if (auto *itemParent = qmlobject_cast<QQuickItem *>(objectParent)) {
+ if (!d->itemParentWindowChangeListener) {
+ d->itemParentWindowChangeListener = connect(
+ itemParent, &QQuickItem::windowChanged,
+ this, &QQuickWindowQmlImpl::updateTransientParent);
+ }
+ transientParent = itemParent->window();
}
- // We have deferred window creation until we have the full picture of what
- // the user wanted in terms of window state, geometry, visibility, etc.
+ if (!transientParent) {
+ qCDebug(lcTransient) << "No transient parent resolved from object parent";
+ return;
+ }
- if ((d->visibility == Hidden && d->visible) || (d->visibility > AutomaticVisibility && !d->visible)) {
- QQmlData *data = QQmlData::get(this);
- Q_ASSERT(data && data->context);
+ qCDebug(lcTransient) << "Setting" << transientParent << "as transient parent of" << this;
+ setTransientParent(transientParent);
- QQmlError error;
- error.setObject(this);
+ // We want to keep applying the automatic transient parent
+ d->transientParentPropertySet = false;
+}
- QQmlRefPointer<QQmlContextData> urlContext = data->context;
- while (urlContext && urlContext->url().isEmpty())
- urlContext = urlContext->parent();
- error.setUrl(urlContext ? urlContext->url() : QUrl());
+void QQuickWindowQmlImpl::applyWindowVisibility()
+{
+ Q_D(QQuickWindowQmlImpl);
- QString objectId = data->context->findObjectId(this);
- if (!objectId.isEmpty())
- error.setDescription(QCoreApplication::translate("QQuickWindowQmlImpl",
- "Conflicting properties 'visible' and 'visibility' for Window '%1'").arg(objectId));
- else
- error.setDescription(QCoreApplication::translate("QQuickWindowQmlImpl",
- "Conflicting properties 'visible' and 'visibility'"));
+ Q_ASSERT(d->componentComplete);
- QQmlEnginePrivate::get(data->context->engine())->warning(error);
+ const bool visible = d->visibilityExplicitlySet
+ ? d->visibility != Hidden : d->visible;
+
+ qCDebug(lcQuickWindow) << "Applying visible" << visible << "for" << this;
+
+ if (visible) {
+ if (d->visualParent) {
+ // Even though we're complete, and have a visual parent set,
+ // we may not be part of a window yet, or we may have been
+ // removed from a window that's going away. Showing this window
+ // now would make it a top level, which is not what we want.
+ if (!QWindow::parent()) {
+ qCDebug(lcQuickWindow) << "Waiting for visual parent to reparent us into a window";
+ // We apply the visibility again on ParentWindowChange
+ return;
+ }
+ } else {
+ // Handle deferred visibility due to possible transient parent
+ auto *itemParent = qmlobject_cast<QQuickItem *>(QObject::parent());
+ if (!d->transientParentPropertySet && itemParent && !itemParent->window()) {
+ qCDebug(lcTransient) << "Waiting for parent" << itemParent << "to resolve"
+ << "its window. Deferring visibility";
+ return;
+ }
+
+ const QWindow *transientParent = QWindow::transientParent();
+ if (transientParent && !transientParentVisible()) {
+ // Defer visibility of this window until the transient parent has
+ // been made visible, or we've get a new transient parent.
+ qCDebug(lcTransient) << "Transient parent" << transientParent
+ << "not visible yet. Deferring visibility";
+
+ // QWindowPrivate::setVisible emits visibleChanged _before_ actually
+ // propagating the visibility to the platform window, so we can't use
+ // a direct connection here, as that would result in showing this
+ // window before the transient parent.
+ connect(transientParent, &QQuickWindow::visibleChanged, this,
+ &QQuickWindowQmlImpl::applyWindowVisibility,
+ Qt::ConnectionType(Qt::QueuedConnection | Qt::SingleShotConnection));
+ return;
+ }
+ }
+ }
+
+ if (d->visibleExplicitlySet && d->visibilityExplicitlySet &&
+ ((d->visibility == Hidden && d->visible) ||
+ (d->visibility > AutomaticVisibility && !d->visible))) {
+ // FIXME: Should we bail out in this case?
+ qmlWarning(this) << "Conflicting properties 'visible' and 'visibility'";
}
if (d->visibility == AutomaticVisibility) {
- setWindowState(QGuiApplicationPrivate::platformIntegration()->defaultWindowState(flags()));
- setVisible(d->visible);
+ // We're either showing for the first time, with the default
+ // visibility of AutomaticVisibility, or the user has called
+ // setVisibility with AutomaticVisibility at some point, so
+ // apply both window state and visible.
+ if (QWindow::parent() || visualParent())
+ setWindowState(Qt::WindowNoState);
+ else
+ setWindowState(QGuiApplicationPrivate::platformIntegration()->defaultWindowState(flags()));
+ QQuickWindow::setVisible(d->visible);
+ } else if (d->visibilityExplicitlySet) {
+ // We're not AutomaticVisibility, but the user has requested
+ // an explicit visibility, so apply both window state and visible.
+ QQuickWindow::setVisibility(d->visibility);
+ } else {
+ // Our window state should be up to date, so only apply visible
+ QQuickWindow::setVisible(d->visible);
+ }
+}
+
+bool QQuickWindowQmlImpl::transientParentVisible()
+{
+ Q_ASSERT(transientParent());
+ if (!transientParent()->isVisible()) {
+ // handle case where transient parent is offscreen window
+ QWindow *rw = QQuickRenderControl::renderWindowFor(qobject_cast<QQuickWindow*>(transientParent()));
+ return rw && rw->isVisible();
+ }
+ return true;
+}
+
+// -------------------------- Visual Parent ---------------------------
+
+/*!
+ \qmlproperty var QtQuick::Window::parent
+ \since 6.7
+ \internal
+
+ This property holds the visual parent of the window.
+
+ The visual parent can be either another Window, or an Item.
+
+ A window with a visual parent will result in the window becoming a child
+ window of its visual parent, either directly if the visual parent is another
+ Window, or indirectly via the visual parent Item's window.
+
+ Just like QtQuick::Item::parent, the window will be positioned relative to
+ its visual parent.
+
+ The stacking order between sibling Windows follows the document order,
+ just like Items, but can be customized via the Window's \l{QtQuick::Window::z}
+ {z-order} property.
+
+ Setting a visual parent on a Window will take precedence over the
+ \l{QtQuick::Window::transientParent}{transient parent}.
+
+ \sa{Concepts - Visual Parent in Qt Quick}, transientParent
+*/
+
+void QQuickWindowQmlImpl::setVisualParent(QObject *visualParent)
+{
+ Q_D(QQuickWindowQmlImpl);
+ if (visualParent == d->visualParent)
+ return;
+
+ qCDebug(lcQuickWindow) << "Setting visual parent of" << this << "to" << visualParent;
+
+ if (d->visualParent) {
+ // Disconnect from deferred window listener
+ d->visualParent->disconnect(this);
+ }
+
+ d->visualParent = visualParent;
+
+ if (d->componentComplete)
+ applyVisualParent();
+
+ emit visualParentChanged(d->visualParent);
+}
+
+void QQuickWindowQmlImpl::applyVisualParent()
+{
+ Q_D(QQuickWindowQmlImpl);
+ Q_ASSERT(d->componentComplete);
+
+ qCDebug(lcQuickWindow) << "Applying" << this << "visual parent" << d->visualParent;
+
+ if (!d->visualParent) {
+ if (d->windowContainer) {
+ d->windowContainer->setContainedWindow(nullptr);
+ delete std::exchange(d->windowContainer, nullptr);
+ }
+ QQuickWindow::setParent(nullptr);
+ return;
+ }
+
+ QQuickItem *parentItem = nullptr;
+ if ((parentItem = qobject_cast<QQuickItem*>(d->visualParent)))
+ ; // All good, can use directly
+ else if (auto *parentWindow = qobject_cast<QWindow*>(d->visualParent)) {
+ if (auto *parentQuickWindow = qobject_cast<QQuickWindow*>(parentWindow)) {
+ parentItem = parentQuickWindow->contentItem();
+ } else {
+ qmlWarning(this) << "Parenting into non-Quick window. "
+ << "Stacking, position, and destruction must be handled manually";
+ QQuickWindow::setParent(parentWindow); // Try our best
+ return;
+ }
+ }
+
+ if (!parentItem) {
+ qmlWarning(this) << "Unsupported visual parent type"
+ << d->visualParent->metaObject()->className();
+ return;
+ }
+
+ if (!parentItem->window()) {
+ qCDebug(lcQuickWindow) << "No window yet. Deferring.";
+ connect(parentItem, &QQuickItem::windowChanged, this, [this]{
+ qCDebug(lcQuickWindow) << "Got window. Applying deferred visual parent item.";
+ applyVisualParent();
+ }, Qt::SingleShotConnection);
+ return;
+ }
+
+ if (qobject_cast<QQuickWindowContainer*>(d->visualParent)) {
+ qCDebug(lcQuickWindow) << "Visual parent is window container, everything is in order";
+ return;
+ }
+
+ if (!d->windowContainer) {
+ d->windowContainer = new QQuickWindowContainer(parentItem,
+ QQuickWindowContainer::WindowControlsItem);
+ d->windowContainer->setObjectName(objectName() + "Container"_L1);
+
+ auto *objectParent = this->QObject::parent();
+ if (objectParent == parentItem) {
+ // We want to reflect the QML document order of sibling windows in the
+ // resulting stacking order of the windows. We can do so by carefully
+ // using the the information we have about the child object order.
+
+ // We know that the window's object child index is correct in relation
+ // to the other child windows of the parent. Since the window container
+ // is going to represent the window from now on, make the window container
+ // take the window's place in the parent's child object list.
+ auto &objectChildren = QObjectPrivate::get(objectParent)->children;
+ auto windowIndex = objectChildren.indexOf(this);
+ auto containerIndex = objectChildren.indexOf(d->windowContainer);
+ objectChildren.move(containerIndex, windowIndex);
+ containerIndex = windowIndex;
+
+ // The parent's item children are unfortunately managed separately from
+ // the object children. But thanks to the logic above we can use the now
+ // correct object order of the window container in the object children list
+ // to also ensure a correct stacking order between the sibling child items.
+ for (int i = containerIndex + 1; i < objectChildren.size(); ++i) {
+ if (auto *childItem = qobject_cast<QQuickItem*>(objectChildren.at(i))) {
+ qCDebug(lcQuickWindow) << "Stacking" << d->windowContainer
+ << "below" << childItem;
+ d->windowContainer->stackBefore(childItem);
+ break;
+ }
+ }
+ } else {
+ // Having another visual parent than the direct object parent will
+ // mess up the stacking order. This is also the case for normal items.
+ qCDebug(lcQuickWindow) << "Visual parent is not object parent."
+ << "Can not reflect document order as stacking order.";
+ }
+
+ QQmlEngine::setContextForObject(d->windowContainer, qmlContext(this));
+
+ d->windowContainer->classBegin();
+ d->windowContainer->setContainedWindow(this);
+ // Once the window has a window container, all x/y/z changes of
+ // the window will go through the container, and ensure the
+ // correct mapping. But any changes that happened prior to
+ // this have not been mapped yet, so do that now.
+ d->windowContainer->setPosition(position());
+ d->windowContainer->setZ(d->z);
+ d->windowContainer->componentComplete();
+
+ QObject::connect(d->windowContainer, &QQuickItem::zChanged,
+ this, &QQuickWindowQmlImpl::zChanged);
} else {
- setVisibility(d->visibility);
+ d->windowContainer->setParentItem(parentItem);
}
}
+QObject *QQuickWindowQmlImpl::visualParent() const
+{
+ Q_D(const QQuickWindowQmlImpl);
+ return d->visualParent;
+}
+
+// We shadow the x and y properties of the Window, so that in case
+// the window has an Item as its visual parent we can re-map the
+// coordinates via the corresponding window container. We need to
+// do this also for the signal emissions, as otherwise the Window's
+// change signals will reflect different values than what we report
+// via the accessors. It would be nicer if this logic was contained
+// in the window container, for example via meta object property
+// interception, but that does not allow intercepting signal emissions.
+
+void QQuickWindowQmlImpl::setX(int x)
+{
+ Q_D(QQuickWindowQmlImpl);
+ if (Q_UNLIKELY(d->windowContainer && d->windowContainer->window()))
+ d->windowContainer->setX(x);
+ else
+ QQuickWindow::setX(x);
+}
+
+int QQuickWindowQmlImpl::x() const
+{
+ Q_D(const QQuickWindowQmlImpl);
+ if (Q_UNLIKELY(d->windowContainer && d->windowContainer->window()))
+ return d->windowContainer->x();
+ else
+ return QQuickWindow::x();
+}
+
+void QQuickWindowQmlImpl::setY(int y)
+{
+ Q_D(QQuickWindowQmlImpl);
+ if (Q_UNLIKELY(d->windowContainer && d->windowContainer->window()))
+ d->windowContainer->setY(y);
+ else
+ QQuickWindow::setY(y);
+}
+
+int QQuickWindowQmlImpl::y() const
+{
+ Q_D(const QQuickWindowQmlImpl);
+ if (Q_UNLIKELY(d->windowContainer && d->windowContainer->window()))
+ return d->windowContainer->y();
+ else
+ return QQuickWindow::y();
+}
+
+/*!
+ \qmlproperty real QtQuick::Window::z
+ \internal
+
+ Sets the stacking order of sibling windows.
+
+ By default the stacking order is 0.
+
+ Windows with a higher stacking value are drawn on top of windows with a
+ lower stacking order. Windows with the same stacking value are drawn
+ bottom up in the order they appear in the QML document.
+
+ \note This property only has an effect for child windows.
+
+ \sa QtQuick::Item::z
+*/
+
+void QQuickWindowQmlImpl::setZ(qreal z)
+{
+ Q_D(QQuickWindowQmlImpl);
+ if (Q_UNLIKELY(d->windowContainer && d->windowContainer->window()))
+ d->windowContainer->setZ(z);
+ else
+ d->z = z;
+}
+
+qreal QQuickWindowQmlImpl::z() const
+{
+ Q_D(const QQuickWindowQmlImpl);
+ if (Q_UNLIKELY(d->windowContainer && d->windowContainer->window()))
+ return d->windowContainer->z();
+ else
+ return d->z;
+}
+
+// --------------------------------------------------------------------
+
QObject *QQuickWindowQmlImpl::screen() const
{
return new QQuickScreenInfo(const_cast<QQuickWindowQmlImpl *>(this), QWindow::screen());
@@ -160,15 +538,9 @@ void QQuickWindowQmlImpl::setScreen(QObject *screen)
QWindow::setScreen(screenWrapper ? screenWrapper->wrappedScreen() : nullptr);
}
-bool QQuickWindowQmlImpl::transientParentVisible()
+QQuickWindowAttached *QQuickWindowQmlImpl::qmlAttachedProperties(QObject *object)
{
- Q_ASSERT(transientParent());
- if (!transientParent()->isVisible()) {
- // handle case where transient parent is offscreen window
- QWindow *rw = QQuickRenderControl::renderWindowFor(qobject_cast<QQuickWindow*>(transientParent()));
- return rw && rw->isVisible();
- }
- return true;
+ return new QQuickWindowAttached(object);
}
QT_END_NAMESPACE
diff --git a/src/quick/items/qquickwindowmodule_p.h b/src/quick/items/qquickwindowmodule_p.h
index c64ed537f7..709e373d0b 100644
--- a/src/quick/items/qquickwindowmodule_p.h
+++ b/src/quick/items/qquickwindowmodule_p.h
@@ -32,7 +32,7 @@ struct QWindowForeign
QML_ADDED_IN_VERSION(2, 1)
};
-class Q_QUICK_PRIVATE_EXPORT QQuickWindowQmlImpl : public QQuickWindow, public QQmlParserStatus
+class Q_QUICK_EXPORT QQuickWindowQmlImpl : public QQuickWindow, public QQmlParserStatus
{
Q_OBJECT
Q_INTERFACES(QQmlParserStatus)
@@ -47,6 +47,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickWindowQmlImpl : public QQuickWindow, public Q
public:
QQuickWindowQmlImpl(QWindow *parent = nullptr);
+ ~QQuickWindowQmlImpl();
void setVisible(bool visible);
void setVisibility(QWindow::Visibility visibility);
@@ -54,6 +55,18 @@ public:
QObject *screen() const;
void setScreen(QObject *screen);
+ QObject *visualParent() const;
+ void setVisualParent(QObject *parent);
+ void visualParentChanged(QObject *) {};
+
+ void setX(int arg);
+ int x() const;
+ void setY(int arg);
+ int y() const;
+ void setZ(qreal arg);
+ qreal z() const;
+ void zChanged() {};
+
static QQuickWindowAttached *qmlAttachedProperties(QObject *object);
Q_SIGNALS:
@@ -61,17 +74,24 @@ Q_SIGNALS:
void visibilityChanged(QWindow::Visibility visibility);
Q_REVISION(2, 3) void screenChanged();
+ void xChanged(int arg);
+ void yChanged(int arg);
+
protected:
void classBegin() override;
void componentComplete() override;
+ bool event(QEvent *) override;
+
QQuickWindowQmlImpl(QQuickWindowQmlImplPrivate &dd, QWindow *parent);
private Q_SLOTS:
- void setWindowVisibility();
+ Q_REVISION(6, 7) void applyWindowVisibility();
+ Q_REVISION(6, 7) void updateTransientParent();
private:
bool transientParentVisible();
+ void applyVisualParent();
private:
Q_DISABLE_COPY(QQuickWindowQmlImpl)
@@ -80,6 +100,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickWindowQmlImpl)
-
#endif
diff --git a/src/quick/items/qquickwindowmodule_p_p.h b/src/quick/items/qquickwindowmodule_p_p.h
index 3057cfa2c2..227b8aa01e 100644
--- a/src/quick/items/qquickwindowmodule_p_p.h
+++ b/src/quick/items/qquickwindowmodule_p_p.h
@@ -16,18 +16,29 @@
#include "qquickwindow_p.h"
#include <QtQml/private/qv4persistent_p.h>
+#include "qquickwindowcontainer_p.h"
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QQuickWindowQmlImplPrivate : public QQuickWindowPrivate
+class Q_QUICK_EXPORT QQuickWindowQmlImplPrivate : public QQuickWindowPrivate
{
public:
QQuickWindowQmlImplPrivate();
- bool complete = false;
+ bool componentComplete = true;
+
bool visible = false;
+ bool visibleExplicitlySet = false;
QQuickWindow::Visibility visibility = QQuickWindow::AutomaticVisibility;
+ bool visibilityExplicitlySet = false;
+
QV4::PersistentValue rootItemMarker;
+
+ QMetaObject::Connection itemParentWindowChangeListener;
+
+ QObject *visualParent = nullptr;
+ QPointer<QQuickWindowContainer> windowContainer;
+ qreal z = 0.0;
};
QT_END_NAMESPACE
diff --git a/src/quick/items/qquicktextnode.cpp b/src/quick/items/qsginternaltextnode.cpp
index 171cbdee8b..32fde98b9f 100644
--- a/src/quick/items/qquicktextnode.cpp
+++ b/src/quick/items/qsginternaltextnode.cpp
@@ -1,7 +1,7 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-#include "qquicktextnode_p.h"
+#include "qsginternaltextnode_p.h"
#include "qquicktextnodeengine_p.h"
@@ -9,13 +9,12 @@
#include <private/qsgdistancefieldglyphnode_p.h>
#include <private/qquickclipnode_p.h>
#include <private/qquickitem_p.h>
-#include <QtQuick/private/qsgcontext_p.h>
+#include <private/qquicktextdocument_p.h>
#include <QtCore/qpoint.h>
#include <qtextdocument.h>
#include <qtextlayout.h>
#include <qabstracttextdocumentlayout.h>
-#include <qxmlstream.h>
#include <private/qquickstyledtext_p.h>
#include <private/qquicktext_p_p.h>
#include <private/qfont_p.h>
@@ -26,57 +25,47 @@
QT_BEGIN_NAMESPACE
-namespace {
-
- class ProtectedLayoutAccessor: public QAbstractTextDocumentLayout
- {
- public:
- inline QTextCharFormat formatAccessor(int pos)
- {
- return format(pos);
- }
- };
-
-}
-
-Q_DECLARE_LOGGING_CATEGORY(lcVP)
-
/*!
- Creates an empty QQuickTextNode
+ Creates an empty QSGInternalTextNode
*/
-QQuickTextNode::QQuickTextNode(QQuickItem *ownerElement)
- : m_cursorNode(nullptr), m_ownerElement(ownerElement), m_useNativeRenderer(false), m_renderTypeQuality(-1)
+QSGInternalTextNode::QSGInternalTextNode(QSGRenderContext *renderContext)
+ : m_renderContext(renderContext)
{
#ifdef QSG_RUNTIME_DESCRIPTION
qsgnode_set_description(this, QLatin1String("text"));
#endif
+
+ static_assert(int(QSGTextNode::Normal) == int(QQuickText::Normal));
+ static_assert(int(QSGTextNode::Outline) == int(QQuickText::Outline));
+ static_assert(int(QSGTextNode::Raised) == int(QQuickText::Raised));
+ static_assert(int(QSGTextNode::Sunken) == int(QQuickText::Sunken));
+
+ static_assert(int(QSGTextNode::QtRendering) == int(QQuickText::QtRendering));
+ static_assert(int(QSGTextNode::NativeRendering) == int(QQuickText::NativeRendering));
+ static_assert(int(QSGTextNode::CurveRendering) == int(QQuickText::CurveRendering));
}
-QQuickTextNode::~QQuickTextNode()
+QSGInternalTextNode::~QSGInternalTextNode()
{
qDeleteAll(m_textures);
}
-QSGGlyphNode *QQuickTextNode::addGlyphs(const QPointF &position, const QGlyphRun &glyphs, const QColor &color,
- QQuickText::TextStyle style, const QColor &styleColor,
- QSGNode *parentNode)
+QSGGlyphNode *QSGInternalTextNode::addGlyphs(const QPointF &position, const QGlyphRun &glyphs, const QColor &color,
+ QQuickText::TextStyle style, const QColor &styleColor,
+ QSGNode *parentNode)
{
- QSGRenderContext *sg = QQuickItemPrivate::get(m_ownerElement)->sceneGraphRenderContext();
QRawFont font = glyphs.rawFont();
- bool preferNativeGlyphNode = m_useNativeRenderer;
- if (!preferNativeGlyphNode) {
- QRawFontPrivate *fontPriv = QRawFontPrivate::get(font);
- if (fontPriv->fontEngine->hasUnreliableGlyphOutline()) {
- preferNativeGlyphNode = true;
- } else {
- QFontEngine *fe = QRawFontPrivate::get(font)->fontEngine;
- preferNativeGlyphNode = !fe->isSmoothlyScalable;
- }
- }
- QSGGlyphNode *node = sg->sceneGraphContext()->createGlyphNode(sg, preferNativeGlyphNode, m_renderTypeQuality);
+ QSGTextNode::RenderType preferredRenderType = m_renderType;
+ if (m_renderType != NativeRendering) {
+ if (const QFontEngine *fe = QRawFontPrivate::get(font)->fontEngine)
+ if (fe->hasUnreliableGlyphOutline() || !fe->isSmoothlyScalable)
+ preferredRenderType = QSGTextNode::NativeRendering;
+ }
- node->setOwnerElement(m_ownerElement);
+ QSGGlyphNode *node = m_renderContext->sceneGraphContext()->createGlyphNode(m_renderContext,
+ preferredRenderType,
+ m_renderTypeQuality);
node->setGlyphs(position + QPointF(0, glyphs.rawFont().ascent()), glyphs);
node->setStyle(style);
node->setStyleColor(styleColor);
@@ -97,8 +86,9 @@ QSGGlyphNode *QQuickTextNode::addGlyphs(const QPointF &position, const QGlyphRun
parentNode->appendChildNode(node);
if (style == QQuickText::Outline && color.alpha() > 0 && styleColor != color) {
- QSGGlyphNode *fillNode = sg->sceneGraphContext()->createGlyphNode(sg, preferNativeGlyphNode, m_renderTypeQuality);
- fillNode->setOwnerElement(m_ownerElement);
+ QSGGlyphNode *fillNode = m_renderContext->sceneGraphContext()->createGlyphNode(m_renderContext,
+ preferredRenderType,
+ m_renderTypeQuality);
fillNode->setGlyphs(position + QPointF(0, glyphs.rawFont().ascent()), glyphs);
fillNode->setStyle(QQuickText::Normal);
fillNode->setPreferredAntialiasingMode(QSGGlyphNode::GrayAntialiasing);
@@ -115,17 +105,16 @@ QSGGlyphNode *QQuickTextNode::addGlyphs(const QPointF &position, const QGlyphRun
return node;
}
-void QQuickTextNode::setCursor(const QRectF &rect, const QColor &color)
+void QSGInternalTextNode::setCursor(const QRectF &rect, const QColor &color)
{
if (m_cursorNode != nullptr)
delete m_cursorNode;
- QSGRenderContext *sg = QQuickItemPrivate::get(m_ownerElement)->sceneGraphRenderContext();
- m_cursorNode = sg->sceneGraphContext()->createInternalRectangleNode(rect, color);
+ m_cursorNode = m_renderContext->sceneGraphContext()->createInternalRectangleNode(rect, color);
appendChildNode(m_cursorNode);
}
-void QQuickTextNode::clearCursor()
+void QSGInternalTextNode::clearCursor()
{
if (m_cursorNode)
removeChildNode(m_cursorNode);
@@ -133,42 +122,38 @@ void QQuickTextNode::clearCursor()
m_cursorNode = nullptr;
}
-void QQuickTextNode::addRectangleNode(const QRectF &rect, const QColor &color)
+void QSGInternalTextNode::addDecorationNode(const QRectF &rect, const QColor &color)
{
- QSGRenderContext *sg = QQuickItemPrivate::get(m_ownerElement)->sceneGraphRenderContext();
- appendChildNode(sg->sceneGraphContext()->createInternalRectangleNode(rect, color));
+ addRectangleNode(rect, color);
}
+void QSGInternalTextNode::addRectangleNode(const QRectF &rect, const QColor &color)
+{
+ appendChildNode(m_renderContext->sceneGraphContext()->createInternalRectangleNode(rect, color));
+}
-void QQuickTextNode::addImage(const QRectF &rect, const QImage &image)
+void QSGInternalTextNode::addImage(const QRectF &rect, const QImage &image)
{
- QSGRenderContext *sg = QQuickItemPrivate::get(m_ownerElement)->sceneGraphRenderContext();
- QSGInternalImageNode *node = sg->sceneGraphContext()->createInternalImageNode(sg);
- QSGTexture *texture = sg->createTexture(image);
- if (m_ownerElement->smooth())
- texture->setFiltering(QSGTexture::Linear);
+ QSGInternalImageNode *node = m_renderContext->sceneGraphContext()->createInternalImageNode(m_renderContext);
+ QSGTexture *texture = m_renderContext->createTexture(image);
+ texture->setFiltering(m_filtering);
m_textures.append(texture);
node->setTargetRect(rect);
node->setInnerTargetRect(rect);
node->setTexture(texture);
- if (m_ownerElement->smooth())
- node->setFiltering(QSGTexture::Linear);
+ node->setFiltering(m_filtering);
appendChildNode(node);
node->update();
}
-void QQuickTextNode::addTextDocument(const QPointF &position, QTextDocument *textDocument,
- const QColor &textColor,
- QQuickText::TextStyle style, const QColor &styleColor,
- const QColor &anchorColor,
- const QColor &selectionColor, const QColor &selectedTextColor,
- int selectionStart, int selectionEnd)
+void QSGInternalTextNode::doAddTextDocument(QPointF position, QTextDocument *textDocument,
+ int selectionStart, int selectionEnd)
{
QQuickTextNodeEngine engine;
- engine.setTextColor(textColor);
- engine.setSelectedTextColor(selectedTextColor);
- engine.setSelectionColor(selectionColor);
- engine.setAnchorColor(anchorColor);
+ engine.setTextColor(m_color);
+ engine.setSelectedTextColor(m_selectionTextColor);
+ engine.setSelectionColor(m_selectionColor);
+ engine.setAnchorColor(m_linkColor);
engine.setPosition(position);
QList<QTextFrame *> frames;
@@ -182,7 +167,7 @@ void QQuickTextNode::addTextDocument(const QPointF &position, QTextDocument *tex
if (textFrame->firstPosition() > textFrame->lastPosition()
&& textFrame->frameFormat().position() != QTextFrameFormat::InFlow) {
const int pos = textFrame->firstPosition() - 1;
- ProtectedLayoutAccessor *a = static_cast<ProtectedLayoutAccessor *>(textDocument->documentLayout());
+ auto *a = static_cast<QtPrivate::ProtectedLayoutAccessor *>(textDocument->documentLayout());
QTextCharFormat format = a->formatAccessor(pos);
QRectF rect = a->frameBoundingRect(textFrame);
@@ -197,29 +182,26 @@ void QQuickTextNode::addTextDocument(const QPointF &position, QTextDocument *tex
Q_ASSERT(!engine.currentLine().isValid());
QTextBlock block = it.currentBlock();
- engine.addTextBlock(textDocument, block, position, textColor, anchorColor, selectionStart, selectionEnd,
+ engine.addTextBlock(textDocument, block, position, m_color, m_linkColor, selectionStart, selectionEnd,
(textDocument->characterCount() > QQuickTextPrivate::largeTextSizeThreshold ?
- m_ownerElement->clipRect() : QRectF()));
+ m_viewport : QRectF()));
++it;
}
}
}
- engine.addToSceneGraph(this, style, styleColor);
+ engine.addToSceneGraph(this, QQuickText::TextStyle(m_textStyle), m_styleColor);
}
-void QQuickTextNode::addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color,
- QQuickText::TextStyle style, const QColor &styleColor,
- const QColor &anchorColor,
- const QColor &selectionColor, const QColor &selectedTextColor,
- int selectionStart, int selectionEnd,
- int lineStart, int lineCount)
+void QSGInternalTextNode::doAddTextLayout(QPointF position, QTextLayout *textLayout,
+ int selectionStart, int selectionEnd,
+ int lineStart, int lineCount)
{
QQuickTextNodeEngine engine;
- engine.setTextColor(color);
- engine.setSelectedTextColor(selectedTextColor);
- engine.setSelectionColor(selectionColor);
- engine.setAnchorColor(anchorColor);
+ engine.setTextColor(m_color);
+ engine.setSelectedTextColor(m_selectionTextColor);
+ engine.setSelectionColor(m_selectionColor);
+ engine.setAnchorColor(m_linkColor);
engine.setPosition(position);
#if QT_CONFIG(im)
@@ -230,12 +212,6 @@ void QQuickTextNode::addTextLayout(const QPointF &position, QTextLayout *textLay
QVarLengthArray<QTextLayout::FormatRange> colorChanges;
engine.mergeFormats(textLayout, &colorChanges);
- // If there's a lot of text, insert only the range of lines that can possibly be visible within the viewport.
- QRectF viewport;
- if (m_ownerElement->flags().testFlag(QQuickItem::ItemObservesViewport)) {
- viewport = m_ownerElement->clipRect();
- qCDebug(lcVP) << "text viewport" << viewport;
- }
lineCount = lineCount >= 0
? qMin(lineStart + lineCount, textLayout->lineCount())
: textLayout->lineCount();
@@ -255,8 +231,9 @@ void QQuickTextNode::addTextLayout(const QPointF &position, QTextLayout *textLay
end += preeditLength;
}
#endif
- if (viewport.isNull() || (line.y() + line.height() > viewport.top() && line.y() < viewport.bottom())) {
- if (!inViewport && !viewport.isNull()) {
+ // If there's a lot of text, insert only the range of lines that can possibly be visible within the viewport.
+ if (m_viewport.isNull() || (line.y() + line.height() > m_viewport.top() && line.y() < m_viewport.bottom())) {
+ if (!inViewport && !m_viewport.isNull()) {
m_firstLineInViewport = i;
qCDebug(lcVP) << "first line in viewport" << i << "@" << line.y();
}
@@ -264,17 +241,17 @@ void QQuickTextNode::addTextLayout(const QPointF &position, QTextLayout *textLay
engine.setCurrentLine(line);
engine.addGlyphsForRanges(colorChanges, start, end, selectionStart, selectionEnd);
} else if (inViewport) {
- Q_ASSERT(!viewport.isNull());
+ Q_ASSERT(!m_viewport.isNull());
m_firstLinePastViewport = i;
qCDebug(lcVP) << "first omitted line past bottom of viewport" << i << "@" << line.y();
break; // went past the bottom of the viewport, so we're done
}
}
- engine.addToSceneGraph(this, style, styleColor);
+ engine.addToSceneGraph(this, QQuickText::TextStyle(m_textStyle), m_styleColor);
}
-void QQuickTextNode::deleteContent()
+void QSGInternalTextNode::clear()
{
while (firstChild() != nullptr)
delete firstChild();
diff --git a/src/quick/items/qsginternaltextnode_p.h b/src/quick/items/qsginternaltextnode_p.h
new file mode 100644
index 0000000000..0d29657912
--- /dev/null
+++ b/src/quick/items/qsginternaltextnode_p.h
@@ -0,0 +1,199 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGINTERNALTEXTNODE_P_H
+#define QSGINTERNALTEXTNODE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qsgtextnode.h"
+#include "qquicktext_p.h"
+#include <qglyphrun.h>
+
+#include <QtGui/qcolor.h>
+#include <QtGui/qtextlayout.h>
+#include <QtCore/qvarlengtharray.h>
+#include <QtCore/qscopedpointer.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSGGlyphNode;
+class QTextBlock;
+class QColor;
+class QTextDocument;
+class QSGContext;
+class QRawFont;
+class QSGInternalRectangleNode;
+class QSGClipNode;
+class QSGTexture;
+class QSGRenderContext;
+
+class QQuickTextNodeEngine;
+
+class Q_QUICK_EXPORT QSGInternalTextNode : public QSGTextNode
+{
+public:
+ QSGInternalTextNode(QSGRenderContext *renderContext);
+ ~QSGInternalTextNode();
+
+ static bool isComplexRichText(QTextDocument *);
+
+ void setColor(QColor color) override
+ {
+ m_color = color;
+ }
+
+ QColor color() const override
+ {
+ return m_color;
+ }
+
+ void setTextStyle(TextStyle textStyle) override
+ {
+ m_textStyle = textStyle;
+ }
+
+ TextStyle textStyle() override
+ {
+ return m_textStyle;
+ }
+
+ void setStyleColor(QColor styleColor) override
+ {
+ m_styleColor = styleColor;
+ }
+
+ QColor styleColor() const override
+ {
+ return m_styleColor;
+ }
+
+ void setLinkColor(QColor linkColor) override
+ {
+ m_linkColor = linkColor;
+ }
+
+ QColor linkColor() const override
+ {
+ return m_linkColor;
+ }
+
+ void setSelectionColor(QColor selectionColor) override
+ {
+ m_selectionColor = selectionColor;
+ }
+
+ QColor selectionColor() const override
+ {
+ return m_selectionColor;
+ }
+
+ void setSelectionTextColor(QColor selectionTextColor) override
+ {
+ m_selectionTextColor = selectionTextColor;
+ }
+
+ QColor selectionTextColor() const override
+ {
+ return m_selectionTextColor;
+ }
+
+ void setRenderTypeQuality(int renderTypeQuality) override
+ {
+ m_renderTypeQuality = renderTypeQuality;
+ }
+ int renderTypeQuality() const override
+ {
+ return m_renderTypeQuality;
+ }
+
+ void setRenderType(RenderType renderType) override
+ {
+ m_renderType = renderType;
+ }
+
+ RenderType renderType() const override
+ {
+ return m_renderType;
+ }
+
+ void setFiltering(QSGTexture::Filtering filtering) override
+ {
+ m_filtering = filtering;
+ }
+
+ QSGTexture::Filtering filtering() const override
+ {
+ return m_filtering;
+ }
+
+ void setViewport(const QRectF &viewport) override
+ {
+ m_viewport = viewport;
+ }
+
+ QRectF viewport() const override
+ {
+ return m_viewport;
+ }
+
+ void setCursor(const QRectF &rect, const QColor &color);
+ void clearCursor();
+
+ void addRectangleNode(const QRectF &rect, const QColor &color);
+ virtual void addDecorationNode(const QRectF &rect, const QColor &color);
+ void addImage(const QRectF &rect, const QImage &image);
+ void clear() override;
+ QSGGlyphNode *addGlyphs(const QPointF &position, const QGlyphRun &glyphs, const QColor &color,
+ QQuickText::TextStyle style = QQuickText::Normal, const QColor &styleColor = QColor(),
+ QSGNode *parentNode = 0);
+
+ QSGInternalRectangleNode *cursorNode() const { return m_cursorNode; }
+ QPair<int, int> renderedLineRange() const { return { m_firstLineInViewport, m_firstLinePastViewport }; }
+
+protected:
+ void doAddTextLayout(QPointF position,
+ QTextLayout *textLayout,
+ int selectionStart,
+ int selectionEnd,
+ int lineStart,
+ int lineCount) override;
+
+ void doAddTextDocument(QPointF position,
+ QTextDocument *textDocument,
+ int selectionStart,
+ int selectionEnd) override;
+
+private:
+ QSGInternalRectangleNode *m_cursorNode = nullptr;
+ QList<QSGTexture *> m_textures;
+ QSGRenderContext *m_renderContext = nullptr;
+ RenderType m_renderType = QtRendering;
+ TextStyle m_textStyle = Normal;
+ QRectF m_viewport;
+ QColor m_color = QColor(0, 0, 0);
+ QColor m_styleColor = QColor(0, 0, 0);
+ QColor m_linkColor = QColor(0, 0, 255);
+ QColor m_selectionColor = QColor(0, 0, 128);
+ QColor m_selectionTextColor = QColor(255, 255, 255);
+ QSGTexture::Filtering m_filtering = QSGTexture::Nearest;
+ int m_renderTypeQuality = -1;
+ int m_firstLineInViewport = -1;
+ int m_firstLinePastViewport = -1;
+
+ friend class QQuickTextEdit;
+ friend class QQuickTextEditPrivate;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGINTERNALTEXTNODE_P_H
diff --git a/src/quick/jar/CMakeLists.txt b/src/quick/jar/CMakeLists.txt
new file mode 100644
index 0000000000..f6014b2d55
--- /dev/null
+++ b/src/quick/jar/CMakeLists.txt
@@ -0,0 +1,22 @@
+qt_internal_add_jar(Qt${QtDeclarative_VERSION_MAJOR}AndroidQuick
+ INCLUDE_JARS
+ ${QT_ANDROID_JAR}
+ ${QT6_INSTALL_PREFIX}/jar/Qt${QtDeclarative_VERSION_MAJOR}Android.jar
+ SOURCES
+ org/qtproject/qt/android/QtQuickView.java
+ org/qtproject/qt/android/QtSignalListener.java
+ org/qtproject/qt/android/QtQmlStatus.java
+ org/qtproject/qt/android/QtQmlStatusChangeListener.java
+ org/qtproject/qt/android/QtModelIndex.java
+ org/qtproject/qt/android/QtAbstractItemModel.java
+ org/qtproject/qt/android/QtAbstractItemModelProxy.java
+ org/qtproject/qt/android/QtQmlComponent.java
+ org/qtproject/qt/android/QtAbstractListModel.java
+ OUTPUT_DIR "${QT_BUILD_DIR}/jar")
+
+qt_path_join(destination ${INSTALL_DATADIR} "jar")
+
+install_jar(Qt${QtDeclarative_VERSION_MAJOR}AndroidQuick
+ DESTINATION ${destination}
+ COMPONENT Devel
+)
diff --git a/src/quick/jar/build.gradle b/src/quick/jar/build.gradle
new file mode 100644
index 0000000000..ea6d06c257
--- /dev/null
+++ b/src/quick/jar/build.gradle
@@ -0,0 +1,51 @@
+// This is mainly used to allow Android Studio to easily read this folder as an android project.
+
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:8.4.0'
+ }
+}
+
+apply plugin: 'com.android.library'
+
+dependencies {
+ implementation fileTree(dir: "libs", include: ["*.jar"])
+}
+
+repositories {
+ google()
+ mavenCentral()
+}
+
+android {
+ compileSdk 34
+
+ defaultConfig {
+ minSdkVersion 28
+ }
+
+ sourceSets {
+ main {
+ java.srcDir 'src/'
+ resources.srcDir 'libs/'
+ manifest.srcFile 'AndroidManifest.xml'
+ res.srcDirs = ['res/']
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ android {
+ lintOptions {
+ abortOnError true
+ }
+ }
+}
diff --git a/src/quick/jar/org/qtproject/qt/android/QtAbstractItemModel.java b/src/quick/jar/org/qtproject/qt/android/QtAbstractItemModel.java
new file mode 100644
index 0000000000..40c635235f
--- /dev/null
+++ b/src/quick/jar/org/qtproject/qt/android/QtAbstractItemModel.java
@@ -0,0 +1,105 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+package org.qtproject.qt.android;
+
+import java.util.HashMap;
+
+public abstract class QtAbstractItemModel
+{
+ public QtAbstractItemModel(){};
+ public abstract int columnCount(QtModelIndex parent);
+ public abstract Object data(QtModelIndex index, int role);
+ public abstract QtModelIndex index(int row, int column, QtModelIndex parent);
+ public abstract QtModelIndex parent(QtModelIndex index);
+ public abstract int rowCount(QtModelIndex parent);
+
+ public native boolean canFetchMore(QtModelIndex parent);
+ public native void fetchMore(QtModelIndex parent);
+ public native boolean hasChildren(QtModelIndex parent);
+ public native boolean hasIndex(int row, int column, QtModelIndex parent);
+
+ public HashMap<Integer, String> roleNames()
+ {
+ return (HashMap<Integer, String>)jni_roleNames();
+ }
+
+ public QtModelIndex sibling(int row, int column, QtModelIndex parent)
+ {
+ return (QtModelIndex)jni_sibling(row, column, parent);
+ }
+
+ protected final void beginInsertColumns(QtModelIndex parent, int first, int last)
+ {
+ jni_beginInsertColumns(parent, first, last);
+ }
+
+ protected final void beginInsertRows(QtModelIndex parent, int first, int last)
+ {
+ jni_beginInsertRows(parent, first, last);
+ }
+ protected final boolean beginMoveColumns(QtModelIndex sourceParent, int sourceFirst,
+ int sourceLast, QtModelIndex destinationParent,
+ int destinationChild)
+ {
+ return jni_beginMoveColumns(sourceParent, sourceFirst, sourceLast, destinationParent,
+ destinationChild);
+ }
+ protected final boolean beginMoveRows(QtModelIndex sourceParent, int sourceFirst,
+ int sourceLast, QtModelIndex destinationParent,
+ int destinationChild)
+ {
+ return jni_beginMoveRows(sourceParent, sourceFirst, sourceLast, destinationParent,
+ destinationChild);
+ }
+ protected final void beginRemoveColumns(QtModelIndex parent, int first, int last)
+ {
+ jni_beginRemoveColumns(parent, first, last);
+ }
+ protected final void beginRemoveRows(QtModelIndex parent, int first, int last)
+ {
+ jni_beginRemoveRows(parent, first, last);
+ }
+ protected final void beginResetModel() { jni_beginResetModel(); }
+
+ protected final QtModelIndex createIndex(int row, int column, long id)
+ {
+ return (QtModelIndex)jni_createIndex(row, column, id);
+ }
+ protected final void endInsertColumns() { jni_endInsertColumns(); }
+ protected final void endInsertRows() { jni_endInsertRows(); }
+ protected final void endMoveColumns() { jni_endMoveColumns(); }
+ protected final void endMoveRows() { jni_endMoveRows(); }
+ protected final void endRemoveColumns() { jni_endRemoveColumns(); }
+ protected final void endRemoveRows() { jni_endRemoveRows(); }
+ protected final void endResetModel() { jni_endResetModel(); }
+
+ private native void jni_beginInsertColumns(QtModelIndex parent, int first, int last);
+ private native void jni_beginInsertRows(QtModelIndex parent, int first, int last);
+ private native boolean jni_beginMoveColumns(QtModelIndex sourceParent, int sourceFirst,
+ int sourceLast, QtModelIndex destinationParent,
+ int destinationChild);
+ private native boolean jni_beginMoveRows(QtModelIndex sourceParent, int sourceFirst,
+ int sourceLast, QtModelIndex destinationParent,
+ int destinationChild);
+ private native void jni_beginRemoveColumns(QtModelIndex parent, int first, int last);
+ private native void jni_beginRemoveRows(QtModelIndex parent, int first, int last);
+ private native void jni_beginResetModel();
+ private native Object jni_createIndex(int row, int column, long id);
+ private native void jni_endInsertColumns();
+ private native void jni_endInsertRows();
+ private native void jni_endMoveColumns();
+ private native void jni_endMoveRows();
+ private native void jni_endRemoveColumns();
+ private native void jni_endRemoveRows();
+ private native void jni_endResetModel();
+ private native Object jni_roleNames();
+ private native Object jni_sibling(int row, int column, QtModelIndex parent);
+
+ private long m_nativeReference = 0;
+ private QtAbstractItemModel(long nativeReference) { m_nativeReference = nativeReference; }
+ private void detachFromNative() { m_nativeReference = 0; };
+ private long nativeReference() { return m_nativeReference; }
+ private void setNativeReference(long nativeReference) { m_nativeReference = nativeReference; }
+ private static boolean instanceOf(Object obj) { return (obj instanceof QtAbstractItemModel); }
+}
diff --git a/src/quick/jar/org/qtproject/qt/android/QtAbstractItemModelProxy.java b/src/quick/jar/org/qtproject/qt/android/QtAbstractItemModelProxy.java
new file mode 100644
index 0000000000..6432a3e12e
--- /dev/null
+++ b/src/quick/jar/org/qtproject/qt/android/QtAbstractItemModelProxy.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+package org.qtproject.qt.android;
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+class QtAndroidItemModelProxy extends QtAbstractItemModel
+{
+ @Override public int columnCount(QtModelIndex parent) { return jni_columnCount(parent); };
+ @Override public Object data(QtModelIndex index, int role) { return jni_data(index, role); }
+ @Override public QtModelIndex index(int row, int column, QtModelIndex parent)
+ {
+ return (QtModelIndex)jni_index(row, column, parent);
+ }
+ @Override public QtModelIndex parent(QtModelIndex index)
+ {
+ return (QtModelIndex)jni_parent(index);
+ }
+ @Override public int rowCount(QtModelIndex parent) { return jni_rowCount(parent); }
+
+ private native int jni_columnCount(QtModelIndex parent);
+ private native Object jni_data(QtModelIndex index, int role);
+ private native Object jni_index(int row, int column, QtModelIndex parent);
+ private native Object jni_parent(QtModelIndex index);
+ private native int jni_rowCount(QtModelIndex parent);
+}
diff --git a/src/quick/jar/org/qtproject/qt/android/QtAbstractListModel.java b/src/quick/jar/org/qtproject/qt/android/QtAbstractListModel.java
new file mode 100644
index 0000000000..5a49caca9c
--- /dev/null
+++ b/src/quick/jar/org/qtproject/qt/android/QtAbstractListModel.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+package org.qtproject.qt.android;
+
+import java.util.HashMap;
+
+public abstract class QtAbstractListModel extends QtAbstractItemModel
+{
+ public QtAbstractListModel(){};
+
+ @Override public final int columnCount(QtModelIndex parent) { return parent.isValid() ? 0 : 1; }
+
+ @Override public QtModelIndex index(int row, int column, QtModelIndex parent)
+ {
+ return hasIndex(row, column, parent) ? createIndex(row, column, 0) : new QtModelIndex();
+ }
+
+ @Override public final QtModelIndex parent(QtModelIndex index) { return new QtModelIndex(); }
+
+ @Override public final boolean hasChildren(QtModelIndex parent)
+ {
+ return parent.isValid() ? false : (rowCount(new QtModelIndex()) > 0);
+ }
+
+ @Override public QtModelIndex sibling(int row, int column, QtModelIndex parent)
+ {
+ return index(row, column, new QtModelIndex());
+ }
+}
diff --git a/src/quick/jar/org/qtproject/qt/android/QtModelIndex.java b/src/quick/jar/org/qtproject/qt/android/QtModelIndex.java
new file mode 100644
index 0000000000..955c736ec4
--- /dev/null
+++ b/src/quick/jar/org/qtproject/qt/android/QtModelIndex.java
@@ -0,0 +1,42 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+package org.qtproject.qt.android;
+
+public class QtModelIndex
+{
+ public QtModelIndex() { }
+ public int column() { return (int)m_privateData[1]; }
+ public native Object data(int role);
+ public native long internalId();
+ public native boolean isValid();
+ public native QtModelIndex parent();
+ public int row() { return (int)m_privateData[0]; }
+
+ private long[] m_privateData = { -1 /*row*/, -1 /*column*/, 0 /*internalId*/,
+ 0 /*modelReference*/ };
+ private QtModelIndex m_parent = null;
+ private QtModelIndex(int row, int column, long internalId, long modelReference)
+ {
+ m_privateData[0] = row;
+ m_privateData[1] = column;
+ m_privateData[2] = internalId;
+ m_privateData[3] = modelReference;
+ m_parent = null;
+ }
+ private QtModelIndex(int row, int column, QtModelIndex parent, long modelReference)
+ {
+ m_privateData[0] = row;
+ m_privateData[1] = column;
+ m_privateData[2] = 0;
+ m_privateData[3] = modelReference;
+ m_parent = parent;
+ }
+ private void detachFromNative()
+ {
+ m_privateData[0] = -1;
+ m_privateData[1] = -1;
+ m_privateData[2] = 0;
+ m_privateData[3] = 0;
+ };
+}
diff --git a/src/quick/jar/org/qtproject/qt/android/QtQmlComponent.java b/src/quick/jar/org/qtproject/qt/android/QtQmlComponent.java
new file mode 100644
index 0000000000..19d063762e
--- /dev/null
+++ b/src/quick/jar/org/qtproject/qt/android/QtQmlComponent.java
@@ -0,0 +1,206 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+package org.qtproject.qt.android;
+
+import android.util.Log;
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.HashSet;
+
+/**
+ * @Since 6.8
+ *
+ * The QtQmlComponent represents a QML component that can be loaded by a QtQuickView instance
+ * This abstract class should be extended to be used by a QtQuickView. It provides QtQuickView with
+ * essential information to load the QML component it represents.
+ * It also offers convenient methods for seamless interaction with the QtQuickView that loads it.
+ **/
+public abstract class QtQmlComponent
+{
+ private final static String TAG = "QtQmlComponent";
+
+ private WeakReference<QtQuickView> m_viewReference;
+ private QtQmlStatusChangeListener m_statusChangeListener = null;
+ private HashSet<Integer> m_signalListenerIds = new HashSet<>();
+
+ /**
+ * Implement this to return the library name that this component belongs to.
+ **/
+ protected abstract String getLibraryName();
+ /**
+ * Implement this to return the module name that this component belongs to.
+ **/
+ protected abstract String getModuleName();
+ /**
+ * Implement this to return the qrc (Qt Resource) path of this QML component.
+ **/
+ protected abstract String getFilePath();
+
+ /**
+ * Sets a StatusChangeListener to listen to status changes.
+ * <p>
+ * @param listener an instance of a StatusChangeListener interface
+ **/
+ public void setStatusChangeListener(QtQmlStatusChangeListener listener)
+ {
+ m_statusChangeListener = listener;
+ QtQuickView view = getQuickView();
+ if (view != null)
+ view.setStatusChangeListener(listener);
+ }
+
+ /**
+ * Gets the QtQuickView instance that has loaded this component.
+ * <p>
+ * @return Returns an instance of QtQuickView or null if this component is not loaded by any
+ * QtQuickView.
+ **/
+ protected QtQuickView getQuickView()
+ {
+ if (m_viewReference != null)
+ return m_viewReference.get();
+ return null;
+ }
+
+ /**
+ * Checks if this is currently attached to a QtQuickView instance
+ * <p>
+ * @return Returns true if this is attached to a QtQuickView instance, otherwise, returns false.
+ **/
+ protected boolean isViewAttached() { return getQuickView() != null; }
+
+ /**
+ * Attaches this to a QtQuickView instance.
+ **/
+ protected void attachView(QtQuickView view)
+ {
+ m_viewReference = new WeakReference<>(view);
+ if (view != null)
+ view.setStatusChangeListener(m_statusChangeListener);
+ }
+
+ /**
+ * Detaches this from the QtQuickView to which it has previously been attached. A call to this
+ * method will disconnect all signal listeners that have been connected before.
+ **/
+ protected void detachView()
+ {
+ QtQuickView view = getQuickView();
+ if (view != null) {
+ for (int signalListenerId : m_signalListenerIds)
+ view.disconnectSignalListener(signalListenerId);
+
+ view.setStatusChangeListener(null);
+ m_viewReference.clear();
+ if (m_statusChangeListener != null)
+ m_statusChangeListener.onStatusChanged(QtQmlStatus.NULL);
+ }
+ }
+
+ /**
+ * Implement this to return more information about the QML Component.
+ * Default implementation returns an empty HashMap.
+ **/
+ protected HashMap<String, Object> attributes() { return new HashMap<>(); }
+
+ /**
+ * Sets the value of an existing property on the QML component if it has already been attached
+ * and loaded by a QtQuickView instance. The supported types are
+ * {@link java.lang.Integer}, {@link java.lang.Double}, {@link java.lang.Float},
+ * {@link java.lang.Boolean} and {@link java.lang.String}. These types get converted to their
+ * corresponding QML types int, double/float, bool, and string. This function does not add
+ * properties to the QML root object if they do not exist but prints a warning.
+ * <p>
+ * @param propertyName the name of the existing QML property to set the value of
+ * @param value the value to set the property to QML's int, double/float,
+ bool or string
+ * @see <a href="https://doc.qt.io/qt-6/qml-int.html">QML int</a>
+ * @see <a href="https://doc.qt.io/qt-6/qml-double.html">QML double/float</a>
+ * @see <a href="https://doc.qt.io/qt-6/qml-bool.html">QML bool</a>
+ * @see <a href="https://doc.qt.io/qt-6/qml-string.html">QML string</a>
+ **/
+ protected void setProperty(String propertyName, Object value)
+ {
+ QtQuickView view = getQuickView();
+ if (view == null) {
+ Log.w(TAG, "Cannot set property as the QQmlComponent is not loaded in a QtQuickView.");
+ return;
+ }
+ view.setProperty(propertyName, value);
+ }
+
+ /**
+ * Gets the value of an existing property of the QML component if it has already been attached
+ * and loaded by a QtQuickView instance. The supported types are
+ * {@link java.lang.Integer}, {@link java.lang.Double}, {@link java.lang.Float},
+ * {@link java.lang.Boolean} and {@link java.lang.String}. These types get converted to their
+ * corresponding QML types int, double/float, bool and string. If the property does not
+ * exist or the status of the QML component is anything other than
+ * {@link QtQuickView#STATUS_READY STATUS_READY}, this function will return null.
+ * <p>
+ * @param propertyName the name of the existing root object property
+ * @throws ClassCastException if the returned type cannot be cast to the requested type.
+ * @see <a href="https://doc.qt.io/qt-6/qml-int.html">QML int</a>
+ * @see <a href="https://doc.qt.io/qt-6/qml-double.html">QML double/float</a>
+ * @see <a href="https://doc.qt.io/qt-6/qml-bool.html">QML bool</a>
+ * @see <a href="https://doc.qt.io/qt-6/qml-string.html">QML string</a>
+ **/
+ protected <T> T getProperty(String propertyName)
+ {
+ QtQuickView view = getQuickView();
+ if (view == null) {
+ Log.w(TAG, "Cannot get property as the QQmlComponent is not loaded in a QtQuickView.");
+ return null;
+ }
+ return view.<T>getProperty(propertyName);
+ }
+
+ /**
+ * Connects a SignalListener to a signal of the QML component if it has already been attached
+ * and loaded by a QtQuickView instance.
+ * <p>
+ * @param signalName the name of the root object signal
+ * @param argType the Class type of the signal argument
+ * @param listener an instance of the QtSignalListener interface
+ * @return a connection ID between signal and listener or the existing connection ID if there is
+ * an existing connection between the same signal and listener. Return a negative value
+ * if the signal does not exist on the QML root object.
+ **/
+ protected <T> int connectSignalListener(String signalName, Class<T> argType,
+ QtSignalListener<T> listener)
+ {
+ QtQuickView view = getQuickView();
+ if (view == null) {
+ Log.w(TAG,
+ "Cannot connect signal listener as the QQmlComponent is not loaded in a "
+ + "QtQuickView.");
+ return -1;
+ }
+ int signalListenerId = view.connectSignalListener(signalName, argType, listener);
+ m_signalListenerIds.add(signalListenerId);
+ return signalListenerId;
+ }
+
+ /**
+ * Disconnects a SignalListener with a given id obtained from
+ * {@link QtQuickView#connectSignalListener() connectSignalListener} call, from listening to
+ * a signal.
+ * <p>
+ * @param signalListenerId the connection id
+ * @return Returns true if the connection id is valid and has been successfully removed,
+ * otherwise returns false.
+ **/
+ public boolean disconnectSignalListener(int signalListenerId)
+ {
+ QtQuickView view = getQuickView();
+ if (view == null) {
+ Log.w(TAG,
+ "Cannot disconnect signal listener as the QQmlComponent is not loaded in a "
+ + "QtQuickView.");
+ return false;
+ }
+ m_signalListenerIds.remove(signalListenerId);
+ return view.disconnectSignalListener(signalListenerId);
+ }
+}
diff --git a/src/quick/jar/org/qtproject/qt/android/QtQmlStatus.java b/src/quick/jar/org/qtproject/qt/android/QtQmlStatus.java
new file mode 100644
index 0000000000..02bea77d43
--- /dev/null
+++ b/src/quick/jar/org/qtproject/qt/android/QtQmlStatus.java
@@ -0,0 +1,49 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+package org.qtproject.qt.android;
+
+import java.lang.IllegalArgumentException;
+
+/**
+ * QtQmlStatus represents the QML component loading status.
+ */
+public enum QtQmlStatus {
+ /**
+ * Not loaded.
+ **/
+ NULL(0),
+
+ /**
+ * Loaded and ready.
+ * Invoking methods that operate on a QML component would succeed <b>only<b> if
+ * the current status is ready.
+ **/
+ READY(1),
+
+ /**
+ *The QML component is getting loaded from network.
+ **/
+ LOADING(2),
+
+ /**
+ * One or more errors has occurred during loading the QML component.
+ **/
+ ERROR(3);
+
+ private final int m_value;
+
+ QtQmlStatus(int value) { this.m_value = value; }
+
+ QtQmlStatus() { this.m_value = ordinal(); }
+
+ static QtQmlStatus fromInt(int value) throws IllegalArgumentException
+ {
+ for (QtQmlStatus enumValue : QtQmlStatus.values()) {
+ if (enumValue.m_value == value) {
+ return enumValue;
+ }
+ }
+ throw new IllegalArgumentException("No QtQmlStatus enum with value " + value);
+ }
+}
diff --git a/src/quick/jar/org/qtproject/qt/android/QtQmlStatusChangeListener.java b/src/quick/jar/org/qtproject/qt/android/QtQmlStatusChangeListener.java
new file mode 100644
index 0000000000..f1190ed8b1
--- /dev/null
+++ b/src/quick/jar/org/qtproject/qt/android/QtQmlStatusChangeListener.java
@@ -0,0 +1,17 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+package org.qtproject.qt.android;
+
+/**
+ * A callback that notifies clients about the status of QML component loading.
+ **/
+public interface QtQmlStatusChangeListener
+{
+ /**
+ * Called on the Android UI thread when the QML component status has changed.
+ * @param status The current status. The status can be QtQmlStatus.NULL,
+ * QtQmlStatus.READY, QtQmlStatus.LOADING, or QtQmlStatus.ERROR.
+ **/
+ void onStatusChanged(QtQmlStatus status);
+}
diff --git a/src/quick/jar/org/qtproject/qt/android/QtQuickView.java b/src/quick/jar/org/qtproject/qt/android/QtQuickView.java
new file mode 100644
index 0000000000..bd4519355f
--- /dev/null
+++ b/src/quick/jar/org/qtproject/qt/android/QtQuickView.java
@@ -0,0 +1,304 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+package org.qtproject.qt.android;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.util.Log;
+
+import java.lang.IllegalArgumentException;
+import java.lang.ref.WeakReference;
+import java.security.InvalidParameterException;
+
+/**
+ * The QtQuickView class lets you easily add QML content to your Android app as a
+ * {@link android.view.View View}. QtQuickView instantiates a QQuickView with a given
+ * QML component source URI path and embeds it inside itself. You can add it in your Android app's
+ * layout as with any other View. QtQuickView is a good choice when you want to extend your non-Qt
+ * Android app with QML content but do not want to make the entire app using the Qt framework.
+ * It brings the power of Qt Quick into your Android app, making it possible to use various Qt Quick
+ * APIs, in Android Java or Kotlin apps.
+ * </p>
+ * <b>Note:</b> This class is a technical preview. It is subject to change, and no source nor
+ * binary compatibility guarantees exist.
+ * <p>
+ * <b>Known limitations:</b>
+ * <p><ul>
+ * <li> Only CMake is supported, not qmake.
+ * <li> Only one QtQuickView can be added to your app, adding multiple outcomes unknown.
+ * </ul><p>
+ * @see <a href="https://doc.qt.io/qt-6/qquickview.html">Qt QQuickView</a>
+ **/
+public class QtQuickView extends QtView {
+ private final static String TAG = "QtQuickView";
+
+ private String m_qmlUri;
+ private String[] m_qmlImportPaths = null;
+ private QtQmlStatusChangeListener m_statusChangeListener = null;
+ private QtQmlStatus m_lastStatus = QtQmlStatus.NULL;
+ private boolean m_hasQueuedStatus = false;
+ private WeakReference<QtQmlComponent> m_loadedComponent;
+
+ native void createQuickView(String qmlUri, int width, int height, long parentWindowReference,
+ long viewReference, String[] qmlImportPaths);
+ native void setRootObjectProperty(long windowReference, String propertyName, Object value);
+ native Object getRootObjectProperty(long windowReference, String propertyName);
+ native int addRootObjectSignalListener(long windowReference, String signalName, Class argType,
+ Object listener);
+ native boolean removeRootObjectSignalListener(long windowReference, int signalListenerId);
+
+ /**
+ * Creates a QtQuickView to load and view a QML component. Instantiating a QtQuickView will load
+ * the Qt libraries, including the app library specified by <code>appName</code>. Then it
+ * creates a QQuickView that loads the QML source specified by <code>qmlUri</code>.
+ * <p>
+ * @param context the parent Context
+ * @param qmlUri the URI of the main QML file
+ * @param appName the name of the Qt app library to load and start. This corresponds to the
+ * target name set in Qt app's CMakeLists.txt
+ * @throws InvalidParameterException if either qmlUri or appName is empty or null
+ * @see <a href="https://doc.qt.io/qt-6/qquickview.html">Qt QQuickView</a>
+ **/
+ public QtQuickView(Context context, String qmlUri, String appName)
+ throws InvalidParameterException {
+ this(context, qmlUri, appName, null);
+ }
+
+ /**
+ * Creates a QtQuickView to load and view a QML component. Instantiating a QtQuickView will load
+ * the Qt libraries, including the app library specified by appName. Then it creates a
+ * QQuickView that loads the QML source specified by qmlUri. This overload accepts an array of
+ * strings in the case where the QML application should load QML modules from custom paths.
+ * <p>
+ * @param context the parent Context
+ * @param qmlUri the URI of the main QML file
+ * @param appName the name of the Qt app library to load and start. This corresponds to
+ * the target name set in the Qt app's CMakeLists.txt
+ * @param qmlImportPaths an array of strings for additional import paths to be passed to
+ QQmlEngine, or null if additional import paths are not required
+ * @throws InvalidParameterException if either qmlUri or appName is empty or null
+ * @see <a href="https://doc.qt.io/qt-6/qqmlengine.html">Qt QQmlEngine</a>
+ **/
+ public QtQuickView(Context context, String qmlUri, String appName, String[] qmlImportPaths)
+ throws InvalidParameterException
+ {
+ super(context, appName);
+ if (qmlUri == null || qmlUri.isEmpty()) {
+ throw new InvalidParameterException(
+ "QtQuickView: argument 'qmlUri' may not be empty or null");
+ }
+ m_qmlUri = qmlUri;
+ m_qmlImportPaths = qmlImportPaths;
+ }
+
+ /**
+ * Creates a QtQuickView that can later load and view a QML component by calling
+ * {@link QtQuickView#loadComponent() loadComponent}
+ * <p>
+ * @param context the parent Context
+ **/
+ public QtQuickView(Context context)
+ {
+ super(context);
+ }
+
+ /**
+ * Loads a QML component represented by a QtQmlComponent. The library name and the qrc path of
+ * the QML component will be extracted from the QtQmlComponent to load the QML component.
+ * This overload accepts an array of strings in the case where the QML component should load
+ * QML modules from custom paths.
+ * <p>
+ * @param qmlComponent an instance of an object that extends QtQmlComponent
+ * @param qmlImportPaths an array of strings for additional import paths to be passed to
+ * QQmlEngine, or null if additional import paths are not required
+ * @throws InvalidParameterException if QtQmlComponent does not contain valid information
+ * about the module name, and the qrc path.
+ */
+ // TODO: QTBUG-125620 -- Refresh/reset import paths when loading a new component
+ public <T extends QtQmlComponent> void loadComponent(T qmlComponent, String[] qmlImportPaths)
+ throws InvalidParameterException
+ {
+ String libName = qmlComponent.getLibraryName();
+ String qmlUri = qmlComponent.getFilePath();
+
+ if (libName == null || libName.isEmpty()) {
+ throw new InvalidParameterException(
+ "QtQmlComponent: return value of getLibraryName() may not be empty or null");
+ }
+
+ if (qmlUri == null || qmlUri.isEmpty()) {
+ throw new InvalidParameterException(
+ "QtQmlComponent: return value of getFilePath() may not be empty or null");
+ }
+
+ m_qmlUri = qmlUri;
+ m_qmlImportPaths = qmlImportPaths;
+
+ if (m_loadedComponent != null)
+ m_loadedComponent.clear();
+
+ m_loadedComponent = new WeakReference<>(qmlComponent);
+ qmlComponent.detachView();
+ qmlComponent.attachView(this);
+ // The first QQuickView creation happen after first libs loading
+ // and windowReference() returns a reference to native QQuickView
+ // instance, after that. We don't load library again if the view
+ // exists.
+ if (windowReference() == 0) {
+ loadQtLibraries(libName);
+ } else {
+ createQuickView(m_qmlUri, getWidth(), getHeight(), 0, windowReference(),
+ m_qmlImportPaths);
+ }
+ }
+
+ /**
+ * Loads a QML component represented by a QtQmlComponent. The library name and the qrc path of
+ * the QML component will be extracted from the QtQmlComponent to load the QML component.
+ * <p>
+ * @param qmlComponent an instance of a class that extends QtQmlComponent
+ * @throws InvalidParameterException if QtQmlComponent does not contain valid information
+ * about the module name, and the qrc path.
+ */
+ public <T extends QtQmlComponent> void loadComponent(T qmlComponent)
+ throws InvalidParameterException
+ {
+ loadComponent(qmlComponent, null);
+ }
+
+ @Override
+ protected void createWindow(long parentWindowReference) {
+ createQuickView(m_qmlUri, getWidth(), getHeight(), parentWindowReference, windowReference(),
+ m_qmlImportPaths);
+ }
+
+ /**
+ * Sets the value of an existing property on the QML root object. The supported types are
+ * {@link java.lang.Integer}, {@link java.lang.Double}, {@link java.lang.Float},
+ * {@link java.lang.Boolean} and {@link java.lang.String}. These types get converted to their
+ * corresponding QML types int, double/float, bool and string. This function does not add
+ * properties to the QML root object if they do not exist, but prints a warning.
+ * <p>
+ * @param propertyName the name of the existing root object property to set the value of
+ * @param value the value to set the property to QML's int, double/float, bool or
+ string
+ * @see <a href="https://doc.qt.io/qt-6/qml-int.html">QML int</a>
+ * @see <a href="https://doc.qt.io/qt-6/qml-double.html">QML double/float</a>
+ * @see <a href="https://doc.qt.io/qt-6/qml-bool.html">QML bool</a>
+ * @see <a href="https://doc.qt.io/qt-6/qml-string.html">QML string</a>
+ **/
+ public void setProperty(String propertyName, Object value)
+ {
+ setRootObjectProperty(windowReference(), propertyName, value);
+ }
+
+ /**
+ * Gets the value of an existing property of the QML root object. The supported types are
+ * {@link java.lang.Integer}, {@link java.lang.Double}, {@link java.lang.Float},
+ * {@link java.lang.Boolean} and {@link java.lang.String}. These types get converted to their
+ * corresponding QML types int, double/float, bool and string. If the property does not
+ * exist or the status of the QML component is anything other than
+ * {@link QtQuickView#STATUS_READY STATUS_READY}, this function will return null.
+ * <p>
+ * @param propertyName the name of the existing root object property
+ * @throws ClassCastException if the returned type could not be casted to the requested type.
+ * @see <a href="https://doc.qt.io/qt-6/qml-int.html">QML int</a>
+ * @see <a href="https://doc.qt.io/qt-6/qml-double.html">QML double/float</a>
+ * @see <a href="https://doc.qt.io/qt-6/qml-bool.html">QML bool</a>
+ * @see <a href="https://doc.qt.io/qt-6/qml-string.html">QML string</a>
+ **/
+ // getRootObjectProperty always returns a primitive type or an Object
+ // so it is safe to suppress the unchecked warning
+ @SuppressWarnings("unchecked")
+ public <T> T getProperty(String propertyName)
+ {
+ return (T)getRootObjectProperty(windowReference(), propertyName);
+ }
+
+ /**
+ * Connects a SignalListener to a signal of the QML root object.
+ * <p>
+ * @param signalName the name of the root object signal
+ * @param argType the Class type of the signal argument
+ * @param listener an instance of the QtSignalListener interface
+ * @return a connection id between signal and listener or the existing connection id if there is
+ * an existing connection between the same signal and listener. Return a negative value
+ * if the signal does not exists on the QML root object.
+ **/
+ public <T> int connectSignalListener(String signalName, Class<T> argType,
+ QtSignalListener<T> listener)
+ {
+ int signalListenerId =
+ addRootObjectSignalListener(windowReference(), signalName, argType, listener);
+ if (signalListenerId < 0) {
+ Log.w(TAG, "The signal " + signalName + " does not exist in the root object "
+ + "or the arguments do not match with the listener.");
+ }
+ return signalListenerId;
+ }
+
+ /**
+ * Disconnects a SignalListener with a given id obtained from
+ * {@link QtQuickView#connectSignalListener() connectSignalListener} call, from listening to
+ * a signal.
+ * <p>
+ * @param signalListenerId the connection id
+ * @return Returns true if the connection id is valid and has been successfuly removed,
+ * otherwise returns false.
+ **/
+ public boolean disconnectSignalListener(int signalListenerId)
+ {
+ return removeRootObjectSignalListener(windowReference(), signalListenerId);
+ }
+
+ /**
+ * Gets the status of the QML component.
+ * <p>
+ * @return Returns QtQmlStatus.READY when the QML component is ready. Invoking methods that
+ * operate on the QML root object ({@link QtQuickView#setProperty() setProperty},
+ * {@link QtQuickView#getProperty() getProperty}, and
+ * {@link QtQuickView#addSignalListener() addSignalListener}) would succeed <b>only</b>
+ * if the current status is QtQmlStatus.READY. It can also return QtQmlStatus.NULL,
+ * QtQmlStatus.LOADING, or QtQmlStatus.ERROR based on the status of the underlaying
+ QQuickView instance.
+ * @see <a href="https://doc.qt.io/qt-6/qquickview.html">QQuickView</a>
+ **/
+ public QtQmlStatus getStatus()
+ {
+ return m_lastStatus;
+ }
+
+ /**
+ * Sets a QtQmlStatusChangeListener to listen to status changes.
+ * <p>
+ * @param listener an instance of a QtQmlStatusChangeListener interface
+ **/
+ public void setStatusChangeListener(QtQmlStatusChangeListener listener)
+ {
+ m_statusChangeListener = listener;
+
+ if (m_hasQueuedStatus) {
+ QtNative.runAction(() -> { m_statusChangeListener.onStatusChanged(m_lastStatus); });
+ m_hasQueuedStatus = false;
+ }
+ }
+
+ private void handleStatusChange(int status)
+ {
+ try {
+ m_lastStatus = QtQmlStatus.fromInt(status);
+ } catch (IllegalArgumentException e) {
+ m_lastStatus = QtQmlStatus.NULL;
+ e.printStackTrace();
+ }
+
+ if (m_statusChangeListener != null)
+ QtNative.runAction(() -> {
+ m_statusChangeListener.onStatusChanged(QtQmlStatus.fromInt(status));
+ });
+ else
+ m_hasQueuedStatus = true;
+ }
+}
diff --git a/src/quick/jar/org/qtproject/qt/android/QtQuickView.qdoc b/src/quick/jar/org/qtproject/qt/android/QtQuickView.qdoc
new file mode 100644
index 0000000000..d603ac4144
--- /dev/null
+++ b/src/quick/jar/org/qtproject/qt/android/QtQuickView.qdoc
@@ -0,0 +1,277 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \page qtquickview-android-class.html
+ \title Qt Quick View Android Class
+ \ingroup qt_android_classes
+ \brief Allows you to add QML content to your Android app as a View.
+ \techpreview
+ \since 6.7
+
+ The QtQuickView class lets you easily add QML content to your Android app as
+ a \l {Android: View}{View}.
+
+ \target QtQuickView
+ \table
+ \row
+ \li Class:
+ \li QtQuickView
+ \row
+ \li Package Name:
+ \li org.qtproject.qt.android
+ \row
+ \li Extends:
+ \li org.qtproject.qt.android.QtView
+
+ – org.qtproject.qt.android.QtLayout
+
+ –– android.view.ViewGroup
+ \endtable
+
+ \section1 Detailed description
+
+ The QtQuickView class lets you easily add QML content to your Android app as
+ a \l {Android: View}{View}. \c QtQuickView instantiates a \l QQuickView with
+ a given QML component source (a local or network file) and embeds it to itself.
+ You can add it to your Android app's layout as with any other View. \c QtQuickView
+ is a good choice when you want to extend your non-Qt Android app with QML content but
+ do not want to make the entire app using the Qt framework. It brings the power
+ of Qt Quick into your Android app, making it possible to use various Qt Quick
+ APIs in Android apps.
+
+ A typical use of the class:
+
+ \code
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ ...
+
+ QtQuickView qmlView = new QtQuickView(this, "qrc:/qt/qml/target/main.qml", "target");
+ qmlView.setStatusChangeListener(status -> {
+ Log.i(TAG, "QML loading status changed to " + status);
+ });
+
+ // Add QML to your layout
+ layout.addView(qmlView, params);
+ ...
+ }
+ \endcode
+
+ For a more detailed example, see \l {QML in Android Studio Projects}.
+
+ \section1 QtQuickView in an Android Service
+
+ It is also possible to add a QtQuickView from a Service context by using
+ the Android WindowManager interface:
+
+ \code
+ @Override
+ public void onCreate() {
+ m_windowManager = getSystemService(WindowManager.class);
+ m_qtView = new QtQuickView(this, "qrc:/qt/qml/target/main.qml", "target");
+ WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
+ 640, 320,
+ WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
+ WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
+ PixelFormat.TRANSLUCENT);
+ m_windowManager.addView(m_qtView, layoutParams);
+ }
+ \endcode
+
+ To clean up the QtQuickView and Qt libraries, the onDestroy() lifecycle
+ function can be used:
+
+ \code
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ m_windowManager.removeView(m_qtView);
+ m_qtView = null;
+ }
+ \endcode
+
+ \note Adding a QtQuickView from a Service context requires your application
+ to have the \l {Android: SYSTEM_ALERT_WINDOW}{SYSTEM_ALERT_WINDOW}
+ permission, and to be signed with the platform key.
+
+ \note QML views embedded within a Service context do not
+ support keyboard input or accessibility features.
+
+ \section1 Constructors
+
+ \section2 public QtQuickView(Context parent, String qmlUri, String appName)
+
+ Creates a QtQuickView to load and render a QML component. Instantiating a
+ QtQuickView will load the Qt libraries, including the app library specified
+ by \e appName. Then, it creates a QQuickView that loads the QML source specified
+ by \e qmlUri.
+
+ \section3 Parameters
+
+ \list
+ \li \b context: the parent Context.
+ \li \b qmlUri: the URI of the main QML file.
+ \li \b appName: the name of the Qt app library to load and start.
+ This corresponds to the target name set in the Qt app's CMakeLists.txt.
+ \endlist
+
+ \section3 Throws
+
+ Throws a \l {Android: InvalidParameterException}{InvalidParameterException} if
+ a parameter is invalid.
+
+ \section2 public QtQuickView(Context context, String qmlUri, String appName, String[] qmlImportPaths)
+
+ Creates a QtQuickView to load and view a QML component. Instantiating a
+ QtQuickView will load the Qt libraries, including the app library specified
+ by \e appName. Then, it creates a QQuickView that loads the QML source specified
+ by \e qmlUri. This overload accepts an array of strings \e qmlImportPaths in the
+ case where the QML application should load QML modules from custom paths.
+
+ \section3 Parameters
+
+ \list
+ \li \b context: the parent Context.
+ \li \b qmlUri: the URI of the main QML file.
+ \li \b appName: the name of the Qt app library to load and start.
+ This corresponds to the target name set in the Qt app's CMakeLists.txt.
+ \li \b qmlImportPaths: an array of strings for additional import paths to
+ be passed to.
+ \endlist
+
+ \section3 Throws
+
+ Throws a \l {Android: InvalidParameterException}{InvalidParameterException} if
+ a parameter is invalid.
+
+ \section1 Interfaces
+
+ \section2 public interface SignalListener<T>
+ \target SignalListener
+
+ Invoked on the Android UI thread when the signal has been emitted.
+
+ \section3 Parameters
+
+ \list
+ \li \b signalName: literal signal name
+ \li \b value: the value delivered by the signal or null if the signal is
+ without a parameter.
+ \endlist
+
+ \section2 public interface StatusChangeListener
+ \target StatusChangeListener
+
+ Invoked on the Android UI thread when the QML component status has changed.
+
+ \section3 Parameters
+
+ \list
+ \li \b status: The current status.
+ \endlist
+
+ \section1 Fields
+
+ \section2 Status values
+ \target Status values
+
+ The status can be \e STATUS_NULL, \e STATUS_READY, \e STATUS_LOADING or
+ \e STATUS_ERROR. For more information, see \l {QQuickView::Status}.
+
+ \section1 Methods
+
+ \section2 public void setProperty(String propertyName, Object value)
+ \target setProperty()
+
+ Sets the value of an existing property on the QML root object. The supported
+ types are \c Integer, \c Double, \c Float, \c Boolean, and \c String. These
+ types get converted to their corresponding QML types int, double/float, bool,
+ and string. This function does not add properties to the QML root object if
+ they do not exist.
+
+ \section3 Parameters
+ \list
+ \li \b propertyName: the name of the existing root object property to set its value
+ \li \b value: the value of the property
+ \endlist
+
+ \section2 public <T extends Object> T getProperty(String propertyName)
+ \target getProperty()
+
+ Gets the value of an existing property of the QML root object. The supported
+ return types are \e Integer, \e Double, \e Float, \e Boolean, and \e String.
+ These types get converted from their corresponding QML types int, double/float,
+ bool, and string.
+
+ \section3 Parameters
+ \list
+ \li \b propertyName: the name of the existing root object property.
+ \endlist
+
+ \section3 Returns
+
+ If the property does not exist or the status of the QML component is
+ anything other than \l {Status values}{STATUS_READY}, this function will return null.
+
+ \section3 Throws
+
+ Throws a \l {Android: ClassCastException}{ClassCastException} if type casting fails.
+
+ \section2 public <T> int addSignalListener(String signalName, Class<T> argType, SignalListener<T> listener)
+ \target addSignalListener()
+
+ Associates a \l {SignalListener} with a signal of the QML root object.
+
+ \section3 Parameters
+ \list
+ \li \b signalName: the name of the root object signal.
+ \li \b argType: the Class type of the signal argument.
+ \li \b listener: an instance of the SignalListener interface.
+ \endlist
+
+ \section3 Returns
+
+ A \c {Connection ID} between signal and listener or the existing connection
+ ID if there is an existing connection between the same signal and listener.
+ Returns a negative value if the signal does not exist on the QML root object.
+
+ \section2 public boolean removeSignalListener(int signalListenerId)
+
+ Stops a \l {SignalListener} with a given id obtained from \l addSignalListener()
+ call, from listening to a signal.
+
+ \section3 Parameters
+ \list
+ \li \b signalListenerId: the connection ID.
+ \endlist
+
+ \section3 Returns
+ \e True if the connection ID is valid and has been successfully removed,
+ otherwise returns false.
+
+ \section2 public int getStatus()
+ \target getStatus()
+
+ Gets the \l {Status values}{status} of the QML component.
+
+ \section3 Returns
+
+ \e STATUS_READY when the QML is ready. Invoking methods that operate on the QML
+ root object, such as \l {setProperty()}, \l {getProperty()}, and
+ \l {addSignalListener()}, would succeed \b only if the current status is
+ \c STATUS_READY. It can also return other \l {Status values}{status} values
+ representing the status of the underlying QQuickView instance.
+
+ \section2 public void setStatusChangeListener(StatusChangeListener listener)
+
+ Sets a \l {StatusChangeListener} to listen to status changes.
+
+ \section3 Parameters
+
+ \list
+ \li \b listener: an instance of a \l {StatusChangeListener} interface.
+ \endlist
+*/
diff --git a/src/quick/jar/org/qtproject/qt/android/QtSignalListener.java b/src/quick/jar/org/qtproject/qt/android/QtSignalListener.java
new file mode 100644
index 0000000000..195f983be4
--- /dev/null
+++ b/src/quick/jar/org/qtproject/qt/android/QtSignalListener.java
@@ -0,0 +1,17 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+package org.qtproject.qt.android;
+
+/**
+ * A callback that notifies clients when a signal is emitted from the QML component.
+ **/
+@FunctionalInterface
+public interface QtSignalListener<T> {
+ /**
+ * Called on the Android UI thread when the signal has been emitted.
+ * @param signalName literal signal name
+ * @param value the value delivered by the signal or null if the signal is parameterless
+ **/
+ void onSignalEmitted(String signalName, T value);
+}
diff --git a/src/quick/jar/settings.gradle b/src/quick/jar/settings.gradle
new file mode 100644
index 0000000000..8bf0aad842
--- /dev/null
+++ b/src/quick/jar/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = "QtAndroidQuick"
diff --git a/src/quick/platform/android/qandroiditemmodelproxy.cpp b/src/quick/platform/android/qandroiditemmodelproxy.cpp
new file mode 100644
index 0000000000..4a4653da39
--- /dev/null
+++ b/src/quick/platform/android/qandroiditemmodelproxy.cpp
@@ -0,0 +1,379 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <QtQuick/private/qandroiditemmodelproxy_p.h>
+#include <QtQuick/private/qandroidmodelindexproxy_p.h>
+#include <QtQuick/private/qandroidtypeconverter_p.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace QtJniTypes;
+
+jint QAndroidItemModelProxy::columnCount(const QModelIndex &parent) const
+{
+ Q_ASSERT(jInstance.isValid());
+ auto parentIndex = QAndroidModelIndexProxy::jInstance(parent);
+ return jInstance.callMethod<jint>("columnCount", parentIndex);
+}
+
+bool QAndroidItemModelProxy::canFetchMore(const QModelIndex &parent) const
+{
+ Q_ASSERT(jInstance.isValid());
+ auto parentIndex = QAndroidModelIndexProxy::jInstance(parent);
+ return jInstance.callMethod<jboolean>("canFetchMore", parentIndex);
+}
+
+bool QAndroidItemModelProxy::canFetchMoreDefault(const QModelIndex &parent) const
+{
+ return QAbstractItemModel::canFetchMore(parent);
+}
+
+QVariant QAndroidItemModelProxy::data(const QModelIndex &index, int role) const
+{
+ Q_ASSERT(jInstance.isValid());
+ auto jIndex = QAndroidModelIndexProxy::jInstance(index);
+ QJniObject jData = jInstance.callMethod<jobject>("data", jIndex, role);
+ return QAndroidTypeConverter::toQVariant(jData);
+}
+
+QModelIndex QAndroidItemModelProxy::index(int row, int column, const QModelIndex &parent) const
+{
+ Q_ASSERT(jInstance.isValid());
+ JQtModelIndex jIndex = jInstance.callMethod<JQtModelIndex>(
+ "index", row, column, QAndroidModelIndexProxy::jInstance(parent));
+ return QAndroidModelIndexProxy::qInstance(jIndex);
+}
+
+QModelIndex QAndroidItemModelProxy::parent(const QModelIndex &index) const
+{
+ Q_ASSERT(jInstance.isValid());
+
+ auto jIndex = QAndroidModelIndexProxy::jInstance(index);
+ return QAndroidModelIndexProxy::qInstance(
+ jInstance.callMethod<JQtModelIndex>("parent", jIndex));
+}
+int QAndroidItemModelProxy::rowCount(const QModelIndex &parent) const
+{
+ Q_ASSERT(jInstance.isValid());
+
+ auto parentIndex = QAndroidModelIndexProxy::jInstance(parent);
+ return jInstance.callMethod<int>("rowCount", parentIndex);
+}
+
+QHash<int, QByteArray> QAndroidItemModelProxy::roleNames() const
+{
+ Q_ASSERT(jInstance.isValid());
+
+ QHash<int, QByteArray> roleNames;
+ HashMap hashMap = jInstance.callMethod<HashMap>("roleNames");
+ Set set = hashMap.callMethod<Set>("keySet");
+ QJniArray<jobject> keyArray = set.callMethod<QJniArray<jobject>>("toArray");
+
+ for (auto key : keyArray) {
+ const QJniObject roleName = hashMap.callMethod<jobject>("get", key);
+ const int intKey = QJniObject(key).callMethod<jint>("intValue");
+ const QByteArray roleByteArray = String(roleName).toString().toLatin1();
+ roleNames.insert(intKey, roleByteArray);
+ }
+ return roleNames;
+}
+
+QHash<int, QByteArray> QAndroidItemModelProxy::defaultRoleNames() const
+{
+ return QAbstractItemModel::roleNames();
+}
+
+void QAndroidItemModelProxy::fetchMore(const QModelIndex &parent)
+{
+ Q_ASSERT(jInstance.isValid());
+ auto parentIndex = QAndroidModelIndexProxy::jInstance(parent);
+ jInstance.callMethod<void>("fetchMore", parentIndex);
+}
+
+void QAndroidItemModelProxy::fetchMoreDefault(const QModelIndex &parent)
+{
+ QAbstractItemModel::fetchMore(parent);
+}
+
+bool QAndroidItemModelProxy::hasChildren(const QModelIndex &parent) const
+{
+ Q_ASSERT(jInstance.isValid());
+ auto parentIndex = QAndroidModelIndexProxy::jInstance(parent);
+ return jInstance.callMethod<jboolean>("hasChildren", parentIndex);
+}
+
+bool QAndroidItemModelProxy::hasChildrenDefault(const QModelIndex &parent) const
+{
+ return QAbstractItemModel::hasChildren(parent);
+}
+
+QModelIndex QAndroidItemModelProxy::sibling(int row, int column, const QModelIndex &parent) const
+{
+ Q_ASSERT(jInstance.isValid());
+ return QAndroidModelIndexProxy::qInstance(jInstance.callMethod<jobject>(
+ "sibling", row, column, QAndroidModelIndexProxy::jInstance(parent)));
+}
+
+QModelIndex QAndroidItemModelProxy::siblingDefault(int row, int column, const QModelIndex &parent)
+{
+ return QAbstractItemModel::sibling(row, column, parent);
+}
+
+Q_REQUIRED_RESULT QAbstractItemModel *
+QAndroidItemModelProxy::nativeInstance(JQtAbstractItemModel itemModel)
+{
+ jlong nativeReference = itemModel.callMethod<jlong>("nativeReference");
+ return reinterpret_cast<QAbstractItemModel *>(nativeReference);
+}
+
+Q_REQUIRED_RESULT QAbstractItemModel *
+QAndroidItemModelProxy::createNativeProxy(QJniObject itemModel)
+{
+ QAbstractItemModel *nativeProxy = nativeInstance(itemModel);
+ if (!nativeProxy) {
+ nativeProxy = new QAndroidItemModelProxy(itemModel);
+
+ itemModel.callMethod<void>("setNativeReference", reinterpret_cast<jlong>(nativeProxy));
+ connect(nativeProxy, &QAndroidItemModelProxy::destroyed, nativeProxy, [](QObject *obj) {
+ auto proxy = qobject_cast<QAndroidItemModelProxy *>(obj);
+ if (proxy)
+ proxy->jInstance.callMethod<void>("detachFromNative");
+ });
+ }
+ return nativeProxy;
+}
+
+QJniObject QAndroidItemModelProxy::createProxy(QAbstractItemModel *itemModel)
+{
+ return JQtAndroidItemModelProxy(reinterpret_cast<jlong>(itemModel));
+}
+
+int QAndroidItemModelProxy::jni_columnCount(JNIEnv *env, jobject object, JQtModelIndex parent)
+{
+ const QModelIndex nativeParent = QAndroidModelIndexProxy::qInstance(parent);
+ return invokeNativeMethod(env, object, &QAbstractItemModel::columnCount, nativeParent);
+}
+
+jobject QAndroidItemModelProxy::jni_data(JNIEnv *env, jobject object, JQtModelIndex index,
+ jint role)
+{
+ const QModelIndex nativeIndex = QAndroidModelIndexProxy::qInstance(index);
+ const QVariant data =
+ invokeNativeMethod(env, object, &QAbstractItemModel::data, nativeIndex, role);
+ return QAndroidTypeConverter::toJavaObject(data, env);
+}
+
+jobject QAndroidItemModelProxy::jni_index(JNIEnv *env, jobject object, jint row, jint column,
+ JQtModelIndex parent)
+{
+ auto nativeParent = QAndroidModelIndexProxy::qInstance(parent);
+ const QModelIndex modelIndex =
+ invokeNativeMethod(env, object, &QAbstractItemModel::index, row, column, nativeParent);
+ return env->NewLocalRef(QAndroidModelIndexProxy::jInstance(modelIndex).object());
+}
+
+jobject QAndroidItemModelProxy::jni_parent(JNIEnv *env, jobject object, JQtModelIndex index)
+{
+ const QModelIndex nativeIndex = QAndroidModelIndexProxy::qInstance(index);
+ QModelIndex (QAbstractItemModel::*parentOverloadPtr)(const QModelIndex &) const =
+ &QAbstractItemModel::parent;
+ const QModelIndex parent = invokeNativeMethod(env, object, parentOverloadPtr, nativeIndex);
+ return env->NewLocalRef(QAndroidModelIndexProxy::jInstance(parent).object());
+}
+
+jint QAndroidItemModelProxy::jni_rowCount(JNIEnv *env, jobject object, JQtModelIndex parent)
+{
+ return invokeNativeMethod(env, object, &QAbstractItemModel::rowCount,
+ QAndroidModelIndexProxy::qInstance(parent));
+}
+
+jobject QAndroidItemModelProxy::jni_roleNames(JNIEnv *env, jobject object)
+{
+ auto roleNames = invokeNativeImpl(env, object, &QAndroidItemModelProxy::defaultRoleNames,
+ &QAbstractItemModel::roleNames);
+ HashMap jRoleNames{};
+ for (auto [role, roleName] : roleNames.asKeyValueRange()) {
+ const Integer jRole(role);
+ const QJniObject jRoleName = QJniObject::fromString(roleName);
+ jRoleNames.callMethod<jobject>("put", jRole.object(), jRoleName.object());
+ }
+ return env->NewLocalRef(jRoleNames.object());
+}
+
+jobject QAndroidItemModelProxy::jni_createIndex(JNIEnv *env, jobject object, jint row, jint column,
+ jlong id)
+{
+ QModelIndex (QAndroidItemModelProxy::*createIndexPtr)(int, int, quintptr) const =
+ &QAndroidItemModelProxy::createIndex;
+ const QModelIndex index = invokeNativeProxyMethod(env, object, createIndexPtr, row, column, id);
+ return env->NewLocalRef(QAndroidModelIndexProxy::jInstance(index).object());
+}
+
+jboolean QAndroidItemModelProxy::jni_canFetchMore(JNIEnv *env, jobject object, JQtModelIndex parent)
+{
+ return invokeNativeImpl(env, object, &QAndroidItemModelProxy::canFetchMoreDefault,
+ &QAbstractItemModel::canFetchMore,
+ QAndroidModelIndexProxy::qInstance(parent));
+}
+
+void QAndroidItemModelProxy::jni_fetchMore(JNIEnv *env, jobject object, JQtModelIndex parent)
+{
+ return invokeNativeImpl(env, object, &QAndroidItemModelProxy::fetchMoreDefault,
+ &QAbstractItemModel::fetchMore,
+ QAndroidModelIndexProxy::qInstance(parent));
+}
+
+jboolean QAndroidItemModelProxy::jni_hasChildren(JNIEnv *env, jobject object, JQtModelIndex parent)
+{
+ return invokeNativeImpl(env, object, &QAndroidItemModelProxy::hasChildrenDefault,
+ &QAbstractItemModel::hasChildren,
+ QAndroidModelIndexProxy::qInstance(parent));
+}
+
+jboolean QAndroidItemModelProxy::jni_hasIndex(JNIEnv *env, jobject object, jint row, jint column,
+ JQtModelIndex parent)
+{
+ return invokeNativeMethod(env, object, &QAbstractItemModel::hasIndex, row, column,
+ QAndroidModelIndexProxy::qInstance(parent));
+}
+
+void QAndroidItemModelProxy::jni_beginInsertColumns(JNIEnv *env, jobject object,
+ JQtModelIndex parent, jint first, jint last)
+{
+
+ invokeNativeProxyMethod(env, object, &QAndroidItemModelProxy::beginInsertColumns,
+ QAndroidModelIndexProxy::qInstance(parent), first, last);
+}
+
+void QAndroidItemModelProxy::jni_beginInsertRows(JNIEnv *env, jobject object, JQtModelIndex parent,
+ jint first, jint last)
+{
+ invokeNativeProxyMethod(env, object, &QAndroidItemModelProxy::beginInsertRows,
+ QAndroidModelIndexProxy::qInstance(parent), first, last);
+}
+
+jboolean QAndroidItemModelProxy::jni_beginMoveColumns(JNIEnv *env, jobject object,
+ JQtModelIndex sourceParent, jint sourceFirst,
+ jint sourceLast,
+ JQtModelIndex destinationParent,
+ jint destinationChild)
+{
+ return invokeNativeProxyMethod(
+ env, object, &QAndroidItemModelProxy::beginMoveColumns,
+ QAndroidModelIndexProxy::qInstance(sourceParent), sourceFirst, sourceLast,
+ QAndroidModelIndexProxy::qInstance(destinationParent), destinationChild);
+}
+
+jboolean QAndroidItemModelProxy::jni_beginMoveRows(JNIEnv *env, jobject object,
+ JQtModelIndex sourceParent, jint sourceFirst,
+ jint sourceLast, JQtModelIndex destinationParent,
+ jint destinationChild)
+{
+ return invokeNativeProxyMethod(
+ env, object, &QAndroidItemModelProxy::beginMoveRows,
+ QAndroidModelIndexProxy::qInstance(sourceParent), sourceFirst, sourceLast,
+ QAndroidModelIndexProxy::qInstance(destinationParent), destinationChild);
+}
+
+void QAndroidItemModelProxy::jni_beginRemoveColumns(JNIEnv *env, jobject object,
+ JQtModelIndex parent, jint first, jint last)
+{
+ invokeNativeProxyMethod(env, object, &QAndroidItemModelProxy::beginRemoveColumns,
+ QAndroidModelIndexProxy::qInstance(parent), first, last);
+}
+
+void QAndroidItemModelProxy::jni_beginRemoveRows(JNIEnv *env, jobject object, JQtModelIndex parent,
+ jint first, jint last)
+{
+ invokeNativeProxyMethod(env, object, &QAndroidItemModelProxy::beginRemoveRows,
+ QAndroidModelIndexProxy::qInstance(parent), first, last);
+}
+
+void QAndroidItemModelProxy::jni_beginResetModel(JNIEnv *env, jobject object)
+{
+ invokeNativeProxyMethod(env, object, &QAndroidItemModelProxy::beginResetModel);
+}
+
+void QAndroidItemModelProxy::jni_endInsertColumns(JNIEnv *env, jobject object)
+{
+ invokeNativeProxyMethod(env, object, &QAndroidItemModelProxy::endInsertColumns);
+}
+
+void QAndroidItemModelProxy::jni_endInsertRows(JNIEnv *env, jobject object)
+{
+ invokeNativeProxyMethod(env, object, &QAndroidItemModelProxy::endInsertRows);
+}
+
+void QAndroidItemModelProxy::jni_endMoveColumns(JNIEnv *env, jobject object)
+{
+ invokeNativeProxyMethod(env, object, &QAndroidItemModelProxy::endMoveColumns);
+}
+
+void QAndroidItemModelProxy::jni_endMoveRows(JNIEnv *env, jobject object)
+{
+ invokeNativeProxyMethod(env, object, &QAndroidItemModelProxy::endMoveRows);
+}
+
+void QAndroidItemModelProxy::jni_endRemoveColumns(JNIEnv *env, jobject object)
+{
+ invokeNativeProxyMethod(env, object, &QAndroidItemModelProxy::endRemoveColumns);
+}
+
+void QAndroidItemModelProxy::jni_endRemoveRows(JNIEnv *env, jobject object)
+{
+ invokeNativeProxyMethod(env, object, &QAndroidItemModelProxy::endRemoveRows);
+}
+
+void QAndroidItemModelProxy::jni_endResetModel(JNIEnv *env, jobject object)
+{
+ invokeNativeProxyMethod(env, object, &QAndroidItemModelProxy::endResetModel);
+}
+
+jobject QAndroidItemModelProxy::jni_sibling(JNIEnv *env, jobject object, jint row, jint column,
+ JQtModelIndex parent)
+{
+ const QModelIndex index = invokeNativeImpl(env, object, &QAndroidItemModelProxy::siblingDefault,
+ &QAbstractItemModel::sibling, row, column,
+ QAndroidModelIndexProxy::qInstance(parent));
+ return env->NewLocalRef(QAndroidModelIndexProxy::jInstance(index).object());
+}
+
+bool QAndroidItemModelProxy::registerAbstractNatives(QJniEnvironment &env)
+{
+ return env.registerNativeMethods(
+ Traits<JQtAbstractItemModel>::className(),
+ { Q_JNI_NATIVE_SCOPED_METHOD(jni_roleNames, QAndroidItemModelProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(jni_canFetchMore, QAndroidItemModelProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(jni_createIndex, QAndroidItemModelProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(jni_fetchMore, QAndroidItemModelProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(jni_hasChildren, QAndroidItemModelProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(jni_hasIndex, QAndroidItemModelProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(jni_beginInsertColumns, QAndroidItemModelProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(jni_beginInsertRows, QAndroidItemModelProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(jni_beginMoveColumns, QAndroidItemModelProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(jni_beginMoveRows, QAndroidItemModelProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(jni_beginRemoveColumns, QAndroidItemModelProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(jni_beginRemoveRows, QAndroidItemModelProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(jni_beginResetModel, QAndroidItemModelProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(jni_endInsertColumns, QAndroidItemModelProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(jni_endInsertRows, QAndroidItemModelProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(jni_endMoveColumns, QAndroidItemModelProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(jni_endMoveRows, QAndroidItemModelProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(jni_endRemoveColumns, QAndroidItemModelProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(jni_endRemoveRows, QAndroidItemModelProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(jni_endResetModel, QAndroidItemModelProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(jni_sibling, QAndroidItemModelProxy) });
+}
+
+bool QAndroidItemModelProxy::registerProxyNatives(QJniEnvironment &env)
+{
+ return env.registerNativeMethods(
+ Traits<JQtAndroidItemModelProxy>::className(),
+ { Q_JNI_NATIVE_SCOPED_METHOD(jni_columnCount, QAndroidItemModelProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(jni_data, QAndroidItemModelProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(jni_index, QAndroidItemModelProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(jni_parent, QAndroidItemModelProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(jni_rowCount, QAndroidItemModelProxy) });
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/platform/android/qandroiditemmodelproxy_p.h b/src/quick/platform/android/qandroiditemmodelproxy_p.h
new file mode 100644
index 0000000000..6670395596
--- /dev/null
+++ b/src/quick/platform/android/qandroiditemmodelproxy_p.h
@@ -0,0 +1,191 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QANDROIDITEMMODELPROXY_P_H
+#define QANDROIDITEMMODELPROXY_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#include <QtQuick/private/qandroidmodelindexproxy_p.h>
+#include <QtQuick/private/qandroidtypes_p.h>
+#include <QtQuick/private/qtquickglobal_p.h>
+
+#include <QtCore/qabstractitemmodel.h>
+#include <QtCore/qjniobject.h>
+#include <QtCore/qjnienvironment.h>
+#include <QtCore/qjnitypes.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_QUICK_EXPORT QAndroidItemModelProxy : public QAbstractItemModel
+{
+ Q_OBJECT
+
+public:
+ explicit QAndroidItemModelProxy(QtJniTypes::JQtAbstractItemModel jInstance)
+ : jInstance(jInstance)
+ {
+ }
+
+ int columnCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QModelIndex index(int row, int column,
+ const QModelIndex &parent = QModelIndex()) const override;
+ QModelIndex parent(const QModelIndex &index) const override;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+
+ bool canFetchMore(const QModelIndex &parent) const override;
+ bool canFetchMoreDefault(const QModelIndex &parent) const;
+ QHash<int, QByteArray> roleNames() const override;
+ QHash<int, QByteArray> defaultRoleNames() const;
+ void fetchMore(const QModelIndex &parent) override;
+ void fetchMoreDefault(const QModelIndex &parent);
+ bool hasChildren(const QModelIndex &parent) const override;
+ bool hasChildrenDefault(const QModelIndex &parent) const;
+ QModelIndex sibling(int row, int column, const QModelIndex &parent) const override;
+ QModelIndex siblingDefault(int row, int column, const QModelIndex &parent);
+
+ Q_REQUIRED_RESULT static QAbstractItemModel *
+ nativeInstance(QtJniTypes::JQtAbstractItemModel itemModel);
+ Q_REQUIRED_RESULT static QAbstractItemModel *createNativeProxy(QJniObject itemModel);
+ static QJniObject createProxy(QAbstractItemModel *abstractClass);
+
+ template <typename Func, typename... Args>
+ static auto invokeNativeProxyMethod(JNIEnv */*env*/, jobject jvmObject, Func func, Args &&...args)
+ {
+ Q_ASSERT(jvmObject);
+ auto model = qobject_cast<QAndroidItemModelProxy *>(nativeInstance(jvmObject));
+ Q_ASSERT(model);
+ return std::invoke(func, model, std::forward<Args>(args)...);
+ }
+
+ template <typename Func, typename... Args>
+ static auto invokeNativeMethod(JNIEnv */*env*/, jobject jvmObject, Func func, Args &&...args)
+ {
+ Q_ASSERT(jvmObject);
+ auto model = nativeInstance(jvmObject);
+ Q_ASSERT(model);
+ return std::invoke(func, model, std::forward<Args>(args)...);
+ }
+
+ template <typename Func1, typename Func2, typename... Args>
+ static auto invokeNativeImpl(JNIEnv */*env*/, jobject jvmObject, Func1 defaultFunc, Func2 func,
+ Args &&...args)
+ {
+ Q_ASSERT(jvmObject);
+ auto nativeModel = nativeInstance(jvmObject);
+ auto nativeProxyModel = qobject_cast<QAndroidItemModelProxy *>(nativeModel);
+ if (nativeProxyModel)
+ return std::invoke(defaultFunc, nativeProxyModel, std::forward<Args>(args)...);
+ else
+ return std::invoke(func, nativeModel, std::forward<Args>(args)...);
+ }
+
+ static jint jni_columnCount(JNIEnv *env, jobject object, JQtModelIndex parent);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_columnCount)
+
+ static jobject jni_data(JNIEnv *env, jobject object, JQtModelIndex index, jint role);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_data)
+
+ static jobject jni_index(JNIEnv *env, jobject object, jint row, jint column,
+ JQtModelIndex parent);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_index)
+
+ static jobject jni_parent(JNIEnv *env, jobject object, JQtModelIndex index);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_parent)
+
+ static jint jni_rowCount(JNIEnv *env, jobject object, JQtModelIndex parent);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_rowCount)
+
+ static jboolean jni_canFetchMore(JNIEnv *env, jobject object, JQtModelIndex parent);
+ QT_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE_2(jni_canFetchMore, canFetchMore)
+
+ static void jni_fetchMore(JNIEnv *env, jobject object, JQtModelIndex parent);
+ QT_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE_2(jni_fetchMore, fetchMore)
+
+ static jboolean jni_hasChildren(JNIEnv *env, jobject object, JQtModelIndex parent);
+ QT_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE_2(jni_hasChildren, hasChildren)
+
+ static jboolean jni_hasIndex(JNIEnv *env, jobject object, jint row, jint column,
+ JQtModelIndex parent);
+ QT_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE_2(jni_hasIndex, hasIndex)
+
+ static jobject jni_roleNames(JNIEnv *env, jobject object);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_roleNames)
+
+ static void jni_beginInsertColumns(JNIEnv *env, jobject object, JQtModelIndex parent,
+ jint first, jint last);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_beginInsertColumns)
+
+ static void jni_beginInsertRows(JNIEnv *env, jobject object, JQtModelIndex parent, jint first,
+ jint last);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_beginInsertRows)
+
+ static jboolean jni_beginMoveColumns(JNIEnv *env, jobject object, JQtModelIndex sourceParent,
+ jint sourceFirst, jint sourceLast,
+ JQtModelIndex destinationParent, jint destinationChild);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_beginMoveColumns)
+
+ static jboolean jni_beginMoveRows(JNIEnv *env, jobject object, JQtModelIndex sourceParent,
+ jint sourceFirst, jint sourceLast,
+ JQtModelIndex destinationParent, jint destinationChild);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_beginMoveRows)
+
+ static void jni_beginRemoveColumns(JNIEnv *env, jobject object, JQtModelIndex parent,
+ jint first, jint last);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_beginRemoveColumns)
+
+ static void jni_beginRemoveRows(JNIEnv *env, jobject object, JQtModelIndex parent, jint first,
+ jint last);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_beginRemoveRows)
+
+ static void jni_beginResetModel(JNIEnv *env, jobject object);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_beginResetModel)
+
+ static jobject jni_createIndex(JNIEnv *env, jobject object, jint row, jint column, jlong id);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_createIndex)
+
+ static void jni_endInsertColumns(JNIEnv *env, jobject object);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_endInsertColumns)
+
+ static void jni_endInsertRows(JNIEnv *env, jobject object);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_endInsertRows)
+
+ static void jni_endMoveColumns(JNIEnv *env, jobject object);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_endMoveColumns)
+
+ static void jni_endMoveRows(JNIEnv *env, jobject object);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_endMoveRows)
+
+ static void jni_endRemoveColumns(JNIEnv *env, jobject object);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_endRemoveColumns)
+
+ static void jni_endRemoveRows(JNIEnv *env, jobject object);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_endRemoveRows)
+
+ static void jni_endResetModel(JNIEnv *env, jobject object);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_endResetModel)
+
+ static jobject jni_sibling(JNIEnv *env, jobject object, jint row, jint column,
+ JQtModelIndex parent);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_sibling)
+
+ static bool registerAbstractNatives(QJniEnvironment &env);
+ static bool registerProxyNatives(QJniEnvironment &env);
+
+private:
+ QJniObject jInstance;
+ friend class QAndroidModelIndexProxy;
+};
+
+QT_END_NAMESPACE
+
+#endif // QANDROIDITEMMODELPROXY_P_H
diff --git a/src/quick/platform/android/qandroidmodelindexproxy.cpp b/src/quick/platform/android/qandroidmodelindexproxy.cpp
new file mode 100644
index 0000000000..b749d9f344
--- /dev/null
+++ b/src/quick/platform/android/qandroidmodelindexproxy.cpp
@@ -0,0 +1,103 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <QtQuick/private/qandroiditemmodelproxy_p.h>
+#include <QtQuick/private/qandroidmodelindexproxy_p.h>
+#include <QtQuick/private/qandroidtypeconverter_p.h>
+
+#include <QtCore/qjniarray.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace QtJniTypes;
+
+QModelIndex QAndroidModelIndexProxy::qInstance(JQtModelIndex jModelIndex)
+{
+ if (!jModelIndex.isValid())
+ return QModelIndex();
+
+ const QJniArray<jlong> jPrivateArray = jModelIndex.getField<jlong[]>("m_privateData");
+ const auto privateData = jPrivateArray.toContainer();
+ Q_ASSERT(privateData.size() == 4);
+
+ const jlong modelReference = privateData[3];
+ if (!modelReference)
+ return QModelIndex();
+
+ const jint row = privateData[0];
+ const jint column = privateData[1];
+ QAbstractItemModel *model = reinterpret_cast<QAbstractItemModel *>(modelReference);
+ QAndroidItemModelProxy *proxyModel = qobject_cast<QAndroidItemModelProxy *>(model);
+
+ // If the native model instance is a proxy we have access to the protected function
+ // createIndex(). Else, if the native instance is not a results Java->Qt proxy, we
+ // use index() to get the QModelIndex.
+ if (proxyModel) {
+ const jint internalId = privateData[2];
+ return proxyModel->createIndex(row, column, internalId);
+ } else {
+ const JQtModelIndex parent = jModelIndex.getField<JQtModelIndex>("m_parent");
+ if (parent.isValid())
+ return model->index(row, column, QAndroidModelIndexProxy::qInstance(parent));
+ }
+ return QModelIndex();
+}
+
+JQtModelIndex QAndroidModelIndexProxy::jInstance(QModelIndex modelIndex)
+{
+ if (!modelIndex.isValid())
+ return JQtModelIndex();
+ bool isModelProxy = qobject_cast<const QAndroidItemModelProxy *>(modelIndex.model());
+ if (isModelProxy)
+ return JQtModelIndex(modelIndex.row(), modelIndex.column(), modelIndex.internalId(),
+ reinterpret_cast<jlong>(modelIndex.model()));
+ else
+ return JQtModelIndex(modelIndex.row(), modelIndex.column(),
+ QAndroidModelIndexProxy::jInstance(modelIndex.parent()),
+ reinterpret_cast<jlong>(modelIndex.model()));
+}
+
+jobject QAndroidModelIndexProxy::data(JNIEnv *env, jobject object, int role)
+{
+ Q_ASSERT(env);
+ Q_ASSERT(object);
+
+ QModelIndex modelIndex = qInstance(object);
+ if (!modelIndex.isValid())
+ return nullptr;
+
+ return QAndroidTypeConverter::toJavaObject(modelIndex.model()->data(modelIndex, role), env);
+}
+
+jlong QAndroidModelIndexProxy::internalId(JNIEnv *env, jobject object)
+{
+ Q_ASSERT(env);
+ Q_ASSERT(object);
+ return qInstance(object).internalId();
+};
+
+jboolean QAndroidModelIndexProxy::isValid(JNIEnv *env, jobject object)
+{
+ Q_ASSERT(env);
+ Q_ASSERT(object);
+ return qInstance(object).isValid();
+}
+
+JQtModelIndex QAndroidModelIndexProxy::parent(JNIEnv *env, jobject object)
+{
+ Q_ASSERT(env);
+ Q_ASSERT(object);
+ return jInstance(qInstance(object).parent());
+};
+
+bool QAndroidModelIndexProxy::registerNatives(QJniEnvironment &env)
+{
+ return env.registerNativeMethods(
+ Traits<JQtModelIndex>::className(),
+ { Q_JNI_NATIVE_SCOPED_METHOD(data, QAndroidModelIndexProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(internalId, QAndroidModelIndexProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(isValid, QAndroidModelIndexProxy),
+ Q_JNI_NATIVE_SCOPED_METHOD(parent, QAndroidModelIndexProxy) });
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/platform/android/qandroidmodelindexproxy_p.h b/src/quick/platform/android/qandroidmodelindexproxy_p.h
new file mode 100644
index 0000000000..05db788652
--- /dev/null
+++ b/src/quick/platform/android/qandroidmodelindexproxy_p.h
@@ -0,0 +1,56 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QANDROIDMODELINDEXPROXY_P_H
+#define QANDROIDMODELINDEXPROXY_P_H
+
+#include <QtQuick/private/qandroidtypes_p.h>
+#include <QtQuick/private/qtquickglobal_p.h>
+
+#include <QtCore/qabstractitemmodel.h>
+#include <QtCore/qjniobject.h>
+#include <QtCore/qjnienvironment.h>
+#include <QtCore/qjnitypes.h>
+#include <QDebug>
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+QT_BEGIN_NAMESPACE
+
+using namespace QtJniTypes;
+
+class QAndroidItemModelProxy;
+
+class Q_QUICK_EXPORT QAndroidModelIndexProxy
+{
+public:
+ static JQtModelIndex jInstance(QModelIndex modelIndex);
+ static QModelIndex qInstance(JQtModelIndex jModelIndex);
+
+ static jobject data(JNIEnv *env, jobject object, int role);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(data)
+
+ static jlong internalId(JNIEnv *env, jobject object);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(internalId)
+
+ static jboolean isValid(JNIEnv *env, jobject object);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(isValid)
+
+ static JQtModelIndex parent(JNIEnv *env, jobject object);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(parent)
+
+ static bool registerNatives(QJniEnvironment &env);
+};
+
+QT_BEGIN_NAMESPACE
+
+#endif // QANDROIDMODELINDEXPROXY_P_H
diff --git a/src/quick/platform/android/qandroidquickviewembedding.cpp b/src/quick/platform/android/qandroidquickviewembedding.cpp
new file mode 100644
index 0000000000..262dd9179c
--- /dev/null
+++ b/src/quick/platform/android/qandroidquickviewembedding.cpp
@@ -0,0 +1,332 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <QtQuick/private/qandroidquickviewembedding_p.h>
+#include <QtQuick/private/qandroidtypes_p.h>
+#include <QtQuick/private/qandroidtypeconverter_p.h>
+#include <QtQuick/private/qandroidviewsignalmanager_p.h>
+#include <QtQuick/private/qandroiditemmodelproxy_p.h>
+#include <QtQuick/private/qandroidmodelindexproxy_p.h>
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qjnienvironment.h>
+#include <QtCore/qjniobject.h>
+#include <QtCore/qjniarray.h>
+#include <QtCore/qjnitypes.h>
+#include <QtQml/qqmlengine.h>
+#include <QtQuick/qquickitem.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_JNI_CLASS(QtDelegate, "org/qtproject/qt/android/QtEmbeddedContextDelegate");
+Q_DECLARE_JNI_CLASS(QtQuickView, "org/qtproject/qt/android/QtQuickView");
+Q_DECLARE_JNI_CLASS(QtWindow, "org/qtproject/qt/android/QtWindow");
+Q_DECLARE_JNI_CLASS(View, "android/view/View");
+
+namespace QtAndroidQuickViewEmbedding
+{
+ constexpr const char *uninitializedViewMessage = "because QtQuickView is not loaded or ready yet.";
+
+ void createQuickView(JNIEnv *, jobject nativeWindow, jstring qmlUri, jint width, jint height,
+ jlong parentWindowReference, jlong viewReference,
+ const QJniArray<jstring> &qmlImportPaths)
+ {
+ static_assert (sizeof(jlong) >= sizeof(void*),
+ "Insufficient size of Java type to hold the c++ pointer");
+ const QUrl qmlUrl(QJniObject(qmlUri).toString());
+
+ const QStringList importPaths = qmlImportPaths.toContainer();
+ QMetaObject::invokeMethod(qApp, [qtViewObject = QJniObject(nativeWindow),
+ parentWindowReference,
+ viewReference,
+ width,
+ height,
+ qmlUrl,
+ importPaths] {
+ // If the view does not exists (viewReference==0) we should create and set it up.
+ // Else we only reset the source of the view.
+ QAndroidQuickView *view = reinterpret_cast<QAndroidQuickView *>(viewReference);
+ if (!view) {
+ QWindow *parentWindow = reinterpret_cast<QWindow *>(parentWindowReference);
+ view = new QAndroidQuickView(parentWindow);
+ QObject::connect(view, &QAndroidQuickView::statusChanged, view,
+ [qtViewObject](QAndroidQuickView::Status status) {
+ qtViewObject.callMethod<void>("handleStatusChange", status);
+ });
+ view->setResizeMode(QAndroidQuickView::SizeRootObjectToView);
+ view->setColor(QColor(Qt::transparent));
+ view->setWidth(width);
+ view->setHeight(height);
+ QQmlEngine *engine = view->engine();
+ for (const QString &path : importPaths)
+ engine->addImportPath(path);
+
+ const QtJniTypes::QtWindow window = reinterpret_cast<jobject>(view->winId());
+ qtViewObject.callMethod<void>("addQtWindow",
+ window,
+ reinterpret_cast<jlong>(view),
+ parentWindowReference);
+ }
+ view->setSource(qmlUrl);
+ });
+ }
+
+ std::pair<QAndroidQuickView *, QQuickItem *> getViewAndRootObject(jlong windowReference)
+ {
+ QAndroidQuickView *view = reinterpret_cast<QAndroidQuickView *>(windowReference);
+ QQuickItem *rootObject = Q_LIKELY(view) ? view->rootObject() : nullptr;
+ return std::make_pair(view, rootObject);
+ }
+
+ void setRootObjectProperty(JNIEnv *env, jobject object, jlong windowReference,
+ jstring propertyName, jobject value)
+ {
+ Q_UNUSED(env);
+ Q_UNUSED(object);
+
+ auto [_, rootObject] = getViewAndRootObject(windowReference);
+ if (!rootObject) {
+ qWarning("Cannot set property %s %s", qPrintable(QJniObject(propertyName).toString()),
+ uninitializedViewMessage);
+ return;
+ }
+
+ const QString property = QJniObject(propertyName).toString();
+ const QMetaObject *rootMetaObject = rootObject->metaObject();
+ int propertyIndex = rootMetaObject->indexOfProperty(qPrintable(property));
+ if (propertyIndex < 0) {
+ qWarning("Property %s does not exist in the root QML object.", qPrintable(property));
+ return;
+ }
+
+ QMetaProperty metaProperty = rootMetaObject->property(propertyIndex);
+ const QJniObject propertyValue(value);
+ const QVariant variantToWrite = QAndroidTypeConverter::toQVariant(propertyValue);
+
+ if (!variantToWrite.isValid()) {
+ qWarning("Setting the property type of %s is not supported.",
+ qPrintable(propertyValue.className()));
+ } else {
+ metaProperty.write(rootObject, variantToWrite);
+ }
+ }
+
+ jobject getRootObjectProperty(JNIEnv *env, jobject object, jlong windowReference,
+ jstring propertyName)
+ {
+ Q_UNUSED(object);
+ Q_ASSERT(env);
+
+ const QString property = QJniObject(propertyName).toString();
+ auto [_, rootObject] = getViewAndRootObject(windowReference);
+ if (!rootObject) {
+ qWarning("Cannot get property %s %s", qPrintable(property), uninitializedViewMessage);
+ return nullptr;
+ }
+
+ const QMetaObject *rootMetaObject = rootObject->metaObject();
+ int propertyIndex = rootMetaObject->indexOfProperty(property.toUtf8().constData());
+ if (propertyIndex < 0) {
+ qWarning("Cannot get property %s as it does not exist in the root QML object.",
+ qPrintable(property));
+ return nullptr;
+ }
+
+ QMetaProperty metaProperty = rootMetaObject->property(propertyIndex);
+ const QVariant propertyValue = metaProperty.read(rootObject);
+ jobject jObject = QAndroidTypeConverter::toJavaObject(propertyValue, env);
+ if (!jObject) {
+ qWarning("Property %s cannot be converted to a supported Java data type.",
+ qPrintable(property));
+ }
+ return jObject;
+ }
+
+ int addRootObjectSignalListener(JNIEnv *env, jobject, jlong windowReference, jstring signalName,
+ jclass argType, jobject listener)
+ {
+ Q_ASSERT(env);
+ static QHash<QByteArray, int> javaToQMetaType = {
+ { "java/lang/Void", QMetaType::Type::Void },
+ { "java/lang/String", QMetaType::Type::QString },
+ { "java/lang/Integer", QMetaType::Type::Int },
+ { "java/lang/Double", QMetaType::Type::Double },
+ { "java/lang/Float", QMetaType::Type::Float },
+ { "java/lang/Boolean", QMetaType::Type::Bool }
+ };
+
+ auto [view, rootObject] = getViewAndRootObject(windowReference);
+ if (!rootObject) {
+ qWarning("Cannot connect to signal %s %s",
+ qPrintable(QJniObject(signalName).toString()), uninitializedViewMessage);
+ return -1;
+ }
+
+ QAndroidViewSignalManager *signalManager = view->signalManager();
+ const QByteArray javaArgClass = QJniObject(argType).className();
+ const char *qArgName =
+ QMetaType(javaToQMetaType.value(javaArgClass, QMetaType::Type::UnknownType)).name();
+ const QString signalMethodName = QJniObject(signalName).toString();
+
+ const QMetaObject *metaObject = rootObject->metaObject();
+ int signalIndex = -1;
+ int propertyIndex = -1;
+
+ QByteArray signalSignature = QMetaObject::normalizedSignature(qPrintable(
+ QStringLiteral("%1(%2)").arg(signalMethodName).arg(QLatin1StringView(qArgName))));
+ signalIndex = metaObject->indexOfSignal(signalSignature.constData());
+
+ // Try to check if the signal is a parameterless notifier of a property
+ // or a property name itself.
+ if (signalIndex == -1) {
+ signalSignature = QMetaObject::normalizedSignature(
+ qPrintable(QStringLiteral("%1()").arg(signalMethodName)));
+ for (int i = 0; i < metaObject->propertyCount(); ++i) {
+ QMetaProperty metaProperty = metaObject->property(i);
+ QMetaMethod notifyMethod = metaProperty.notifySignal();
+
+ if (signalSignature == notifyMethod.methodSignature()) {
+ signalIndex = metaObject->property(i).notifySignalIndex();
+ propertyIndex = i;
+ break;
+ } else if (signalMethodName == QLatin1StringView(metaProperty.name())) {
+ signalIndex = metaObject->property(i).notifySignalIndex();
+ signalSignature = notifyMethod.methodSignature();
+ propertyIndex = i;
+ break;
+ }
+ }
+ }
+
+ if (signalIndex == -1)
+ return -1;
+
+ const QMetaObject *helperMetaObject = signalManager->metaObject();
+ QByteArray helperSignalSignature = signalSignature;
+ helperSignalSignature.replace(0, signalSignature.indexOf('('), "forwardSignal");
+ int helperSlotIndex = helperMetaObject->indexOfSlot(helperSignalSignature.constData());
+ if (helperSlotIndex == -1)
+ return -1;
+
+ // Return the id if the signal is already connected to the same listener.
+ QJniObject listenerJniObject(listener);
+ if (signalManager->connectionInfoMap.contains(signalSignature)) {
+ auto connectionInfos = signalManager->connectionInfoMap.values(signalSignature);
+ auto isSameListener =
+ [listenerJniObject](
+ const QAndroidViewSignalManager::ConnectionInfo &connectionInfo) {
+ return connectionInfo.listener == listenerJniObject;
+ };
+ auto iterator = std::find_if(connectionInfos.constBegin(),
+ connectionInfos.constEnd(),
+ isSameListener);
+ if (iterator != connectionInfos.end()) {
+ qWarning("Signal listener with the ID of %i is already connected to %s signal.",
+ iterator->id,
+ signalSignature.constData());
+ return iterator->id;
+ }
+ }
+
+ QMetaMethod signalMethod = metaObject->method(signalIndex);
+ QMetaMethod signalForwarderMethod = helperMetaObject->method(helperSlotIndex);
+ signalManager->connectionHandleCounter++;
+
+ QMetaObject::Connection connection;
+ if (signalManager->connectionInfoMap.contains(signalSignature)) {
+ const int existingId = signalManager->connectionInfoMap.value(signalSignature).id;
+ connection = signalManager->connections[existingId];
+ } else {
+ connection = QObject::connect(rootObject,
+ signalMethod,
+ signalManager,
+ signalForwarderMethod);
+ }
+
+ QAndroidViewSignalManager::ConnectionInfo connectionInfo;
+ connectionInfo.listener = listenerJniObject;
+ connectionInfo.javaArgType = javaArgClass;
+ connectionInfo.propertyIndex = propertyIndex;
+ connectionInfo.signalSignature = signalSignature;
+ connectionInfo.id = signalManager->connectionHandleCounter;
+
+ signalManager->connectionInfoMap.insert(signalSignature, connectionInfo);
+ signalManager->connections.insert(connectionInfo.id, connection);
+
+ return connectionInfo.id;
+ }
+
+ bool removeRootObjectSignalListener(JNIEnv *, jobject, jlong windowReference,
+ jint signalListenerId)
+ {
+ auto [view, rootObject] = getViewAndRootObject(windowReference);
+ if (!rootObject) {
+ qWarning("Cannot disconnect the signal connection with id: %i %s", signalListenerId,
+ uninitializedViewMessage);
+ return false;
+ }
+
+ QAndroidViewSignalManager *signalManager = view->signalManager();
+ if (!signalManager->connections.contains(signalListenerId))
+ return false;
+
+ QByteArray signalSignature;
+ for (auto listenerInfoIter = signalManager->connectionInfoMap.begin();
+ listenerInfoIter != signalManager->connectionInfoMap.end();) {
+ if (listenerInfoIter->id == signalListenerId) {
+ signalSignature = listenerInfoIter->signalSignature;
+ signalManager->connectionInfoMap.erase(listenerInfoIter);
+ break;
+ } else {
+ ++listenerInfoIter;
+ }
+ }
+
+ // disconnect if its the last listener associated with the signal signatures
+ if (!signalManager->connectionInfoMap.contains(signalSignature))
+ rootObject->disconnect(signalManager->connections.value(signalListenerId));
+
+ signalManager->connections.remove(signalListenerId);
+ return true;
+ }
+
+ bool registerNatives(QJniEnvironment& env) {
+ return env.registerNativeMethods(QtJniTypes::Traits<QtJniTypes::QtQuickView>::className(),
+ {Q_JNI_NATIVE_SCOPED_METHOD(createQuickView,
+ QtAndroidQuickViewEmbedding),
+ Q_JNI_NATIVE_SCOPED_METHOD(setRootObjectProperty,
+ QtAndroidQuickViewEmbedding),
+ Q_JNI_NATIVE_SCOPED_METHOD(getRootObjectProperty,
+ QtAndroidQuickViewEmbedding),
+ Q_JNI_NATIVE_SCOPED_METHOD(addRootObjectSignalListener,
+ QtAndroidQuickViewEmbedding),
+ Q_JNI_NATIVE_SCOPED_METHOD(removeRootObjectSignalListener,
+ QtAndroidQuickViewEmbedding)});
+ }
+}
+
+Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
+{
+ Q_UNUSED(vm)
+ Q_UNUSED(reserved)
+
+ static bool initialized = false;
+ if (initialized)
+ return JNI_VERSION_1_6;
+ initialized = true;
+
+ QJniEnvironment env;
+ if (!env.isValid())
+ return JNI_ERR;
+ if (!QtAndroidQuickViewEmbedding::registerNatives(env))
+ return JNI_ERR;
+ if (!QAndroidItemModelProxy::registerAbstractNatives(env))
+ return JNI_ERR;
+ if (!QAndroidItemModelProxy::registerProxyNatives(env))
+ return JNI_ERR;
+ if (!QAndroidModelIndexProxy::registerNatives(env))
+ return JNI_ERR;
+ return JNI_VERSION_1_6;
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/platform/android/qandroidquickviewembedding_p.h b/src/quick/platform/android/qandroidquickviewembedding_p.h
new file mode 100644
index 0000000000..43ff997264
--- /dev/null
+++ b/src/quick/platform/android/qandroidquickviewembedding_p.h
@@ -0,0 +1,62 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QANDROIDQUICKVIEWEMBEDDING_P_H
+#define QANDROIDQUICKVIEWEMBEDDING_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtQuick/private/qandroidviewsignalmanager_p.h>
+
+#include <QtCore/qjnienvironment.h>
+#include <QtCore/qjnitypes.h>
+#include <QtQuick/qquickview.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QtAndroidQuickViewEmbedding
+{
+ bool registerNatives(QJniEnvironment& env);
+ void createQuickView(JNIEnv *env, jobject nativeWindow, jstring qmlUri, jint width, jint height,
+ jlong parentWindowReference, jlong viewReference,
+ const QJniArray<jstring> &qmlImportPaths);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(createQuickView)
+ void setRootObjectProperty(JNIEnv *env, jobject, jlong parentWindowReference,
+ jstring propertyName, jobject value);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(setRootObjectProperty)
+ jobject getRootObjectProperty(JNIEnv *env, jobject, jlong parentWindowReference,
+ jstring propertyName);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(getRootObjectProperty)
+ int addRootObjectSignalListener(JNIEnv *env, jobject, jlong parentWindowReference,
+ jstring signalName, jclass argType, jobject listener);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(addRootObjectSignalListener)
+ bool removeRootObjectSignalListener(JNIEnv *env, jobject, jlong parentWindowReference,
+ jint signalListenerId);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(removeRootObjectSignalListener)
+
+ class QAndroidQuickView : public QQuickView
+ {
+ Q_OBJECT
+ std::unique_ptr<QAndroidViewSignalManager> m_signalManager;
+
+ public:
+ explicit QAndroidQuickView(QWindow *parent)
+ : QQuickView(parent), m_signalManager(new QAndroidViewSignalManager())
+ {
+ }
+ inline QAndroidViewSignalManager *signalManager() const { return m_signalManager.get(); };
+ };
+};
+
+QT_END_NAMESPACE
+
+#endif // QANDROIDQUICKVIEWEMBEDDING_P_H
diff --git a/src/quick/platform/android/qandroidtypeconverter_p.h b/src/quick/platform/android/qandroidtypeconverter_p.h
new file mode 100644
index 0000000000..1bde5a5464
--- /dev/null
+++ b/src/quick/platform/android/qandroidtypeconverter_p.h
@@ -0,0 +1,99 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QANDROIDTYPECONVERTER_P_H
+#define QANDROIDTYPECONVERTER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#include <QtQuick/private/qandroidtypes_p.h>
+#include <QtQuick/private/qandroiditemmodelproxy_p.h>
+
+#include <QtCore/qjniobject.h>
+#include <QtCore/qjnienvironment.h>
+#include <QtCore/qjnitypes.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QAndroidTypeConverter
+{
+ [[maybe_unused]] static QVariant toQVariant(const QJniObject &object)
+ {
+ using namespace QtJniTypes;
+ if (!object.isValid())
+ return QVariant{};
+ const QByteArray classname(object.className());
+
+ if (classname == Traits<String>::className())
+ return object.toString();
+ else if (classname == Traits<Integer>::className())
+ return object.callMethod<jint>("intValue");
+ else if (classname == Traits<Long>::className())
+ return QVariant::fromValue<long>(object.callMethod<jlong>("longValue"));
+ else if (classname == Traits<Double>::className())
+ return object.callMethod<jdouble>("doubleValue");
+ else if (classname == Traits<Float>::className())
+ return object.callMethod<jfloat>("floatValue");
+ else if (classname == Traits<Boolean>::className())
+ return QVariant::fromValue<bool>(object.callMethod<jboolean>("booleanValue"));
+ else {
+ QJniEnvironment env;
+ const jclass className = env.findClass(Traits<JQtAbstractItemModel>::className());
+ if (env->IsInstanceOf(object.object(), className))
+ return QVariant::fromValue(QAndroidItemModelProxy::createNativeProxy(object));
+ }
+
+ return QVariant{};
+ }
+
+ [[maybe_unused]] Q_REQUIRED_RESULT static jobject toJavaObject(const QVariant &var, JNIEnv *env)
+ {
+ Q_ASSERT(env);
+ switch (var.typeId()) {
+ case QMetaType::Type::Int:
+ return env->NewLocalRef(QJniObject::construct<QtJniTypes::Integer>(
+ get<int>(var))
+ .object());
+ case QMetaType::Type::Long:
+ case QMetaType::Type::LongLong:
+ return env->NewLocalRef(QJniObject::construct<QtJniTypes::Long>(
+ get<jlong>(var))
+ .object());
+ case QMetaType::Type::Double:
+ return env->NewLocalRef(QJniObject::construct<QtJniTypes::Double>(
+ get<double>(var))
+ .object());
+ case QMetaType::Type::Float:
+ return env->NewLocalRef(QJniObject::construct<QtJniTypes::Float>(
+ get<float>(var))
+ .object());
+ case QMetaType::Type::Bool:
+ return env->NewLocalRef(QJniObject::construct<QtJniTypes::Boolean>(
+ get<bool>(var))
+ .object());
+ case QMetaType::Type::QString:
+ return env->NewLocalRef(
+ QJniObject::fromString(get<QString>(var)).object());
+ default:
+ if (var.canConvert<QAbstractItemModel *>()) {
+ return env->NewLocalRef(
+ QAndroidItemModelProxy::createProxy(var.value<QAbstractItemModel *>())
+ .object());
+ } else
+ return nullptr;
+ }
+ return nullptr;
+ }
+};
+
+QT_END_NAMESPACE
+
+#endif // QANDROIDTYPECONVERTER_P_H
diff --git a/src/quick/platform/android/qandroidtypes_p.h b/src/quick/platform/android/qandroidtypes_p.h
new file mode 100644
index 0000000000..e2c25f6965
--- /dev/null
+++ b/src/quick/platform/android/qandroidtypes_p.h
@@ -0,0 +1,43 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QANDROIDTYPES_P_H
+#define QANDROIDTYPES_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#include <QtCore/qjniobject.h>
+#include <QtCore/qjnienvironment.h>
+#include <QtCore/qjnitypes.h>
+
+QT_BEGIN_NAMESPACE
+
+#ifndef QT_DECLARE_JNI_CLASS_STANDARD_TYPES
+Q_DECLARE_JNI_CLASS(Void, "java/lang/Void");
+Q_DECLARE_JNI_CLASS(Integer, "java/lang/Integer");
+Q_DECLARE_JNI_CLASS(Long, "java/lang/Long");
+Q_DECLARE_JNI_CLASS(Double, "java/lang/Double");
+Q_DECLARE_JNI_CLASS(Float, "java/lang/Float");
+Q_DECLARE_JNI_CLASS(Boolean, "java/lang/Boolean");
+Q_DECLARE_JNI_CLASS(String, "java/lang/String");
+Q_DECLARE_JNI_CLASS(Class, "java/lang/Class");
+
+Q_DECLARE_JNI_CLASS(HashMap, "java/util/HashMap")
+Q_DECLARE_JNI_CLASS(Set, "java/util/Set")
+#endif
+
+Q_DECLARE_JNI_CLASS(JQtAbstractItemModel, "org/qtproject/qt/android/QtAbstractItemModel")
+Q_DECLARE_JNI_CLASS(JQtAndroidItemModelProxy, "org/qtproject/qt/android/QtAndroidItemModelProxy")
+Q_DECLARE_JNI_CLASS(JQtModelIndex, "org/qtproject/qt/android/QtModelIndex")
+
+QT_END_NAMESPACE
+
+#endif // QANDROIDTYPES_P_H
diff --git a/src/quick/platform/android/qandroidviewsignalmanager.cpp b/src/quick/platform/android/qandroidviewsignalmanager.cpp
new file mode 100644
index 0000000000..9fb5fc4ec5
--- /dev/null
+++ b/src/quick/platform/android/qandroidviewsignalmanager.cpp
@@ -0,0 +1,77 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <QtQuick/private/qandroidtypeconverter_p.h>
+#include <QtQuick/private/qandroidviewsignalmanager_p.h>
+
+QT_BEGIN_NAMESPACE
+
+void QAndroidViewSignalManager::forwardSignal()
+{
+ invokeListener(sender(), senderSignalIndex(), QVariant());
+}
+
+void QAndroidViewSignalManager::forwardSignal(int signalValue)
+{
+ invokeListener(sender(), senderSignalIndex(), QVariant(signalValue));
+}
+
+void QAndroidViewSignalManager::forwardSignal(bool signalValue)
+{
+ invokeListener(sender(), senderSignalIndex(), QVariant(signalValue));
+}
+
+void QAndroidViewSignalManager::forwardSignal(double signalValue)
+{
+ invokeListener(sender(), senderSignalIndex(), QVariant(signalValue));
+}
+
+void QAndroidViewSignalManager::forwardSignal(float signalValue)
+{
+ invokeListener(sender(), senderSignalIndex(), QVariant(signalValue));
+}
+
+void QAndroidViewSignalManager::forwardSignal(QString signalValue)
+{
+ invokeListener(sender(), senderSignalIndex(), QVariant(signalValue));
+}
+
+void QAndroidViewSignalManager::invokeListener(QObject *sender, int senderSignalIndex,
+ QVariant signalValue)
+{
+ using namespace QtJniTypes;
+
+ const QMetaObject *metaObject = sender->metaObject();
+ const QMetaMethod signalMethod = metaObject->method(senderSignalIndex);
+
+ for (auto connectionInfoIter = connectionInfoMap.constFind(signalMethod.methodSignature());
+ connectionInfoIter != connectionInfoMap.constEnd()
+ && connectionInfoIter.key() == signalMethod.methodSignature();
+ ++connectionInfoIter) {
+ const ConnectionInfo connectionInfo = *connectionInfoIter;
+ const QByteArray javaArgType = connectionInfo.javaArgType;
+ QJniObject jSignalMethodName =
+ QJniObject::fromString(QLatin1StringView(signalMethod.name()));
+
+ if (connectionInfo.propertyIndex != -1 && javaArgType != Traits<Void>::className())
+ signalValue = metaObject->property(connectionInfo.propertyIndex).read(sender);
+
+ QJniObject jValue(
+ QAndroidTypeConverter::toJavaObject(signalValue, QJniEnvironment::getJniEnv()));
+
+ if (!jValue.isValid()) {
+ qWarning("Mismatching argument types between QML signal (%s) and the Java function "
+ "(%s). Sending null as argument.",
+ signalMethod.methodSignature().constData(), javaArgType.constData());
+ }
+
+ QNativeInterface::QAndroidApplication::runOnAndroidMainThread(
+ [connectionInfo, jSignalMethodName, jValue]() {
+ connectionInfo.listener.callMethod<void, jstring, jobject>(
+ "onSignalEmitted", jSignalMethodName.object<jstring>(),
+ jValue.object());
+ });
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/platform/android/qandroidviewsignalmanager_p.h b/src/quick/platform/android/qandroidviewsignalmanager_p.h
new file mode 100644
index 0000000000..c6ca6543b3
--- /dev/null
+++ b/src/quick/platform/android/qandroidviewsignalmanager_p.h
@@ -0,0 +1,57 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QANDROIDVIEWSIGNALMANAGER_P_H
+#define QANDROIDVIEWSIGNALMANAGER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qobject.h>
+#include <QtCore/qjnitypes.h>
+
+QT_BEGIN_NAMESPACE
+
+class QAndroidViewSignalManager : public QObject
+{
+ Q_OBJECT
+public:
+ struct ConnectionInfo
+ {
+ int id;
+ QJniObject listener;
+ QByteArray javaArgType;
+ QByteArray signalSignature;
+ int propertyIndex{ -1 };
+ };
+
+ explicit QAndroidViewSignalManager()
+ : QObject(), connectionHandleCounter(0)
+ {
+ }
+ void invokeListener(QObject *sender, int senderSignalIndex, QVariant signalValue);
+
+ int connectionHandleCounter;
+ QMultiMap<QByteArray, ConnectionInfo> connectionInfoMap;
+ QHash<int, QMetaObject::Connection> connections;
+
+public slots:
+ void forwardSignal();
+ void forwardSignal(int);
+ void forwardSignal(double);
+ void forwardSignal(float);
+ void forwardSignal(bool);
+ void forwardSignal(QString);
+};
+
+QT_END_NAMESPACE
+
+#endif // QANDROIDVIEWSIGNALMANAGER_P_H
diff --git a/src/quick/qtquick.tracepoints b/src/quick/qtquick.tracepoints
deleted file mode 100644
index f6518444d2..0000000000
--- a/src/quick/qtquick.tracepoints
+++ /dev/null
@@ -1,48 +0,0 @@
-QSG_renderWindow_entry()
-QSG_renderWindow_exit()
-QSG_renderScene_entry()
-QSG_renderScene_exit()
-QSG_syncAndRender_entry()
-QSG_syncAndRender_exit()
-QSG_polishAndSync_entry()
-QSG_polishAndSync_exit()
-QSG_preprocess_entry()
-QSG_preprocess_exit()
-QSG_update_entry()
-QSG_update_exit()
-
-QSG_polishItems_entry()
-QSG_polishItems_exit()
-QSG_sync_entry()
-QSG_sync_exit()
-QSG_wait_entry()
-QSG_wait_exit()
-QSG_render_entry()
-QSG_render_exit()
-QSG_swap_entry()
-QSG_swap_exit()
-QSG_animations_entry()
-QSG_animations_exit()
-
-QSG_texture_prepare_entry()
-QSG_texture_prepare_exit()
-QSG_texture_bind_entry()
-QSG_texture_bind_exit()
-QSG_texture_delete_entry()
-QSG_texture_delete_exit()
-QSG_texture_upload_entry()
-QSG_texture_upload_exit()
-QSG_texture_mipmap_entry()
-QSG_texture_mipmap_exit()
-
-QSG_binding_entry()
-QSG_binding_exit()
-QSG_prepareMaterial_entry()
-QSG_prepareMaterial_exit()
-
-QSGDistanceFieldGlyphCache_update_entry(int count)
-QSGDistanceFieldGlyphCache_update_exit()
-QSGDistanceFieldGlyphCache_glyphRender_entry()
-QSGDistanceFieldGlyphCache_glyphRender_exit()
-QSGDistanceFieldGlyphCache_glyphStore_entry()
-QSGDistanceFieldGlyphCache_glyphStore_exit()
diff --git a/src/quick/qtquickglobal_p.h b/src/quick/qtquickglobal_p.h
index 7198493cda..89d2e31942 100644
--- a/src/quick/qtquickglobal_p.h
+++ b/src/quick/qtquickglobal_p.h
@@ -22,18 +22,17 @@
//
#include "qtquickglobal.h"
-#include <QtQuick/private/qtquickexports_p.h>
+#include <QtQuick/qtquickexports.h>
QT_BEGIN_NAMESPACE
-void Q_QUICK_PRIVATE_EXPORT qml_register_types_QtQuick();
+void Q_QUICK_EXPORT qml_register_types_QtQuick();
-void Q_QUICK_PRIVATE_EXPORT QQuick_initializeModule();
+void Q_QUICK_EXPORT QQuick_initializeModule();
Q_DECLARE_LOGGING_CATEGORY(lcTouch)
Q_DECLARE_LOGGING_CATEGORY(lcMouse)
Q_DECLARE_LOGGING_CATEGORY(lcFocus)
-Q_DECLARE_LOGGING_CATEGORY(lcDirty)
/*
This is needed for QuickTestUtils. Q_AUTOTEST_EXPORT checks QT_BUILDING_QT
diff --git a/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer.cpp b/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer.cpp
index d33919f344..c9385630d9 100644
--- a/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer.cpp
+++ b/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer.cpp
@@ -178,8 +178,12 @@ QRegion QSGAbstractSoftwareRenderer::optimizeRenderList()
for (auto j = m_renderableNodes.begin(); j != m_renderableNodes.end(); ++j) {
auto node = *j;
- if (!node->isOpaque() && !m_dirtyRegion.isEmpty()) {
- // Only blended nodes need to be updated
+ if ((!node->isOpaque() || node->boundingRectMax() != node->boundingRectMin()) && !m_dirtyRegion.isEmpty()) {
+ // Blended nodes need to be updated
+ // QTBUG-113745: Also nodes with floating point boundary rectangles need to
+ // be updated. The reason is that m_obscuredRegion contains only the rounded
+ // down bounding rectangle (node->boundingRectMin()) and thus not the whole
+ // node. As a result up to 1 pixel would be overpainted when it should not.
node->addDirtyRegion(m_dirtyRegion, true);
}
diff --git a/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer_p.h b/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer_p.h
index 5d27708342..b0426a0b37 100644
--- a/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer_p.h
+++ b/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer_p.h
@@ -26,7 +26,7 @@ class QSGSimpleRectNode;
class QSGSoftwareRenderableNode;
class QSGSoftwareRenderableNodeUpdater;
-class Q_QUICK_PRIVATE_EXPORT QSGAbstractSoftwareRenderer : public QSGRenderer
+class Q_QUICK_EXPORT QSGAbstractSoftwareRenderer : public QSGRenderer
{
public:
QSGAbstractSoftwareRenderer(QSGRenderContext *context);
diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp b/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp
index 4da188bbfe..4e07508ea0 100644
--- a/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp
+++ b/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp
@@ -75,10 +75,10 @@ QSGPainterNode *QSGSoftwareContext::createPainterNode(QQuickPaintedItem *item)
return new QSGSoftwarePainterNode(item);
}
-QSGGlyphNode *QSGSoftwareContext::createGlyphNode(QSGRenderContext *rc, bool preferNativeGlyphNode, int renderTypeQuality)
+QSGGlyphNode *QSGSoftwareContext::createGlyphNode(QSGRenderContext *rc, QSGTextNode::RenderType renderType, int renderTypeQuality)
{
Q_UNUSED(rc);
- Q_UNUSED(preferNativeGlyphNode);
+ Q_UNUSED(renderType);
Q_UNUSED(renderTypeQuality);
return new QSGSoftwareGlyphNode();
}
@@ -107,6 +107,17 @@ void QSGSoftwareRenderContext::initializeIfNeeded()
void QSGSoftwareRenderContext::invalidate()
{
+ qDeleteAll(m_texturesToDelete);
+ m_texturesToDelete.clear();
+
+ qDeleteAll(m_textures);
+ m_textures.clear();
+
+ Q_ASSERT(m_fontEnginesToClean.isEmpty());
+
+ qDeleteAll(m_glyphCaches);
+ m_glyphCaches.clear();
+
m_sg->renderContextInvalidated(this);
emit invalidated();
}
@@ -190,7 +201,7 @@ void *QSGSoftwareContext::getResource(QQuickWindow *window, Resource resource) c
if (resource == PainterResource)
return window->isSceneGraphInitialized() ? static_cast<QSGSoftwareRenderContext *>(cd->context)->m_activePainter : nullptr;
else if (resource == RedirectPaintDevice)
- return cd->redirect.rt.paintDevice;
+ return cd->redirect.rt.sw.paintDevice;
return nullptr;
}
diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext_p.h b/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext_p.h
index 6b95ebb1ad..68f18b490a 100644
--- a/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext_p.h
+++ b/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext_p.h
@@ -55,7 +55,7 @@ public:
QSGInternalRectangleNode *createInternalRectangleNode() override;
QSGInternalImageNode *createInternalImageNode(QSGRenderContext *renderContext) override;
QSGPainterNode *createPainterNode(QQuickPaintedItem *item) override;
- QSGGlyphNode *createGlyphNode(QSGRenderContext *rc, bool preferNativeGlyphNode, int renderTypeQuality) override;
+ QSGGlyphNode *createGlyphNode(QSGRenderContext *rc, QSGTextNode::RenderType renderType, int renderTypeQuality) override;
QSGLayer *createLayer(QSGRenderContext *renderContext) override;
QSurfaceFormat defaultSurfaceFormat() const override;
QSGRendererInterface *rendererInterface(QSGRenderContext *renderContext) override;
diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwareglyphnode.cpp b/src/quick/scenegraph/adaptations/software/qsgsoftwareglyphnode.cpp
index 15e8096d8d..83eef8b54f 100644
--- a/src/quick/scenegraph/adaptations/software/qsgsoftwareglyphnode.cpp
+++ b/src/quick/scenegraph/adaptations/software/qsgsoftwareglyphnode.cpp
@@ -59,6 +59,10 @@ void QSGSoftwareGlyphNode::setGlyphs(const QPointF &position, const QGlyphRun &g
{
m_position = position;
m_glyphRun = glyphs;
+ // Decorations handled by text node
+ m_glyphRun.setOverline(false);
+ m_glyphRun.setStrikeOut(false);
+ m_glyphRun.setUnderline(false);
m_bounding_rect = calculateBoundingRect(position, glyphs);
}
diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalrectanglenode.cpp b/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalrectanglenode.cpp
index 488f622dce..411c189b3d 100644
--- a/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalrectanglenode.cpp
+++ b/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalrectanglenode.cpp
@@ -11,6 +11,10 @@ QT_BEGIN_NAMESPACE
QSGSoftwareInternalRectangleNode::QSGSoftwareInternalRectangleNode()
: m_penWidth(0)
, m_radius(0)
+ , m_topLeftRadius(-1)
+ , m_topRightRadius(-1)
+ , m_bottomLeftRadius(-1)
+ , m_bottomRightRadius(-1)
, m_vertical(true)
, m_cornerPixmapIsDirty(true)
, m_devicePixelRatio(1)
@@ -171,6 +175,42 @@ void QSGSoftwareInternalRectangleNode::setRadius(qreal radius)
}
}
+void QSGSoftwareInternalRectangleNode::setTopLeftRadius(qreal radius)
+{
+ if (m_topLeftRadius != radius) {
+ m_topLeftRadius = radius;
+ m_cornerPixmapIsDirty = true;
+ markDirty(DirtyMaterial);
+ }
+}
+
+void QSGSoftwareInternalRectangleNode::setTopRightRadius(qreal radius)
+{
+ if (m_topRightRadius != radius) {
+ m_topRightRadius = radius;
+ m_cornerPixmapIsDirty = true;
+ markDirty(DirtyMaterial);
+ }
+}
+
+void QSGSoftwareInternalRectangleNode::setBottomLeftRadius(qreal radius)
+{
+ if (m_bottomLeftRadius != radius) {
+ m_bottomLeftRadius = radius;
+ m_cornerPixmapIsDirty = true;
+ markDirty(DirtyMaterial);
+ }
+}
+
+void QSGSoftwareInternalRectangleNode::setBottomRightRadius(qreal radius)
+{
+ if (m_bottomRightRadius != radius) {
+ m_bottomRightRadius = radius;
+ m_cornerPixmapIsDirty = true;
+ markDirty(DirtyMaterial);
+ }
+}
+
void QSGSoftwareInternalRectangleNode::setAligned(bool /*aligned*/)
{
}
@@ -185,9 +225,8 @@ void QSGSoftwareInternalRectangleNode::update()
}
if (!m_stops.isEmpty()) {
- QLinearGradient gradient(QPoint(0,0), QPoint(m_vertical ? 0 : 1, m_vertical ? 1 : 0));
+ QLinearGradient gradient(QPoint(0,0), QPoint(m_vertical ? 0 : m_rect.width(), m_vertical ? m_rect.height() : 0));
gradient.setStops(m_stops);
- gradient.setCoordinateMode(QGradient::ObjectBoundingMode);
m_brush = QBrush(gradient);
} else {
m_brush = QBrush(m_color);
@@ -212,13 +251,21 @@ void QSGSoftwareInternalRectangleNode::paint(QPainter *painter)
//Rotated rectangles lose the benefits of direct rendering, and have poor rendering
//quality when using only blits and fills.
- if (m_radius == 0 && m_penWidth == 0) {
+ if (m_radius == 0
+ && m_penWidth == 0
+ && m_topLeftRadius <= 0
+ && m_topRightRadius <= 0
+ && m_bottomLeftRadius <= 0
+ && m_bottomRightRadius <= 0) {
//Non-Rounded Rects without borders (fall back to drawRect)
//Most common case
painter->setPen(Qt::NoPen);
painter->setBrush(m_brush);
painter->drawRect(m_rect);
- } else {
+ } else if (m_topLeftRadius < 0
+ && m_topRightRadius < 0
+ && m_bottomLeftRadius < 0
+ && m_bottomRightRadius < 0) {
//Rounded Rects and Rects with Borders
//Avoids broken behaviors of QPainter::drawRect/roundedRect
QPixmap pixmap = QPixmap(qRound(m_rect.width() * m_devicePixelRatio), qRound(m_rect.height() * m_devicePixelRatio));
@@ -231,12 +278,34 @@ void QSGSoftwareInternalRectangleNode::paint(QPainter *painter)
painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
painter->drawPixmap(m_rect, pixmap);
painter->setRenderHints(previousRenderHints);
+ } else {
+ // Corners with different radii. Split implementation to avoid
+ // performance regression of the majority of cases
+ QPixmap pixmap = QPixmap(qRound(m_rect.width() * m_devicePixelRatio), qRound(m_rect.height() * m_devicePixelRatio));
+ pixmap.fill(Qt::transparent);
+ pixmap.setDevicePixelRatio(m_devicePixelRatio);
+ QPainter pixmapPainter(&pixmap);
+ // Slow function relying on paths
+ paintRectangleIndividualCorners(&pixmapPainter, QRect(0, 0, m_rect.width(), m_rect.height()));
+
+ QPainter::RenderHints previousRenderHints = painter->renderHints();
+ painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
+ painter->drawPixmap(m_rect, pixmap);
+ painter->setRenderHints(previousRenderHints);
+
}
} else {
//Paint directly
- paintRectangle(painter, m_rect);
+ if (m_topLeftRadius < 0
+ && m_topRightRadius < 0
+ && m_bottomLeftRadius < 0
+ && m_bottomRightRadius < 0) {
+ paintRectangle(painter, m_rect);
+ } else {
+ paintRectangleIndividualCorners(painter, m_rect);
+ }
}
}
@@ -387,6 +456,75 @@ void QSGSoftwareInternalRectangleNode::paintRectangle(QPainter *painter, const Q
painter->setRenderHints(previousRenderHints);
}
+void QSGSoftwareInternalRectangleNode::paintRectangleIndividualCorners(QPainter *painter, const QRect &rect)
+{
+ QPainterPath path;
+
+ const float w = m_penWidth;
+
+ // Radius should never exceeds half of the width or half of the height
+ const float radiusTL = qMin(qMin(rect.width(), rect.height()) * 0.5f, float(m_topLeftRadius < 0. ? m_radius : m_topLeftRadius));
+ const float radiusTR = qMin(qMin(rect.width(), rect.height()) * 0.5f, float(m_topRightRadius < 0. ? m_radius : m_topRightRadius));
+ const float radiusBL = qMin(qMin(rect.width(), rect.height()) * 0.5f, float(m_bottomLeftRadius < 0. ? m_radius : m_bottomLeftRadius));
+ const float radiusBR = qMin(qMin(rect.width(), rect.height()) * 0.5f, float(m_bottomRightRadius < 0 ? m_radius : m_bottomRightRadius));
+
+ const float innerRadiusTL = qMin(qMin(rect.width(), rect.height()) * 0.5f, radiusTL - w);
+ const float innerRadiusTR = qMin(qMin(rect.width(), rect.height()) * 0.5f, radiusTR - w);
+ const float innerRadiusBL = qMin(qMin(rect.width(), rect.height()) * 0.5f, radiusBL - w);
+ const float innerRadiusBR = qMin(qMin(rect.width(), rect.height()) * 0.5f, radiusBR - w);
+
+ QRect rect2 = rect.adjusted(0, 0, 1, 1);
+
+ path.moveTo(rect2.topRight() - QPointF(radiusTR, -w));
+ if (innerRadiusTR > 0.)
+ path.arcTo(QRectF(rect2.topRight() - QPointF(radiusTR + innerRadiusTR, -w), 2. * QSizeF(innerRadiusTR, innerRadiusTR)), 90, -90);
+ else
+ path.lineTo(rect2.topRight() - QPointF(w, -w));
+
+ if (innerRadiusBR > 0.)
+ path.arcTo(QRectF(rect2.bottomRight() - QPointF(radiusBR + innerRadiusBR, radiusBR + innerRadiusBR), 2. * QSizeF(innerRadiusBR, innerRadiusBR)), 0, -90);
+ else
+ path.lineTo(rect2.bottomRight() - QPointF(w, w));
+
+ if (innerRadiusBL > 0.)
+ path.arcTo(QRectF(rect2.bottomLeft() - QPointF(-w, radiusBL + innerRadiusBL), 2. * QSizeF(innerRadiusBL, innerRadiusBL)), -90, -90);
+ else
+ path.lineTo(rect2.bottomLeft() - QPointF(-w, w));
+ if (innerRadiusTL > 0.)
+ path.arcTo(QRectF(rect2.topLeft() + QPointF(w, w), 2. * QSizeF(innerRadiusTL, innerRadiusTL)), -180, -90);
+ else
+ path.lineTo(rect2.topLeft() + QPointF(w, w));
+ path.closeSubpath();
+
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(m_brush);
+ painter->drawPath(path);
+
+ if (w > 0) {
+ path.moveTo(rect2.topRight() - QPointF(radiusTR, 0.));
+ if (radiusTR > 0.)
+ path.arcTo(QRectF(rect2.topRight() - 2. * QPointF(radiusTR, 0.), 2. * QSizeF(radiusTR, radiusTR)), 90, -90);
+ else
+ path.lineTo(rect2.topRight());
+ if (radiusBR > 0.)
+ path.arcTo(QRectF(rect2.bottomRight() - 2. * QPointF(radiusBR, radiusBR), 2. * QSizeF(radiusBR, radiusBR)), 0, -90);
+ else
+ path.lineTo(rect2.bottomRight());
+ if (radiusBL > 0.)
+ path.arcTo(QRectF(rect2.bottomLeft() - 2. * QPointF(0., radiusBL), 2. * QSizeF(radiusBL, radiusBL)), -90, -90);
+ else
+ path.lineTo(rect2.bottomLeft());
+ if (radiusTL > 0.)
+ path.arcTo(QRectF(rect2.topLeft() - 2. * QPointF(0., 0.), 2. * QSizeF(radiusTL, radiusTL)), -180, -90);
+ else
+ path.lineTo(rect2.topLeft());
+ path.closeSubpath();
+
+ painter->setBrush(m_penColor);
+ painter->drawPath(path);
+ }
+}
+
void QSGSoftwareInternalRectangleNode::generateCornerPixmap()
{
//Generate new corner Pixmap
diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalrectanglenode_p.h b/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalrectanglenode_p.h
index bf815f42df..ac58e7b254 100644
--- a/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalrectanglenode_p.h
+++ b/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalrectanglenode_p.h
@@ -35,6 +35,10 @@ public:
void setGradientStops(const QGradientStops &stops) override;
void setGradientVertical(bool vertical) override;
void setRadius(qreal radius) override;
+ void setTopLeftRadius(qreal radius) override;
+ void setTopRightRadius(qreal radius) override;
+ void setBottomLeftRadius(qreal radius) override;
+ void setBottomRightRadius(qreal radius) override;
void setAntialiasing(bool antialiasing) override { Q_UNUSED(antialiasing); }
void setAligned(bool aligned) override;
@@ -46,14 +50,19 @@ public:
QRectF rect() const;
private:
void paintRectangle(QPainter *painter, const QRect &rect);
+ void paintRectangleIndividualCorners(QPainter *painter, const QRect &rect);
void generateCornerPixmap();
QRect m_rect;
QColor m_color;
QColor m_penColor;
- double m_penWidth;
+ qreal m_penWidth;
QGradientStops m_stops;
- double m_radius;
+ qreal m_radius;
+ qreal m_topLeftRadius;
+ qreal m_topRightRadius;
+ qreal m_bottomLeftRadius;
+ qreal m_bottomRightRadius;
QPen m_pen;
QBrush m_brush;
bool m_vertical;
diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwarepainternode.cpp b/src/quick/scenegraph/adaptations/software/qsgsoftwarepainternode.cpp
index 75b106f464..a200b84af8 100644
--- a/src/quick/scenegraph/adaptations/software/qsgsoftwarepainternode.cpp
+++ b/src/quick/scenegraph/adaptations/software/qsgsoftwarepainternode.cpp
@@ -137,7 +137,10 @@ void QSGSoftwarePainterNode::update()
void QSGSoftwarePainterNode::paint(QPainter *painter)
{
+ bool before = painter->testRenderHint(QPainter::SmoothPixmapTransform);
+ painter->setRenderHint(QPainter::SmoothPixmapTransform, m_linear_filtering);
painter->drawPixmap(0, 0, m_size.width(), m_size.height(), m_pixmap);
+ painter->setRenderHint(QPainter::SmoothPixmapTransform, before);
}
void QSGSoftwarePainterNode::paint()
diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderablenode.cpp b/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderablenode.cpp
index e59f894d7f..79fa4a78ad 100644
--- a/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderablenode.cpp
+++ b/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderablenode.cpp
@@ -225,8 +225,8 @@ QRegion QSGSoftwareRenderableNode::renderNode(QPainter *painter, bool forceOpaqu
return QRegion();
} else {
QSGRenderNodePrivate *rd = QSGRenderNodePrivate::get(m_handle.renderNode);
- QMatrix4x4 m = m_transform;
- rd->m_matrix = &m;
+ rd->m_localMatrix = m_transform;
+ rd->m_matrix = &rd->m_localMatrix;
rd->m_opacity = m_opacity;
// all the clip region below is in world coordinates, taking m_transform into account already
diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderablenode_p.h b/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderablenode_p.h
index 5e695bd615..281539b3cb 100644
--- a/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderablenode_p.h
+++ b/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderablenode_p.h
@@ -36,7 +36,7 @@ class QSGSoftwareNinePatchNode;
class QSGSoftwareSpriteNode;
class QSGRenderNode;
-class Q_QUICK_PRIVATE_EXPORT QSGSoftwareRenderableNode
+class Q_QUICK_EXPORT QSGSoftwareRenderableNode
{
public:
enum NodeType {
diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderer_p.h b/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderer_p.h
index 9dff08cf42..e87f79f8f3 100644
--- a/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderer_p.h
+++ b/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderer_p.h
@@ -22,7 +22,7 @@ QT_BEGIN_NAMESPACE
class QPaintDevice;
class QBackingStore;
-class Q_QUICK_PRIVATE_EXPORT QSGSoftwareRenderer : public QSGAbstractSoftwareRenderer
+class Q_QUICK_EXPORT QSGSoftwareRenderer : public QSGAbstractSoftwareRenderer
{
public:
QSGSoftwareRenderer(QSGRenderContext *context);
diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwarethreadedrenderloop.cpp b/src/quick/scenegraph/adaptations/software/qsgsoftwarethreadedrenderloop.cpp
index 54bcdf9b49..39aa89472f 100644
--- a/src/quick/scenegraph/adaptations/software/qsgsoftwarethreadedrenderloop.cpp
+++ b/src/quick/scenegraph/adaptations/software/qsgsoftwarethreadedrenderloop.cpp
@@ -29,23 +29,6 @@
QT_BEGIN_NAMESPACE
-// Passed from the RL to the RT when a window is removed obscured and should be
-// removed from the render loop.
-const QEvent::Type WM_Obscure = QEvent::Type(QEvent::User + 1);
-
-// Passed from the RL to RT when GUI has been locked, waiting for sync.
-const QEvent::Type WM_RequestSync = QEvent::Type(QEvent::User + 2);
-
-// Passed by the RL to the RT to maybe release resource if no windows are
-// rendering.
-const QEvent::Type WM_TryRelease = QEvent::Type(QEvent::User + 4);
-
-// Passed by the RL to the RT when a QQuickWindow::grabWindow() is called.
-const QEvent::Type WM_Grab = QEvent::Type(QEvent::User + 5);
-
-// Passed by the window when there is a render job to run.
-const QEvent::Type WM_PostJob = QEvent::Type(QEvent::User + 6);
-
class QSGSoftwareWindowEvent : public QEvent
{
public:
@@ -57,7 +40,7 @@ class QSGSoftwareTryReleaseEvent : public QSGSoftwareWindowEvent
{
public:
QSGSoftwareTryReleaseEvent(QQuickWindow *win, bool destroy)
- : QSGSoftwareWindowEvent(win, WM_TryRelease), destroying(destroy) { }
+ : QSGSoftwareWindowEvent(win, QEvent::Type(WM_TryRelease)), destroying(destroy) { }
bool destroying;
};
@@ -65,7 +48,7 @@ class QSGSoftwareSyncEvent : public QSGSoftwareWindowEvent
{
public:
QSGSoftwareSyncEvent(QQuickWindow *c, bool inExpose, bool force)
- : QSGSoftwareWindowEvent(c, WM_RequestSync)
+ : QSGSoftwareWindowEvent(c, QEvent::Type(WM_RequestSync))
, size(c->size())
, dpr(c->effectiveDevicePixelRatio())
, syncInExpose(inExpose)
@@ -80,7 +63,7 @@ class QSGSoftwareGrabEvent : public QSGSoftwareWindowEvent
{
public:
QSGSoftwareGrabEvent(QQuickWindow *c, QImage *result)
- : QSGSoftwareWindowEvent(c, WM_Grab), image(result) { }
+ : QSGSoftwareWindowEvent(c, QEvent::Type(WM_Grab)), image(result) { }
QImage *image;
};
@@ -88,7 +71,7 @@ class QSGSoftwareJobEvent : public QSGSoftwareWindowEvent
{
public:
QSGSoftwareJobEvent(QQuickWindow *c, QRunnable *postedJob)
- : QSGSoftwareWindowEvent(c, WM_PostJob), job(postedJob) { }
+ : QSGSoftwareWindowEvent(c, QEvent::Type(WM_PostJob)), job(postedJob) { }
~QSGSoftwareJobEvent() { delete job; }
QRunnable *job;
};
@@ -516,11 +499,11 @@ void QSGSoftwareRenderThread::syncAndRender()
QQuickProfiler::SceneGraphRenderLoopSwap);
}
-template<class T> T *windowFor(const QVector<T> &list, QQuickWindow *window)
+QSGSoftwareThreadedRenderLoop::WindowData *QSGSoftwareThreadedRenderLoop::windowFor(QQuickWindow *window)
{
- for (const T &t : list) {
+ for (const auto &t : std::as_const(m_windows)) {
if (t.window == window)
- return const_cast<T *>(&t);
+ return const_cast<WindowData *>(&t);
}
return nullptr;
}
@@ -552,7 +535,7 @@ void QSGSoftwareThreadedRenderLoop::hide(QQuickWindow *window)
qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "hide" << window;
if (window->isExposed())
- handleObscurity(windowFor(m_windows, window));
+ handleObscurity(windowFor(window));
releaseResources(window);
}
@@ -569,7 +552,7 @@ void QSGSoftwareThreadedRenderLoop::windowDestroyed(QQuickWindow *window)
{
qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "window destroyed" << window;
- WindowData *w = windowFor(m_windows, window);
+ WindowData *w = windowFor(window);
if (!w)
return;
@@ -603,7 +586,7 @@ void QSGSoftwareThreadedRenderLoop::exposureChanged(QQuickWindow *window)
if (window->isExposed()) {
handleExposure(window);
} else {
- WindowData *w = windowFor(m_windows, window);
+ WindowData *w = windowFor(window);
if (w)
handleObscurity(w);
}
@@ -613,13 +596,13 @@ QImage QSGSoftwareThreadedRenderLoop::grab(QQuickWindow *window)
{
qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "grab" << window;
- WindowData *w = windowFor(m_windows, window);
+ WindowData *w = windowFor(window);
// Have to support invisible (but created()'ed) windows as well.
// Unlike with GL, leaving that case for QQuickWindow to handle is not feasible.
const bool tempExpose = !w;
if (tempExpose) {
handleExposure(window);
- w = windowFor(m_windows, window);
+ w = windowFor(window);
Q_ASSERT(w);
}
@@ -650,7 +633,7 @@ QImage QSGSoftwareThreadedRenderLoop::grab(QQuickWindow *window)
void QSGSoftwareThreadedRenderLoop::update(QQuickWindow *window)
{
- WindowData *w = windowFor(m_windows, window);
+ WindowData *w = windowFor(window);
if (!w)
return;
@@ -667,7 +650,7 @@ void QSGSoftwareThreadedRenderLoop::update(QQuickWindow *window)
void QSGSoftwareThreadedRenderLoop::maybeUpdate(QQuickWindow *window)
{
- WindowData *w = windowFor(m_windows, window);
+ WindowData *w = windowFor(window);
if (w)
scheduleUpdate(w);
}
@@ -676,7 +659,7 @@ void QSGSoftwareThreadedRenderLoop::handleUpdateRequest(QQuickWindow *window)
{
qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "handleUpdateRequest" << window;
- WindowData *w = windowFor(m_windows, window);
+ WindowData *w = windowFor(window);
if (w)
polishAndSync(w, false);
}
@@ -700,14 +683,14 @@ void QSGSoftwareThreadedRenderLoop::releaseResources(QQuickWindow *window)
{
qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "releaseResources" << window;
- WindowData *w = windowFor(m_windows, window);
+ WindowData *w = windowFor(window);
if (w)
handleResourceRelease(w, false);
}
void QSGSoftwareThreadedRenderLoop::postJob(QQuickWindow *window, QRunnable *job)
{
- WindowData *w = windowFor(m_windows, window);
+ WindowData *w = windowFor(window);
if (w && w->thread && w->thread->exposedWindow)
w->thread->postEvent(new QSGSoftwareJobEvent(window, job));
else
@@ -791,7 +774,7 @@ void QSGSoftwareThreadedRenderLoop::handleExposure(QQuickWindow *window)
{
qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "handleExposure" << window;
- WindowData *w = windowFor(m_windows, window);
+ WindowData *w = windowFor(window);
if (!w) {
qCDebug(QSG_RASTER_LOG_RENDERLOOP, "adding window to list");
WindowData win;
@@ -851,7 +834,7 @@ void QSGSoftwareThreadedRenderLoop::handleObscurity(QSGSoftwareThreadedRenderLoo
if (w->thread->isRunning()) {
w->thread->mutex.lock();
- w->thread->postEvent(new QSGSoftwareWindowEvent(w->window, WM_Obscure));
+ w->thread->postEvent(new QSGSoftwareWindowEvent(w->window, QEvent::Type(WM_Obscure)));
w->thread->waitCondition.wait(&w->thread->mutex);
w->thread->mutex.unlock();
}
@@ -921,7 +904,7 @@ void QSGSoftwareThreadedRenderLoop::polishAndSync(QSGSoftwareThreadedRenderLoop:
// Flush pending touch events.
QQuickWindowPrivate::get(window)->deliveryAgentPrivate()->flushFrameSynchronousEvents(window);
// The delivery of the event might have caused the window to stop rendering
- w = windowFor(m_windows, window);
+ w = windowFor(window);
if (!w || !w->thread || !w->thread->exposedWindow) {
qCDebug(QSG_RASTER_LOG_RENDERLOOP, "polishAndSync - removed after touch event flushing, abort");
return;
diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwarethreadedrenderloop_p.h b/src/quick/scenegraph/adaptations/software/qsgsoftwarethreadedrenderloop_p.h
index adb3c401db..02adb28ab3 100644
--- a/src/quick/scenegraph/adaptations/software/qsgsoftwarethreadedrenderloop_p.h
+++ b/src/quick/scenegraph/adaptations/software/qsgsoftwarethreadedrenderloop_p.h
@@ -61,6 +61,8 @@ private:
uint forceRenderPass : 1;
};
+ WindowData *windowFor(QQuickWindow *window);
+
void startOrStopAnimationTimer();
void handleExposure(QQuickWindow *window);
void handleObscurity(WindowData *w);
@@ -72,7 +74,7 @@ private:
QAnimationDriver *m_anim;
int animationTimer = 0;
bool lockedForSync = false;
- QVector<WindowData> m_windows;
+ QList<WindowData> m_windows;
friend class QSGSoftwareRenderThread;
};
diff --git a/src/quick/scenegraph/compressedtexture/qsgcompressedtexture.cpp b/src/quick/scenegraph/compressedtexture/qsgcompressedtexture.cpp
index 0e33312a9e..629c6427b8 100644
--- a/src/quick/scenegraph/compressedtexture/qsgcompressedtexture.cpp
+++ b/src/quick/scenegraph/compressedtexture/qsgcompressedtexture.cpp
@@ -6,7 +6,7 @@
#include <QDebug>
#include <QtQuick/private/qquickwindow_p.h>
#include <QtQuick/private/qquickitem_p.h>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
QT_BEGIN_NAMESPACE
diff --git a/src/quick/scenegraph/compressedtexture/qsgcompressedtexture_p.h b/src/quick/scenegraph/compressedtexture/qsgcompressedtexture_p.h
index a9ff902e6e..eef95a3fee 100644
--- a/src/quick/scenegraph/compressedtexture/qsgcompressedtexture_p.h
+++ b/src/quick/scenegraph/compressedtexture/qsgcompressedtexture_p.h
@@ -18,7 +18,7 @@
#include <private/qtexturefiledata_p.h>
#include <private/qsgcontext_p.h>
#include <private/qsgtexture_p.h>
-#include <private/qrhi_p.h>
+#include <rhi/qrhi.h>
#include <QQuickTextureFactory>
#include <QOpenGLFunctions>
@@ -26,7 +26,7 @@ QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(QSG_LOG_TEXTUREIO);
-class Q_QUICK_PRIVATE_EXPORT QSGCompressedTexture : public QSGTexture
+class Q_QUICK_EXPORT QSGCompressedTexture : public QSGTexture
{
Q_OBJECT
public:
@@ -63,7 +63,7 @@ namespace QSGOpenGLAtlasTexture {
class Manager;
}
-class Q_QUICK_PRIVATE_EXPORT QSGCompressedTextureFactory : public QQuickTextureFactory
+class Q_QUICK_EXPORT QSGCompressedTextureFactory : public QQuickTextureFactory
{
public:
QSGCompressedTextureFactory(const QTextureFileData& texData);
diff --git a/src/quick/scenegraph/coreapi/qsgabstractrenderer.cpp b/src/quick/scenegraph/coreapi/qsgabstractrenderer.cpp
index a51521eeaa..ffa5599231 100644
--- a/src/quick/scenegraph/coreapi/qsgabstractrenderer.cpp
+++ b/src/quick/scenegraph/coreapi/qsgabstractrenderer.cpp
@@ -14,19 +14,6 @@ QT_BEGIN_NAMESPACE
*/
/*!
- \enum QSGAbstractRenderer::ClearModeBit
-
- Used with setClearMode() to indicate which buffer should
- be cleared before the scene render.
-
- \value ClearColorBuffer Clear the color buffer using clearColor().
- \value ClearDepthBuffer Clear the depth buffer.
- \value ClearStencilBuffer Clear the stencil buffer.
-
- \sa setClearMode(), setClearColor()
- */
-
-/*!
\enum QSGAbstractRenderer::MatrixTransformFlag
Used with setProjectionMatrixToRect() to indicate the expectations towards
@@ -61,8 +48,9 @@ QT_BEGIN_NAMESPACE
QSGAbstractRendererPrivate::QSGAbstractRendererPrivate()
: m_root_node(nullptr)
, m_clear_color(Qt::transparent)
- , m_clear_mode(QSGAbstractRenderer::ClearColorBuffer | QSGAbstractRenderer::ClearDepthBuffer)
{
+ m_projection_matrix.resize(1);
+ m_projection_matrix_native_ndc.resize(1);
}
/*!
@@ -193,15 +181,7 @@ QRect QSGAbstractRenderer::viewportRect() const
*/
void QSGAbstractRenderer::setProjectionMatrixToRect(const QRectF &rect)
{
- QMatrix4x4 matrix;
- matrix.ortho(rect.x(),
- rect.x() + rect.width(),
- rect.y() + rect.height(),
- rect.y(),
- 1,
- -1);
- setProjectionMatrix(matrix);
- setProjectionMatrixWithNativeNDC(matrix);
+ setProjectionMatrixToRect(rect, {}, false);
}
/*!
@@ -217,46 +197,77 @@ void QSGAbstractRenderer::setProjectionMatrixToRect(const QRectF &rect)
*/
void QSGAbstractRenderer::setProjectionMatrixToRect(const QRectF &rect, MatrixTransformFlags flags)
{
+ setProjectionMatrixToRect(rect, flags, flags.testFlag(MatrixTransformFlipY));
+}
+
+/*!
+ Convenience method that calls setProjectionMatrix() with an
+ orthographic matrix generated from \a rect.
+
+ Set MatrixTransformFlipY in \a flags when the graphics API uses Y down in
+ its normalized device coordinate system (for example, Vulkan).
+
+ Convenience method that calls setProjectionMatrixWithNativeNDC() with an
+ orthographic matrix generated from \a rect.
+
+ Set true to \a nativeNDCFlipY to flip the Y axis relative to
+ projection matrix in its normalized device coordinate system.
+
+ \sa setProjectionMatrix(), projectionMatrix()
+ \sa setProjectionMatrixWithNativeNDC(), projectionMatrixWithNativeNDC()
+
+ \since 6.7
+ */
+void QSGAbstractRenderer::setProjectionMatrixToRect(const QRectF &rect, MatrixTransformFlags flags,
+ bool nativeNDCFlipY)
+{
const bool flipY = flags.testFlag(MatrixTransformFlipY);
+
+ const float left = rect.x();
+ const float right = rect.x() + rect.width();
+ float bottom = rect.y() + rect.height();
+ float top = rect.y();
+
+ if (flipY)
+ std::swap(top, bottom);
+
QMatrix4x4 matrix;
- matrix.ortho(rect.x(),
- rect.x() + rect.width(),
- flipY ? rect.y() : rect.y() + rect.height(),
- flipY ? rect.y() + rect.height() : rect.y(),
- 1,
- -1);
- setProjectionMatrix(matrix);
-
- if (flipY) {
+ matrix.ortho(left, right, bottom, top, 1, -1);
+ setProjectionMatrix(matrix, 0);
+
+ if (nativeNDCFlipY) {
+ std::swap(top, bottom);
+
matrix.setToIdentity();
- matrix.ortho(rect.x(),
- rect.x() + rect.width(),
- rect.y() + rect.height(),
- rect.y(),
- 1,
- -1);
+ matrix.ortho(left, right, bottom, top, 1, -1);
}
- setProjectionMatrixWithNativeNDC(matrix);
+ setProjectionMatrixWithNativeNDC(matrix, 0);
}
/*!
Use \a matrix to project the QSGNode coordinates onto surface pixels.
+ \a index specifies the view index when multiview rendering is in use.
+
\sa projectionMatrix(), setProjectionMatrixToRect()
*/
-void QSGAbstractRenderer::setProjectionMatrix(const QMatrix4x4 &matrix)
+void QSGAbstractRenderer::setProjectionMatrix(const QMatrix4x4 &matrix, int index)
{
Q_D(QSGAbstractRenderer);
- d->m_projection_matrix = matrix;
+ if (d->m_projection_matrix.count() <= index)
+ d->m_projection_matrix.resize(index + 1);
+ d->m_projection_matrix[index] = matrix;
}
/*!
\internal
*/
-void QSGAbstractRenderer::setProjectionMatrixWithNativeNDC(const QMatrix4x4 &matrix)
+void QSGAbstractRenderer::setProjectionMatrixWithNativeNDC(const QMatrix4x4 &matrix, int index)
{
Q_D(QSGAbstractRenderer);
- d->m_projection_matrix_native_ndc = matrix;
+ if (d->m_projection_matrix_native_ndc.count() <= index)
+ d->m_projection_matrix_native_ndc.resize(index + 1);
+ d->m_projection_matrix_native_ndc[index] = matrix;
}
/*!
@@ -264,67 +275,54 @@ void QSGAbstractRenderer::setProjectionMatrixWithNativeNDC(const QMatrix4x4 &mat
\sa setProjectionMatrix(), setProjectionMatrixToRect()
*/
-QMatrix4x4 QSGAbstractRenderer::projectionMatrix() const
+QMatrix4x4 QSGAbstractRenderer::projectionMatrix(int index) const
{
Q_D(const QSGAbstractRenderer);
- return d->m_projection_matrix;
+ return d->m_projection_matrix[index];
}
-/*!
- \internal
- */
-QMatrix4x4 QSGAbstractRenderer::projectionMatrixWithNativeNDC() const
+int QSGAbstractRenderer::projectionMatrixCount() const
{
Q_D(const QSGAbstractRenderer);
- return d->m_projection_matrix_native_ndc;
+ return d->m_projection_matrix.count();
}
-/*!
- Use \a color to clear the framebuffer when clearMode() is
- set to QSGAbstractRenderer::ClearColorBuffer.
-
- \sa clearColor(), setClearMode()
- */
-void QSGAbstractRenderer::setClearColor(const QColor &color)
+int QSGAbstractRenderer::projectionMatrixWithNativeNDCCount() const
{
- Q_D(QSGAbstractRenderer);
- d->m_clear_color = color;
+ Q_D(const QSGAbstractRenderer);
+ return d->m_projection_matrix_native_ndc.count();
}
/*!
- Returns the color that clears the framebuffer at the beginning
- of the rendering.
-
- \sa setClearColor(), clearMode()
+ \internal
*/
-QColor QSGAbstractRenderer::clearColor() const
+QMatrix4x4 QSGAbstractRenderer::projectionMatrixWithNativeNDC(int index) const
{
Q_D(const QSGAbstractRenderer);
- return d->m_clear_color;
+ return d->m_projection_matrix_native_ndc[index];
}
/*!
- Defines which attachment of the framebuffer should be cleared
- before each scene render with the \a mode flag.
+ Sets the \a color to clear the framebuffer.
- \sa clearMode(), setClearColor()
+ \sa clearColor()
*/
-void QSGAbstractRenderer::setClearMode(ClearMode mode)
+void QSGAbstractRenderer::setClearColor(const QColor &color)
{
Q_D(QSGAbstractRenderer);
- d->m_clear_mode = mode;
+ d->m_clear_color = color;
}
/*!
- Flags defining which attachment of the framebuffer will be cleared
- before each scene render.
+ Returns the color that clears the framebuffer at the beginning
+ of the rendering.
- \sa setClearMode(), clearColor()
+ \sa setClearColor()
*/
-QSGAbstractRenderer::ClearMode QSGAbstractRenderer::clearMode() const
+QColor QSGAbstractRenderer::clearColor() const
{
Q_D(const QSGAbstractRenderer);
- return d->m_clear_mode;
+ return d->m_clear_color;
}
/*!
diff --git a/src/quick/scenegraph/coreapi/qsgabstractrenderer_p.h b/src/quick/scenegraph/coreapi/qsgabstractrenderer_p.h
index ec2cb66662..5d7a3b13b6 100644
--- a/src/quick/scenegraph/coreapi/qsgabstractrenderer_p.h
+++ b/src/quick/scenegraph/coreapi/qsgabstractrenderer_p.h
@@ -27,19 +27,10 @@ QT_BEGIN_NAMESPACE
class QSGAbstractRendererPrivate;
-class Q_QUICK_PRIVATE_EXPORT QSGAbstractRenderer : public QObject
+class Q_QUICK_EXPORT QSGAbstractRenderer : public QObject
{
Q_OBJECT
public:
- enum ClearModeBit
- {
- ClearColorBuffer = 0x0001,
- ClearDepthBuffer = 0x0002,
- ClearStencilBuffer = 0x0004
- };
- Q_DECLARE_FLAGS(ClearMode, ClearModeBit)
- Q_FLAG(ClearMode)
-
enum MatrixTransformFlag
{
MatrixTransformFlipY = 0x01
@@ -61,17 +52,18 @@ public:
void setProjectionMatrixToRect(const QRectF &rect);
void setProjectionMatrixToRect(const QRectF &rect, MatrixTransformFlags flags);
- void setProjectionMatrix(const QMatrix4x4 &matrix);
- void setProjectionMatrixWithNativeNDC(const QMatrix4x4 &matrix);
- QMatrix4x4 projectionMatrix() const;
- QMatrix4x4 projectionMatrixWithNativeNDC() const;
+ void setProjectionMatrixToRect(const QRectF &rect, MatrixTransformFlags flags,
+ bool nativeNDCFlipY);
+ void setProjectionMatrix(const QMatrix4x4 &matrix, int index = 0);
+ void setProjectionMatrixWithNativeNDC(const QMatrix4x4 &matrix, int index = 0);
+ QMatrix4x4 projectionMatrix(int index) const;
+ QMatrix4x4 projectionMatrixWithNativeNDC(int index) const;
+ int projectionMatrixCount() const;
+ int projectionMatrixWithNativeNDCCount() const;
void setClearColor(const QColor &color);
QColor clearColor() const;
- void setClearMode(ClearMode mode);
- ClearMode clearMode() const;
-
virtual void renderScene() = 0;
virtual void prepareSceneInline();
@@ -89,8 +81,6 @@ private:
friend class QSGRootNode;
};
-Q_DECLARE_OPERATORS_FOR_FLAGS(QSGAbstractRenderer::ClearMode)
-
QT_END_NAMESPACE
#endif
diff --git a/src/quick/scenegraph/coreapi/qsgabstractrenderer_p_p.h b/src/quick/scenegraph/coreapi/qsgabstractrenderer_p_p.h
index afac489a6a..3bc04247db 100644
--- a/src/quick/scenegraph/coreapi/qsgabstractrenderer_p_p.h
+++ b/src/quick/scenegraph/coreapi/qsgabstractrenderer_p_p.h
@@ -25,7 +25,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QSGAbstractRendererPrivate : public QObjectPrivate
+class Q_QUICK_EXPORT QSGAbstractRendererPrivate : public QObjectPrivate
{
Q_DECLARE_PUBLIC(QSGAbstractRenderer)
public:
@@ -36,13 +36,12 @@ public:
QSGRootNode *m_root_node;
QColor m_clear_color;
- QSGAbstractRenderer::ClearMode m_clear_mode;
QRect m_device_rect;
QRect m_viewport_rect;
- QMatrix4x4 m_projection_matrix;
- QMatrix4x4 m_projection_matrix_native_ndc;
+ QVarLengthArray<QMatrix4x4, 1> m_projection_matrix;
+ QVarLengthArray<QMatrix4x4, 1> m_projection_matrix_native_ndc;
uint m_mirrored : 1;
};
diff --git a/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp b/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp
index cbf459555f..6c084ec441 100644
--- a/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp
+++ b/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp
@@ -22,7 +22,7 @@
QT_BEGIN_NAMESPACE
#ifndef QT_NO_DEBUG
-Q_QUICK_PRIVATE_EXPORT bool qsg_test_and_clear_material_failure();
+Q_QUICK_EXPORT bool qsg_test_and_clear_material_failure();
#endif
int qt_sg_envInt(const char *name, int defaultValue);
@@ -201,13 +201,22 @@ QRhiGraphicsPipeline::Topology qsg_topology(int geomDrawMode)
return topology;
}
+void qsg_setMultiViewFlagsOnMaterial(QSGMaterial *material, int multiViewCount)
+{
+ material->setFlag(QSGMaterial::MultiView2, multiViewCount == 2);
+ material->setFlag(QSGMaterial::MultiView3, multiViewCount == 3);
+ material->setFlag(QSGMaterial::MultiView4, multiViewCount == 4);
+}
+
ShaderManager::Shader *ShaderManager::prepareMaterial(QSGMaterial *material,
const QSGGeometry *geometry,
- QSGRendererInterface::RenderMode renderMode)
+ QSGRendererInterface::RenderMode renderMode,
+ int multiViewCount)
{
- QSGMaterialType *type = material->type();
+ qsg_setMultiViewFlagsOnMaterial(material, multiViewCount);
- ShaderKey key = qMakePair(type, renderMode);
+ QSGMaterialType *type = material->type();
+ ShaderKey key = { type, renderMode, multiViewCount };
Shader *shader = rewrittenShaders.value(key, nullptr);
if (shader)
return shader;
@@ -219,8 +228,8 @@ ShaderManager::Shader *ShaderManager::prepareMaterial(QSGMaterial *material,
shader->inputLayout = calculateVertexInputLayout(s, geometry, true);
QSGMaterialShaderPrivate *sD = QSGMaterialShaderPrivate::get(s);
shader->stages = {
- { QRhiGraphicsShaderStage::Vertex, sD->shader(QShader::VertexStage), QShader::BatchableVertexShader },
- { QRhiGraphicsShaderStage::Fragment, sD->shader(QShader::FragmentStage) }
+ { QRhiShaderStage::Vertex, sD->shader(QShader::VertexStage), QShader::BatchableVertexShader },
+ { QRhiShaderStage::Fragment, sD->shader(QShader::FragmentStage) }
};
shader->lastOpacity = 0;
@@ -231,11 +240,13 @@ ShaderManager::Shader *ShaderManager::prepareMaterial(QSGMaterial *material,
ShaderManager::Shader *ShaderManager::prepareMaterialNoRewrite(QSGMaterial *material,
const QSGGeometry *geometry,
- QSGRendererInterface::RenderMode renderMode)
+ QSGRendererInterface::RenderMode renderMode,
+ int multiViewCount)
{
- QSGMaterialType *type = material->type();
+ qsg_setMultiViewFlagsOnMaterial(material, multiViewCount);
- ShaderKey key = qMakePair(type, renderMode);
+ QSGMaterialType *type = material->type();
+ ShaderKey key = { type, renderMode, multiViewCount };
Shader *shader = stockShaders.value(key, nullptr);
if (shader)
return shader;
@@ -247,8 +258,8 @@ ShaderManager::Shader *ShaderManager::prepareMaterialNoRewrite(QSGMaterial *mate
shader->inputLayout = calculateVertexInputLayout(s, geometry, false);
QSGMaterialShaderPrivate *sD = QSGMaterialShaderPrivate::get(s);
shader->stages = {
- { QRhiGraphicsShaderStage::Vertex, sD->shader(QShader::VertexStage) },
- { QRhiGraphicsShaderStage::Fragment, sD->shader(QShader::FragmentStage) }
+ { QRhiShaderStage::Vertex, sD->shader(QShader::VertexStage) },
+ { QRhiShaderStage::Fragment, sD->shader(QShader::FragmentStage) }
};
shader->lastOpacity = 0;
@@ -696,7 +707,7 @@ BatchCompatibility Batch::isMaterialCompatible(Element *e) const
QSGMaterial *m = e->node->activeMaterial();
QSGMaterial *nm = n->node->activeMaterial();
- return (nm->type() == m->type() && nm->compare(m) == 0)
+ return (nm->type() == m->type() && nm->viewCount() == m->viewCount() && nm->compare(m) == 0)
? BatchIsCompatible
: BatchBreaksOnCompare;
}
@@ -839,6 +850,8 @@ Renderer::Renderer(QSGDefaultRenderContext *ctx, QSGRendererInterface::RenderMod
, m_elementsToDelete(64)
, m_tmpAlphaElements(16)
, m_tmpOpaqueElements(16)
+ , m_vboPool(16)
+ , m_iboPool(16)
, m_rebuild(FullRebuild)
, m_zRange(0)
#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
@@ -915,6 +928,10 @@ Renderer::~Renderer()
qsg_wipeBatch(m_alphaBatches.at(i));
for (int i = 0; i < m_batchPool.size(); ++i)
qsg_wipeBatch(m_batchPool.at(i));
+ for (int i = 0; i < m_vboPool.size(); ++i)
+ delete m_vboPool.at(i);
+ for (int i = 0; i < m_iboPool.size(); ++i)
+ delete m_iboPool.at(i);
}
for (Node *n : std::as_const(m_nodes)) {
@@ -960,12 +977,28 @@ void Renderer::releaseCachedResources()
m_rhi->releaseCachedResources();
- m_vertexUploadPool.resize(0);
- m_indexUploadPool.resize(0);
+ m_vertexUploadPool.shrink(0);
+ m_vertexUploadPool.reset();
+ m_indexUploadPool.shrink(0);
+ m_indexUploadPool.reset();
+
+ for (int i = 0; i < m_vboPool.size(); ++i)
+ delete m_vboPool.at(i);
+ m_vboPool.reset();
+
+ for (int i = 0; i < m_iboPool.size(); ++i)
+ delete m_iboPool.at(i);
+ m_iboPool.reset();
}
void Renderer::invalidateAndRecycleBatch(Batch *b)
{
+ if (b->vbo.buf != nullptr)
+ m_vboPool.add(b->vbo.buf);
+ if (b->ibo.buf != nullptr)
+ m_iboPool.add(b->ibo.buf);
+ b->vbo.buf = nullptr;
+ b->ibo.buf = nullptr;
b->invalidate();
for (int i=0; i<m_batchPool.size(); ++i)
if (b == m_batchPool.at(i))
@@ -994,8 +1027,9 @@ void Renderer::unmap(Buffer *buffer, bool isIndexBuf)
{
// Batches are pooled and reused which means the QRhiBuffer will be
// still valid in a recycled Batch. We only hit the newBuffer() path
- // for brand new Batches.
- if (!buffer->buf) {
+ // when there are no buffers to recycle.
+ QDataBuffer<QRhiBuffer *> *bufferPool = isIndexBuf ? &m_iboPool : &m_vboPool;
+ if (!buffer->buf && bufferPool->isEmpty()) {
buffer->buf = m_rhi->newBuffer(QRhiBuffer::Immutable,
isIndexBuf ? QRhiBuffer::IndexBuffer : QRhiBuffer::VertexBuffer,
buffer->size);
@@ -1005,6 +1039,28 @@ void Renderer::unmap(Buffer *buffer, bool isIndexBuf)
buffer->buf = nullptr;
}
} else {
+ if (!buffer->buf) {
+ const quint32 expectedSize = buffer->size;
+ qsizetype foundBufferIndex = 0;
+ for (qsizetype i = 0; i < bufferPool->size(); ++i) {
+ QRhiBuffer *testBuffer = bufferPool->at(i);
+ if (!buffer->buf
+ || (testBuffer->size() >= expectedSize && testBuffer->size() < buffer->buf->size())
+ || (testBuffer->size() < expectedSize && testBuffer->size() > buffer->buf->size())) {
+ foundBufferIndex = i;
+ buffer->buf = testBuffer;
+ if (buffer->buf->size() == expectedSize)
+ break;
+ }
+ }
+
+ if (foundBufferIndex < bufferPool->size() - 1) {
+ qSwap(bufferPool->data()[foundBufferIndex],
+ bufferPool->data()[bufferPool->size() - 1]);
+ }
+ bufferPool->pop_back();
+ }
+
bool needsRebuild = false;
if (buffer->buf->size() < buffer->size) {
buffer->buf->setSize(buffer->size);
@@ -1220,8 +1276,13 @@ void Renderer::nodeWasRemoved(Node *node)
if (e) {
e->removed = true;
m_elementsToDelete.add(e);
- if (m_renderNodeElements.isEmpty())
+ if (m_renderNodeElements.isEmpty()) {
m_forceNoDepthBuffer = false;
+ // Must have a full rebuild given useDepthBuffer() now returns
+ // a different value than before, meaning there can once again
+ // be an opaque pass.
+ m_rebuild |= FullRebuild;
+ }
if (e->batch != nullptr)
e->batch->needsPurge = true;
@@ -1680,6 +1741,7 @@ void Renderer::prepareOpaqueBatches()
&& gni->geometry()->attributes() == gnj->geometry()->attributes()
&& gni->inheritedOpacity() == gnj->inheritedOpacity()
&& gni->activeMaterial()->type() == gnj->activeMaterial()->type()
+ && gni->activeMaterial()->viewCount() == gnj->activeMaterial()->viewCount()
&& gni->activeMaterial()->compare(gnj->activeMaterial()) == 0) {
ej->batch = batch;
next->nextInBatch = ej;
@@ -1791,6 +1853,7 @@ void Renderer::prepareAlphaBatches()
&& gni->geometry()->attributes() == gnj->geometry()->attributes()
&& gni->inheritedOpacity() == gnj->inheritedOpacity()
&& gni->activeMaterial()->type() == gnj->activeMaterial()->type()
+ && gni->activeMaterial()->viewCount() == gnj->activeMaterial()->viewCount()
&& gni->activeMaterial()->compare(gnj->activeMaterial()) == 0) {
if (!overlapBounds.intersects(ej->bounds) || !checkOverlap(i+1, j - 1, ej->bounds)) {
ej->batch = batch;
@@ -1994,7 +2057,7 @@ void Renderer::uploadBatch(Batch *b)
bool canMerge = (g->drawingMode() == QSGGeometry::DrawTriangles || g->drawingMode() == QSGGeometry::DrawTriangleStrip ||
g->drawingMode() == QSGGeometry::DrawLines || g->drawingMode() == QSGGeometry::DrawPoints)
&& b->positionAttribute >= 0
- && g->indexType() == QSGGeometry::UnsignedShortType
+ && (g->indexType() == QSGGeometry::UnsignedShortType && g->indexCount() > 0)
&& (flags & (QSGMaterial::NoBatching | QSGMaterial_FullMatrix)) == 0
&& ((flags & QSGMaterial::RequiresFullMatrixExceptTranslate) == 0 || b->isTranslateOnlyToRoot())
&& b->isSafeToBatch();
@@ -2243,8 +2306,10 @@ QRhiGraphicsPipeline *Renderer::buildStencilPipeline(const Batch *batch, bool fi
ps->setTopology(m_stencilClipCommon.topology);
- ps->setShaderStages({ QRhiGraphicsShaderStage(QRhiGraphicsShaderStage::Vertex, m_stencilClipCommon.vs),
- QRhiGraphicsShaderStage(QRhiGraphicsShaderStage::Fragment, m_stencilClipCommon.fs) });
+ ps->setMultiViewCount(renderTarget().multiViewCount);
+
+ ps->setShaderStages({ QRhiShaderStage(QRhiShaderStage::Vertex, m_stencilClipCommon.vs),
+ QRhiShaderStage(QRhiShaderStage::Fragment, m_stencilClipCommon.fs) });
ps->setVertexInputLayout(m_stencilClipCommon.inputLayout);
ps->setShaderResourceBindings(batch->stencilClipState.srb); // use something, it just needs to be layout-compatible
ps->setRenderPassDescriptor(renderTarget().rpDesc);
@@ -2291,7 +2356,7 @@ void Renderer::updateClipState(const QSGClipNode *clipList, Batch *batch)
const quint32 StencilClipUbufSize = 64;
while (clip) {
- QMatrix4x4 m = m_current_projection_matrix_native_ndc;
+ QMatrix4x4 m = m_current_projection_matrix_native_ndc[0]; // never hit for 3D and so multiview
if (clip->matrix())
m *= *clip->matrix();
@@ -2462,7 +2527,7 @@ void Renderer::updateClipState(const QSGClipNode *clipList, Batch *batch)
drawCall.ubufOffset = aligned(uOffset, m_ubufAlignment);
uOffset = drawCall.ubufOffset + StencilClipUbufSize;
- QMatrix4x4 matrixYUpNDC = m_current_projection_matrix;
+ QMatrix4x4 matrixYUpNDC = m_current_projection_matrix[0];
if (clip->matrix())
matrixYUpNDC *= *clip->matrix();
@@ -2636,6 +2701,7 @@ bool Renderer::ensurePipelineState(Element *e, const ShaderManager::Shader *sms,
ps->setTopology(qsg_topology(m_gstate.drawMode));
ps->setCullMode(m_gstate.cullMode);
ps->setPolygonMode(m_gstate.polygonMode);
+ ps->setMultiViewCount(m_gstate.multiViewCount);
QRhiGraphicsPipeline::TargetBlend blend;
blend.colorWrite = m_gstate.colorWrite;
@@ -2644,6 +2710,8 @@ bool Renderer::ensurePipelineState(Element *e, const ShaderManager::Shader *sms,
blend.dstColor = m_gstate.dstColor;
blend.srcAlpha = m_gstate.srcAlpha;
blend.dstAlpha = m_gstate.dstAlpha;
+ blend.opColor = m_gstate.opColor;
+ blend.opAlpha = m_gstate.opAlpha;
ps->setTargetBlends({ blend });
ps->setDepthTest(m_gstate.depthTest);
@@ -2775,6 +2843,7 @@ static void rendererToMaterialGraphicsState(QSGMaterialShader::GraphicsPipelineS
// the enum values should match, sanity check it
Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::OneMinusSrc1Alpha) == int(QRhiGraphicsPipeline::OneMinusSrc1Alpha));
+ Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::BlendOpMax) == int(QRhiGraphicsPipeline::Max));
Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::A) == int(QRhiGraphicsPipeline::A));
Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::CullBack) == int(QRhiGraphicsPipeline::Back));
Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::Line) == int(QRhiGraphicsPipeline::Line));
@@ -2792,6 +2861,9 @@ static void rendererToMaterialGraphicsState(QSGMaterialShader::GraphicsPipelineS
dst->srcAlpha = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->srcAlpha);
dst->dstAlpha = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->dstAlpha);
+ dst->opColor = QSGMaterialShader::GraphicsPipelineState::BlendOp(src->opColor);
+ dst->opAlpha = QSGMaterialShader::GraphicsPipelineState::BlendOp(src->opAlpha);
+
dst->colorWrite = QSGMaterialShader::GraphicsPipelineState::ColorMask(int(src->colorWrite));
dst->cullMode = QSGMaterialShader::GraphicsPipelineState::CullMode(src->cullMode);
@@ -2811,6 +2883,8 @@ static void materialToRendererGraphicsState(GraphicsState *dst,
dst->srcAlpha = dst->srcColor;
dst->dstAlpha = dst->dstColor;
}
+ dst->opColor = QRhiGraphicsPipeline::BlendOp(src->opColor);
+ dst->opAlpha = QRhiGraphicsPipeline::BlendOp(src->opAlpha);
dst->colorWrite = QRhiGraphicsPipeline::ColorMask(int(src->colorWrite));
dst->cullMode = QRhiGraphicsPipeline::CullMode(src->cullMode);
dst->polygonMode = QRhiGraphicsPipeline::PolygonMode(src->polygonMode);
@@ -3079,16 +3153,24 @@ bool Renderer::prepareRenderMergedBatch(Batch *batch, PreparedRenderBatch *rende
else
m_current_model_view_matrix.setToIdentity();
m_current_determinant = m_current_model_view_matrix.determinant();
- m_current_projection_matrix = projectionMatrix();
- m_current_projection_matrix_native_ndc = projectionMatrixWithNativeNDC();
+
+ const int viewCount = projectionMatrixCount();
+ m_current_projection_matrix.resize(viewCount);
+ for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
+ m_current_projection_matrix[viewIndex] = projectionMatrix(viewIndex);
+
+ m_current_projection_matrix_native_ndc.resize(projectionMatrixWithNativeNDCCount());
+ for (int viewIndex = 0; viewIndex < projectionMatrixWithNativeNDCCount(); ++viewIndex)
+ m_current_projection_matrix_native_ndc[viewIndex] = projectionMatrixWithNativeNDC(viewIndex);
QSGMaterial *material = gn->activeMaterial();
if (m_renderMode != QSGRendererInterface::RenderMode3D)
updateClipState(gn->clipList(), batch);
const QSGGeometry *g = gn->geometry();
- ShaderManager::Shader *sms = useDepthBuffer() ? m_shaderManager->prepareMaterial(material, g, m_renderMode)
- : m_shaderManager->prepareMaterialNoRewrite(material, g, m_renderMode);
+ const int multiViewCount = renderTarget().multiViewCount;
+ ShaderManager::Shader *sms = useDepthBuffer() ? m_shaderManager->prepareMaterial(material, g, m_renderMode, multiViewCount)
+ : m_shaderManager->prepareMaterialNoRewrite(material, g, m_renderMode, multiViewCount);
if (!sms)
return false;
@@ -3252,8 +3334,14 @@ bool Renderer::prepareRenderUnmergedBatch(Batch *batch, PreparedRenderBatch *ren
batch->uploadedThisFrame = false;
}
- m_current_projection_matrix = projectionMatrix();
- m_current_projection_matrix_native_ndc = projectionMatrixWithNativeNDC();
+ const int viewCount = projectionMatrixCount();
+ m_current_projection_matrix.resize(viewCount);
+ for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
+ m_current_projection_matrix[viewIndex] = projectionMatrix(viewIndex);
+
+ m_current_projection_matrix_native_ndc.resize(projectionMatrixWithNativeNDCCount());
+ for (int viewIndex = 0; viewIndex < projectionMatrixWithNativeNDCCount(); ++viewIndex)
+ m_current_projection_matrix_native_ndc[viewIndex] = projectionMatrixWithNativeNDC(viewIndex);
QSGGeometryNode *gn = e->node;
if (m_renderMode != QSGRendererInterface::RenderMode3D)
@@ -3266,7 +3354,7 @@ bool Renderer::prepareRenderUnmergedBatch(Batch *batch, PreparedRenderBatch *ren
// unmerged batch since the material (and so the shaders) is the same.
QSGGeometry *g = gn->geometry();
QSGMaterial *material = gn->activeMaterial();
- ShaderManager::Shader *sms = m_shaderManager->prepareMaterialNoRewrite(material, g);
+ ShaderManager::Shader *sms = m_shaderManager->prepareMaterialNoRewrite(material, g, m_renderMode, renderTarget().multiViewCount);
if (!sms)
return false;
@@ -3326,11 +3414,19 @@ bool Renderer::prepareRenderUnmergedBatch(Batch *batch, PreparedRenderBatch *ren
m_current_model_view_matrix = rootMatrix * *gn->matrix();
m_current_determinant = m_current_model_view_matrix.determinant();
- m_current_projection_matrix = projectionMatrix();
- m_current_projection_matrix_native_ndc = projectionMatrixWithNativeNDC();
+ const int viewCount = projectionMatrixCount();
+ m_current_projection_matrix.resize(viewCount);
+ for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
+ m_current_projection_matrix[viewIndex] = projectionMatrix(viewIndex);
+
+ m_current_projection_matrix_native_ndc.resize(projectionMatrixWithNativeNDCCount());
+ for (int viewIndex = 0; viewIndex < projectionMatrixWithNativeNDCCount(); ++viewIndex)
+ m_current_projection_matrix_native_ndc[viewIndex] = projectionMatrixWithNativeNDC(viewIndex);
+
if (useDepthBuffer()) {
- m_current_projection_matrix(2, 2) = m_zRange;
- m_current_projection_matrix(2, 3) = calculateElementZOrder(e, m_zRange);
+ // this cannot be multiview
+ m_current_projection_matrix[0](2, 2) = m_zRange;
+ m_current_projection_matrix[0](2, 3) = calculateElementZOrder(e, m_zRange);
}
QSGMaterialShader::RenderState renderState = state(QSGMaterialShader::RenderState::DirtyStates(int(dirty)));
@@ -3664,14 +3760,14 @@ void Renderer::prepareRenderPass(RenderPassContext *ctx)
if (Q_UNLIKELY(debug_render())) ctx->timeSorting = ctx->timer.restart();
- quint32 largestVBO = 0;
- quint32 largestIBO = 0;
+ // Set size to 0, nothing is deallocated, they will "grow" again
+ // as part of uploadBatch.
+ m_vertexUploadPool.reset();
+ m_indexUploadPool.reset();
if (Q_UNLIKELY(debug_upload())) qDebug("Uploading Opaque Batches:");
for (int i=0; i<m_opaqueBatches.size(); ++i) {
Batch *b = m_opaqueBatches.at(i);
- largestVBO = qMax(b->vbo.size, largestVBO);
- largestIBO = qMax(b->ibo.size, largestIBO);
uploadBatch(b);
}
if (Q_UNLIKELY(debug_render())) ctx->timeUploadOpaque = ctx->timer.restart();
@@ -3680,14 +3776,9 @@ void Renderer::prepareRenderPass(RenderPassContext *ctx)
for (int i=0; i<m_alphaBatches.size(); ++i) {
Batch *b = m_alphaBatches.at(i);
uploadBatch(b);
- largestVBO = qMax(b->vbo.size, largestVBO);
- largestIBO = qMax(b->ibo.size, largestIBO);
}
if (Q_UNLIKELY(debug_render())) ctx->timeUploadAlpha = ctx->timer.restart();
- m_vertexUploadPool.resize(largestVBO);
- m_indexUploadPool.resize(largestIBO);
-
if (Q_UNLIKELY(debug_render())) {
qDebug().nospace() << "Rendering:" << Qt::endl
<< " -> Opaque: " << qsg_countNodesInBatches(m_opaqueBatches) << " nodes in " << m_opaqueBatches.size() << " batches..." << Qt::endl
@@ -3728,6 +3819,7 @@ void Renderer::prepareRenderPass(RenderPassContext *ctx)
m_gstate.stencilTest = false;
m_gstate.sampleCount = renderTarget().rt->sampleCount();
+ m_gstate.multiViewCount = renderTarget().multiViewCount;
ctx->opaqueRenderBatches.clear();
if (Q_LIKELY(renderOpaque)) {
@@ -3825,6 +3917,8 @@ void Renderer::recordRenderPass(RenderPassContext *ctx)
cb->debugMarkBegin(QByteArrayLiteral("Qt Quick scene render"));
for (int i = 0, ie = ctx->opaqueRenderBatches.size(); i != ie; ++i) {
+ if (i == 0)
+ cb->debugMarkMsg(QByteArrayLiteral("Qt Quick opaque batches"));
PreparedRenderBatch *renderBatch = &ctx->opaqueRenderBatches[i];
if (renderBatch->batch->merged)
renderMergedBatch(renderBatch);
@@ -3833,6 +3927,12 @@ void Renderer::recordRenderPass(RenderPassContext *ctx)
}
for (int i = 0, ie = ctx->alphaRenderBatches.size(); i != ie; ++i) {
+ if (i == 0) {
+ if (m_renderMode == QSGRendererInterface::RenderMode3D)
+ cb->debugMarkMsg(QByteArrayLiteral("Qt Quick 2D-in-3D batches"));
+ else
+ cb->debugMarkMsg(QByteArrayLiteral("Qt Quick alpha batches"));
+ }
PreparedRenderBatch *renderBatch = &ctx->alphaRenderBatches[i];
if (renderBatch->batch->merged)
renderMergedBatch(renderBatch);
@@ -3843,8 +3943,15 @@ void Renderer::recordRenderPass(RenderPassContext *ctx)
}
if (m_renderMode == QSGRendererInterface::RenderMode3D) {
- // depth post-pass
+ // Depth post-pass to fill up the depth buffer in a way that it
+ // corresponds to what got rendered to the color buffer in the previous
+ // (alpha) pass. The previous pass cannot enable depth write due to Z
+ // fighting. Rather, do it separately in a dedicated color-write-off,
+ // depth-write-on pass. This enables the 3D content drawn afterwards to
+ // depth test against the 2D items' rendering.
for (int i = 0, ie = ctx->alphaRenderBatches.size(); i != ie; ++i) {
+ if (i == 0)
+ cb->debugMarkMsg(QByteArrayLiteral("Qt Quick 2D-in-3D depth post-pass"));
PreparedRenderBatch *renderBatch = &ctx->alphaRenderBatches[i];
if (renderBatch->batch->merged)
renderMergedBatch(renderBatch, true);
@@ -3933,7 +4040,8 @@ bool Renderer::prepareRhiRenderNode(Batch *batch, PreparedRenderBatch *renderBat
}
xform = xform->parent();
}
- rd->m_matrix = &matrix;
+ rd->m_localMatrix = matrix;
+ rd->m_matrix = &rd->m_localMatrix;
QSGNode *opacity = e->renderNode->parent();
rd->m_opacity = 1.0;
@@ -3947,10 +4055,15 @@ bool Renderer::prepareRhiRenderNode(Batch *batch, PreparedRenderBatch *renderBat
rd->m_rt = renderTarget();
- rd->m_projectionMatrix = projectionMatrix();
+ const int viewCount = projectionMatrixCount();
+ rd->m_projectionMatrix.resize(viewCount);
+ for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
+ rd->m_projectionMatrix[viewIndex] = projectionMatrix(viewIndex);
+
if (useDepthBuffer()) {
- rd->m_projectionMatrix(2, 2) = m_zRange;
- rd->m_projectionMatrix(2, 3) = calculateElementZOrder(e, m_zRange);
+ // this cannot be multiview
+ rd->m_projectionMatrix[0](2, 2) = m_zRange;
+ rd->m_projectionMatrix[0](2, 3) = calculateElementZOrder(e, m_zRange);
}
e->renderNode->prepare();
@@ -3970,7 +4083,9 @@ void Renderer::renderRhiRenderNode(const Batch *batch)
QSGRenderNodePrivate *rd = QSGRenderNodePrivate::get(e->renderNode);
RenderNodeState state;
- state.m_projectionMatrix = &rd->m_projectionMatrix;
+ // Expose only the first matrix through the state object, the rest are
+ // queriable through the QSGRenderNode getters anyway.
+ state.m_projectionMatrix = &rd->m_projectionMatrix[0];
const std::array<int, 4> scissor = batch->clipState.scissor.scissor();
state.m_scissorRect = QRect(scissor[0], scissor[1], scissor[2], scissor[3]);
state.m_stencilValue = batch->clipState.stencilRef;
@@ -4034,6 +4149,8 @@ bool operator==(const GraphicsState &a, const GraphicsState &b) noexcept
&& a.dstColor == b.dstColor
&& a.srcAlpha == b.srcAlpha
&& a.dstAlpha == b.dstAlpha
+ && a.opColor == b.opColor
+ && a.opAlpha == b.opAlpha
&& a.colorWrite == b.colorWrite
&& a.cullMode == b.cullMode
&& a.usesScissor == b.usesScissor
@@ -4041,7 +4158,8 @@ bool operator==(const GraphicsState &a, const GraphicsState &b) noexcept
&& a.sampleCount == b.sampleCount
&& a.drawMode == b.drawMode
&& a.lineWidth == b.lineWidth
- && a.polygonMode == b.polygonMode;
+ && a.polygonMode == b.polygonMode
+ && a.multiViewCount == b.multiViewCount;
}
bool operator!=(const GraphicsState &a, const GraphicsState &b) noexcept
@@ -4061,7 +4179,8 @@ size_t qHash(const GraphicsState &s, size_t seed) noexcept
+ s.cullMode
+ s.usesScissor
+ s.stencilTest
- + s.sampleCount;
+ + s.sampleCount
+ + s.multiViewCount;
}
bool operator==(const GraphicsPipelineStateKey &a, const GraphicsPipelineStateKey &b) noexcept
@@ -4085,6 +4204,23 @@ size_t qHash(const GraphicsPipelineStateKey &k, size_t seed) noexcept
^ k.extra.srbLayoutDescriptionHash;
}
+bool operator==(const ShaderKey &a, const ShaderKey &b) noexcept
+{
+ return a.type == b.type
+ && a.renderMode == b.renderMode
+ && a.multiViewCount == b.multiViewCount;
+}
+
+bool operator!=(const ShaderKey &a, const ShaderKey &b) noexcept
+{
+ return !(a == b);
+}
+
+size_t qHash(const ShaderKey &k, size_t seed) noexcept
+{
+ return qHash(k.type, seed) ^ int(k.renderMode) ^ k.multiViewCount;
+}
+
Visualizer::Visualizer(Renderer *renderer)
: m_renderer(renderer),
m_visualizeMode(VisualizeNothing)
diff --git a/src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h b/src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h
index 9a024804a7..11cec6b99b 100644
--- a/src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h
+++ b/src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h
@@ -27,7 +27,7 @@
#include <QtCore/QBitArray>
#include <QtCore/QStack>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
QT_BEGIN_NAMESPACE
@@ -599,6 +599,8 @@ struct GraphicsState
QRhiGraphicsPipeline::BlendFactor dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha;
QRhiGraphicsPipeline::BlendFactor srcAlpha = QRhiGraphicsPipeline::One;
QRhiGraphicsPipeline::BlendFactor dstAlpha = QRhiGraphicsPipeline::OneMinusSrcAlpha;
+ QRhiGraphicsPipeline::BlendOp opColor = QRhiGraphicsPipeline::Add;
+ QRhiGraphicsPipeline::BlendOp opAlpha = QRhiGraphicsPipeline::Add;
QRhiGraphicsPipeline::ColorMask colorWrite = QRhiGraphicsPipeline::ColorMask(0xF);
QRhiGraphicsPipeline::CullMode cullMode = QRhiGraphicsPipeline::None;
bool usesScissor = false;
@@ -607,6 +609,7 @@ struct GraphicsState
QSGGeometry::DrawingMode drawMode = QSGGeometry::DrawTriangles;
float lineWidth = 1.0f;
QRhiGraphicsPipeline::PolygonMode polygonMode = QRhiGraphicsPipeline::Fill;
+ int multiViewCount = 0;
};
bool operator==(const GraphicsState &a, const GraphicsState &b) noexcept;
@@ -640,6 +643,17 @@ bool operator==(const GraphicsPipelineStateKey &a, const GraphicsPipelineStateKe
bool operator!=(const GraphicsPipelineStateKey &a, const GraphicsPipelineStateKey &b) noexcept;
size_t qHash(const GraphicsPipelineStateKey &k, size_t seed = 0) noexcept;
+struct ShaderKey
+{
+ QSGMaterialType *type;
+ QSGRendererInterface::RenderMode renderMode;
+ int multiViewCount;
+};
+
+bool operator==(const ShaderKey &a, const ShaderKey &b) noexcept;
+bool operator!=(const ShaderKey &a, const ShaderKey &b) noexcept;
+size_t qHash(const ShaderKey &k, size_t seed = 0) noexcept;
+
struct ShaderManagerShader
{
~ShaderManagerShader() {
@@ -647,7 +661,7 @@ struct ShaderManagerShader
}
QSGMaterialShader *materialShader = nullptr;
QRhiVertexInputLayout inputLayout;
- QVarLengthArray<QRhiGraphicsShaderStage, 2> stages;
+ QVarLengthArray<QRhiShaderStage, 2> stages;
float lastOpacity;
};
@@ -674,11 +688,16 @@ public Q_SLOTS:
void invalidated();
public:
- Shader *prepareMaterial(QSGMaterial *material, const QSGGeometry *geometry = nullptr, QSGRendererInterface::RenderMode renderMode = QSGRendererInterface::RenderMode2D);
- Shader *prepareMaterialNoRewrite(QSGMaterial *material, const QSGGeometry *geometry = nullptr, QSGRendererInterface::RenderMode renderMode = QSGRendererInterface::RenderMode2D);
+ Shader *prepareMaterial(QSGMaterial *material,
+ const QSGGeometry *geometry = nullptr,
+ QSGRendererInterface::RenderMode renderMode = QSGRendererInterface::RenderMode2D,
+ int multiViewCount = 0);
+ Shader *prepareMaterialNoRewrite(QSGMaterial *material,
+ const QSGGeometry *geometry = nullptr,
+ QSGRendererInterface::RenderMode renderMode = QSGRendererInterface::RenderMode2D,
+ int multiViewCount = 0);
private:
- typedef QPair<QSGMaterialType *, QSGRendererInterface::RenderMode> ShaderKey;
QHash<ShaderKey, Shader *> rewrittenShaders;
QHash<ShaderKey, Shader *> stockShaders;
@@ -723,7 +742,7 @@ protected:
QHash<Node *, uint> m_visualizeChangeSet;
};
-class Q_QUICK_PRIVATE_EXPORT Renderer : public QSGRenderer
+class Q_QUICK_EXPORT Renderer : public QSGRenderer
{
public:
Renderer(QSGDefaultRenderContext *ctx, QSGRendererInterface::RenderMode renderMode = QSGRendererInterface::RenderMode2D);
@@ -858,6 +877,9 @@ private:
QDataBuffer<Element *> m_tmpAlphaElements;
QDataBuffer<Element *> m_tmpOpaqueElements;
+ QDataBuffer<QRhiBuffer *> m_vboPool;
+ QDataBuffer<QRhiBuffer *> m_iboPool;
+
uint m_rebuild;
qreal m_zRange;
#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
diff --git a/src/quick/scenegraph/coreapi/qsggeometry.cpp b/src/quick/scenegraph/coreapi/qsggeometry.cpp
index 32ba34587a..9541107b78 100644
--- a/src/quick/scenegraph/coreapi/qsggeometry.cpp
+++ b/src/quick/scenegraph/coreapi/qsggeometry.cpp
@@ -496,13 +496,18 @@ const void *QSGGeometry::indexData() const
Specifies the drawing mode, also called primitive topology.
+ \note Starting with Qt 6 the scene graph only exposes topologies that are
+ supported across all the supported 3D graphics APIs. As a result, the
+ values \c DrawLineLoop and \c DrawTriangleFan are no longer supported at
+ run time in Qt 6, even though the enum values themselves are still present.
+
\value DrawPoints
\value DrawLines
- \value DrawLineLoop
+ \omitvalue DrawLineLoop
\value DrawLineStrip
\value DrawTriangles
\value DrawTriangleStrip
- \value DrawTriangleFan
+ \omitvalue DrawTriangleFan
*/
/*!
@@ -536,10 +541,10 @@ void QSGGeometry::setDrawingMode(unsigned int mode)
}
/*!
- Gets the current line or point width or to be used for this
- geometry. This property only applies to line width when the drawingMode
- is DrawLines, DarwLineStrip, or DrawLineLoop. When supported, it also
- applies to point size when the drawingMode is DrawPoints.
+ Gets the current line or point width or to be used for this geometry. This
+ property only applies to line width when the drawingMode is DrawLines or
+ DrawLineStrip. When supported, it also applies to point size when the
+ drawingMode is DrawPoints.
The default value is \c 1.0
@@ -558,10 +563,10 @@ float QSGGeometry::lineWidth() const
}
/*!
- Sets the line or point width to be used for this geometry to \a
- width. This property only applies to line width when the drawingMode is
- DrawLines, DrawLineStrip, or DrawLineLoop. When supported, it also
- applies to point size when the drawingMode is DrawPoints.
+ Sets the line or point width to be used for this geometry to \a width. This
+ property only applies to line width when the drawingMode is DrawLines or
+ DrawLineStrip. When supported, it also applies to point size when the
+ drawingMode is DrawPoints.
\note Support for point and line drawing may be limited at run time,
depending on the platform and graphics API. For example, some APIs do
diff --git a/src/quick/scenegraph/coreapi/qsggeometry.h b/src/quick/scenegraph/coreapi/qsggeometry.h
index 0b4a7cdce1..7fff62a915 100644
--- a/src/quick/scenegraph/coreapi/qsggeometry.h
+++ b/src/quick/scenegraph/coreapi/qsggeometry.h
@@ -30,8 +30,6 @@ public:
StaticPattern = 3
};
- // Equivalents to GL_* drawing modes.
- // Keep in sync with GL headers.
enum DrawingMode {
DrawPoints = 0x0000,
DrawLines = 0x0001,
@@ -42,8 +40,6 @@ public:
DrawTriangleFan = 0x0006
};
- // Equivalents to GL_BYTE and similar type constants.
- // Keep in sync with GL headers.
enum Type {
ByteType = 0x1400,
UnsignedByteType = 0x1401,
diff --git a/src/quick/scenegraph/coreapi/qsgmaterial.cpp b/src/quick/scenegraph/coreapi/qsgmaterial.cpp
index 9c7cbe4d8d..c507f2a87f 100644
--- a/src/quick/scenegraph/coreapi/qsgmaterial.cpp
+++ b/src/quick/scenegraph/coreapi/qsgmaterial.cpp
@@ -21,10 +21,6 @@ void qsg_set_material_failure()
}
#endif
-#ifndef QT_NO_DEBUG
-static const bool qsg_leak_check = !qEnvironmentVariableIsEmpty("QML_LEAK_CHECK");
-#endif
-
/*!
\group qtquick-scenegraph-materials
\title Qt Quick Scene Graph Material Classes
@@ -39,7 +35,7 @@ static int qt_material_count = 0;
static void qt_print_material_count()
{
- qDebug("Number of leaked materials: %i", qt_material_count);
+ qCDebug(lcQsgLeak, "Number of leaked materials: %i", qt_material_count);
qt_material_count = -1;
}
#endif
@@ -109,7 +105,7 @@ QSGMaterial::QSGMaterial()
{
Q_UNUSED(m_reserved);
#ifndef QT_NO_DEBUG
- if (qsg_leak_check) {
+ if (lcQsgLeak().isDebugEnabled()) {
++qt_material_count;
static bool atexit_registered = false;
if (!atexit_registered) {
@@ -128,10 +124,10 @@ QSGMaterial::QSGMaterial()
QSGMaterial::~QSGMaterial()
{
#ifndef QT_NO_DEBUG
- if (qsg_leak_check) {
+ if (lcQsgLeak().isDebugEnabled()) {
--qt_material_count;
if (qt_material_count < 0)
- qDebug("Material destroyed after qt_print_material_count() was called.");
+ qCDebug(lcQsgLeak, "Material destroyed after qt_print_material_count() was called.");
}
#endif
}
@@ -165,6 +161,10 @@ QSGMaterial::~QSGMaterial()
\value CustomCompileStep In Qt 6 this flag is identical to NoBatching. Prefer using
NoBatching instead.
+
+ \omitvalue MultiView2
+ \omitvalue MultiView3
+ \omitvalue MultiView4
*/
/*!
@@ -249,4 +249,186 @@ int QSGMaterial::compare(const QSGMaterial *other) const
RenderMode3D is in use.
*/
+/*!
+ \return The number of views in case of the material is used in multiview
+ rendering.
+
+ \note The return value is valid only when called from createShader(), and
+ afterwards. The value is not necessarily up-to-date before createShader()
+ is invokved by the scene graph.
+
+ Normally the return value is \c 1. A view count greater than 2 implies a
+ \e{multiview render pass}. Materials that support multiview are expected to
+ query viewCount() in createShader(), or in their QSGMaterialShader
+ constructor, and ensure the appropriate shaders are picked. The vertex
+ shader is then expected to use
+ \c{gl_ViewIndex} to index the modelview-projection matrix array as there
+ are multiple matrices in multiview mode. (one for each view)
+
+ As an example, take the following simple vertex shader:
+
+ \badcode
+ #version 440
+
+ layout(location = 0) in vec4 vertexCoord;
+ layout(location = 1) in vec4 vertexColor;
+
+ layout(location = 0) out vec4 color;
+
+ layout(std140, binding = 0) uniform buf {
+ mat4 matrix[2];
+ float opacity;
+ };
+
+ void main()
+ {
+ gl_Position = matrix[gl_ViewIndex] * vertexCoord;
+ color = vertexColor * opacity;
+ }
+ \endcode
+
+ This shader is prepared to handle 2 views, and 2 views only. It is not
+ compatible with other view counts. When conditioning the shader, the \c qsb
+ tool has to be invoked with \c{--view-count 2} or, if using the CMake
+ integration,
+ \c{VIEW_COUNT 2} must be specified in the \c{qt_add_shaders()} command.
+
+ \note A line with \c{#extension GL_EXT_multiview : require} is injected
+ automatically by \c qsb whenever a view count of 2 or greater is set.
+
+ Developers are encouraged to use the automatically injected preprocessor
+ variable \c{QSHADER_VIEW_COUNT} to simplify the handling of the different
+ number of views. For example, if there is a need to support both
+ non-multiview and multiview with a view count of 2 in the same source file,
+ the following could be done:
+
+ \badcode
+ #version 440
+
+ layout(location = 0) in vec4 vertexCoord;
+ layout(location = 1) in vec4 vertexColor;
+
+ layout(location = 0) out vec4 color;
+
+ layout(std140, binding = 0) uniform buf {
+ #if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+ #else
+ mat4 matrix;
+ #endif
+ float opacity;
+ };
+
+ void main()
+ {
+ #if QSHADER_VIEW_COUNT >= 2
+ gl_Position = matrix[gl_ViewIndex] * vertexCoord;
+ #else
+ gl_Position = matrix * vertexCoord;
+ #endif
+ color = vertexColor * opacity;
+ }
+ \endcode
+
+ The same source file can now be run through \c qsb or \c{qt_add_shaders()}
+ twice, once without specify the view count, and once with the view count
+ set to 2. The material can then pick the appropriate .qsb file based on
+ viewCount() at run time.
+
+ With CMake, this could looks similar to the following. With this example
+ the corresponding QSGMaterialShader is expected to choose between
+ \c{:/shaders/example.vert.qsb} and \c{:/shaders/multiview/example.vert.qsb}
+ based on the value of viewCount(). (same goes for the fragment shader)
+
+ \badcode
+ qt_add_shaders(application "application_shaders"
+ PREFIX
+ /
+ FILES
+ shaders/example.vert
+ shaders/example.frag
+ )
+
+ qt_add_shaders(application "application_multiview_shaders"
+ GLSL
+ 330,300es
+ HLSL
+ 61
+ MSL
+ 12
+ VIEW_COUNT
+ 2
+ PREFIX
+ /
+ FILES
+ shaders/example.vert
+ shaders/example.frag
+ OUTPUTS
+ shaders/multiview/example.vert
+ shaders/multiview/example.frag
+ )
+ \endcode
+
+ \note The fragment shader should be treated the same way the vertex shader
+ is, even though the fragment shader code cannot have any dependency on the
+ view count (\c{gl_ViewIndex}), for maximum portability. There are two
+ reasons for including fragment shaders too in the multiview set. One is that
+ mixing different shader versions within the same graphics pipeline can be
+ problematic, depending on the underlying graphics API: with D3D12 for
+ example, mixing HLSL shaders for shader model 5.0 and 6.1 would generate an
+ error. The other is that having \c QSHADER_VIEW_COUNT defined in fragment
+ shaders can be very useful, for example when sharing a uniform buffer layout
+ between the vertex and fragment stages.
+
+ \note For OpenGL the minimum GLSL version for vertex shaders relying on
+ \c{gl_ViewIndex} is \c 330. Lower versions may be accepted at build time,
+ but may lead to an error at run time, depending on the OpenGL implementation.
+
+ As a convenience, there is also a \c MULTIVIEW option for qt_add_shaders().
+ This first runs the \c qsb tool normally, then overrides \c VIEW_COUNT to
+ \c 2, sets \c GLSL, \c HLSL, \c MSL to some suitable defaults, and runs \c
+ qsb again, this time outputting .qsb files with a suffix added. The material
+ implementation can then use the \l QSGMaterialShader::setShaderFileName()
+ overload taking a \c viewCount argument, that automatically picks the
+ correct .qsb file.
+
+ The following is therefore mostly equivalent to the example call shown
+ above, except that no manually managed output files need to be specified.
+ Note that there can be cases when the automatically chosen shading language
+ versions are not sufficient, in which case applications should continue
+ specify everything explicitly.
+
+ \badcode
+ qt_add_shaders(application "application_multiview_shaders"
+ MULTIVIEW
+ PREFIX
+ /
+ FILES
+ shaders/example.vert
+ shaders/example.frag
+ )
+ \endcode
+
+ See \l QRhi::MultiView, \l QRhiColorAttachment::setMultiViewCount(), and
+ \l QRhiGraphicsPipeline::setMultiViewCount() for further, lower-level details
+ on multiview support in Qt. The Qt Quick scene graph renderer is prepared to
+ recognize multiview render targets, when specified via \l
+ QQuickRenderTarget::fromRhiRenderTarget() or the 3D API specific functions,
+ such as \l{QQuickRenderTarget::}{fromVulkanImage()} with an \c arraySize
+ argument greater than 1. The renderer will then propagate the view count to
+ graphics pipelines and the materials.
+
+ \since 6.8
+ */
+int QSGMaterial::viewCount() const
+{
+ if (m_flags.testFlag(MultiView4))
+ return 4;
+ if (m_flags.testFlag(MultiView3))
+ return 3;
+ if (m_flags.testFlag(MultiView2))
+ return 2;
+ return 1;
+}
+
QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/coreapi/qsgmaterial.h b/src/quick/scenegraph/coreapi/qsgmaterial.h
index 29de92d53e..ca3f134a9d 100644
--- a/src/quick/scenegraph/coreapi/qsgmaterial.h
+++ b/src/quick/scenegraph/coreapi/qsgmaterial.h
@@ -21,6 +21,10 @@ public:
RequiresFullMatrix = 0x0008 | RequiresFullMatrixExceptTranslate,
NoBatching = 0x0010,
+ MultiView2 = 0x10000,
+ MultiView3 = 0x20000,
+ MultiView4 = 0x40000,
+
#if QT_DEPRECATED_SINCE(6, 3)
CustomCompileStep Q_DECL_ENUMERATOR_DEPRECATED_X(
"Qt 6 does not have custom shader compilation support. If the intention is to just disable batching, use NoBatching instead."
@@ -40,6 +44,8 @@ public:
QSGMaterial::Flags flags() const { return m_flags; }
void setFlag(Flags flags, bool on = true);
+ int viewCount() const;
+
private:
Flags m_flags;
void *m_reserved;
diff --git a/src/quick/scenegraph/coreapi/qsgmaterialshader.cpp b/src/quick/scenegraph/coreapi/qsgmaterialshader.cpp
index b4090eb9cd..57090f73b3 100644
--- a/src/quick/scenegraph/coreapi/qsgmaterialshader.cpp
+++ b/src/quick/scenegraph/coreapi/qsgmaterialshader.cpp
@@ -208,8 +208,8 @@ void QSGMaterialShaderPrivate::prepare(QShader::Variant vertexShaderVariant)
clearCachedRendererData();
for (QShader::Stage stage : { QShader::VertexStage, QShader::FragmentStage }) {
- auto it = shaderFileNames.find(stage);
- if (it != shaderFileNames.end()) {
+ auto it = shaderFileNames.constFind(stage);
+ if (it != shaderFileNames.cend()) {
QString fn = *it;
const QShader s = loadShader(*it);
if (!s.isValid())
@@ -361,6 +361,33 @@ void QSGMaterialShader::setShaderFileName(Stage stage, const QString &filename)
}
/*!
+ Sets the \a filename for the shader for the specified \a stage.
+
+ The file is expected to contain a serialized QShader.
+
+ This overload is used when enabling \l{QSGMaterial::viewCount()}{multiview}
+ rendering, in particular when the \l{Qt Shader Tools Build System
+ Integration}{build system's MULTIVIEW convenience option} is used.
+
+ \a viewCount should be 2, 3, or 4. The \a filename is adjusted automatically
+ based on this.
+
+ \since 6.8
+ */
+void QSGMaterialShader::setShaderFileName(Stage stage, const QString &filename, int viewCount)
+{
+ Q_D(QSGMaterialShader);
+ if (viewCount == 2)
+ d->shaderFileNames[toShaderStage(stage)] = filename + QStringLiteral(".mv2qsb");
+ else if (viewCount == 3)
+ d->shaderFileNames[toShaderStage(stage)] = filename + QStringLiteral(".mv3qsb");
+ else if (viewCount == 4)
+ d->shaderFileNames[toShaderStage(stage)] = filename + QStringLiteral(".mv4qsb");
+ else
+ d->shaderFileNames[toShaderStage(stage)] = filename;
+}
+
+/*!
\return the currently set flags for this material shader.
*/
QSGMaterialShader::Flags QSGMaterialShader::flags() const
@@ -634,6 +661,17 @@ bool QSGMaterialShader::updateGraphicsPipelineState(RenderState &state, Graphics
*/
/*!
+ \enum QSGMaterialShader::GraphicsPipelineState::BlendOp
+ \since 6.8
+
+ \value BlendOpAdd
+ \value BlendOpSubtract
+ \value BlendOpReverseSubtract
+ \value BlendOpMin
+ \value BlendOpMax
+ */
+
+/*!
\enum QSGMaterialShader::GraphicsPipelineState::ColorMaskComponent
\since 5.14
@@ -745,6 +783,18 @@ bool QSGMaterialShader::updateGraphicsPipelineState(RenderState &state, Graphics
*/
/*!
+ \variable QSGMaterialShader::GraphicsPipelineState::opColor
+ \since 6.8
+ \brief RGB blending operation.
+ */
+
+/*!
+ \variable QSGMaterialShader::GraphicsPipelineState::opAlpha
+ \since 6.8
+ \brief Alpha blending operation.
+ */
+
+/*!
Returns the accumulated opacity to be used for rendering.
*/
float QSGMaterialShader::RenderState::opacity() const
@@ -768,7 +818,16 @@ float QSGMaterialShader::RenderState::determinant() const
QMatrix4x4 QSGMaterialShader::RenderState::combinedMatrix() const
{
Q_ASSERT(m_data);
- return static_cast<const QSGRenderer *>(m_data)->currentCombinedMatrix();
+ return static_cast<const QSGRenderer *>(m_data)->currentCombinedMatrix(0);
+}
+
+/*!
+ \internal
+ */
+QMatrix4x4 QSGMaterialShader::RenderState::combinedMatrix(int index) const
+{
+ Q_ASSERT(m_data);
+ return static_cast<const QSGRenderer *>(m_data)->currentCombinedMatrix(index);
}
/*!
@@ -807,7 +866,25 @@ QMatrix4x4 QSGMaterialShader::RenderState::modelViewMatrix() const
QMatrix4x4 QSGMaterialShader::RenderState::projectionMatrix() const
{
Q_ASSERT(m_data);
- return static_cast<const QSGRenderer *>(m_data)->currentProjectionMatrix();
+ return static_cast<const QSGRenderer *>(m_data)->currentProjectionMatrix(0);
+}
+
+/*!
+ \internal
+ */
+QMatrix4x4 QSGMaterialShader::RenderState::projectionMatrix(int index) const
+{
+ Q_ASSERT(m_data);
+ return static_cast<const QSGRenderer *>(m_data)->currentProjectionMatrix(index);
+}
+
+/*!
+ \internal
+ */
+int QSGMaterialShader::RenderState::projectionMatrixCount() const
+{
+ Q_ASSERT(m_data);
+ return static_cast<const QSGRenderer *>(m_data)->projectionMatrixCount();
}
/*!
diff --git a/src/quick/scenegraph/coreapi/qsgmaterialshader.h b/src/quick/scenegraph/coreapi/qsgmaterialshader.h
index caf44942cf..85ebf6eb8a 100644
--- a/src/quick/scenegraph/coreapi/qsgmaterialshader.h
+++ b/src/quick/scenegraph/coreapi/qsgmaterialshader.h
@@ -40,8 +40,11 @@ public:
float opacity() const;
QMatrix4x4 combinedMatrix() const;
+ QMatrix4x4 combinedMatrix(int index) const;
QMatrix4x4 modelViewMatrix() const;
QMatrix4x4 projectionMatrix() const;
+ QMatrix4x4 projectionMatrix(int index) const;
+ int projectionMatrixCount() const;
QRect viewportRect() const;
QRect deviceRect() const;
float determinant() const;
@@ -57,7 +60,7 @@ public:
const void *m_data;
};
- struct Q_QUICK_EXPORT GraphicsPipelineState {
+ struct GraphicsPipelineState {
enum BlendFactor {
Zero,
One,
@@ -80,6 +83,14 @@ public:
OneMinusSrc1Alpha
};
+ enum BlendOp {
+ BlendOpAdd,
+ BlendOpSubtract,
+ BlendOpReverseSubtract,
+ BlendOpMin,
+ BlendOpMax
+ };
+
enum ColorMaskComponent {
R = 1 << 0,
G = 1 << 1,
@@ -109,6 +120,9 @@ public:
bool separateBlendFactors;
BlendFactor srcAlpha;
BlendFactor dstAlpha;
+ BlendOp opColor;
+ BlendOp opAlpha;
+
// This struct is extensible while keeping BC since apps only ever get
// a ptr to the struct, it is not created by them.
};
@@ -147,6 +161,7 @@ protected:
// filename is for a file containing a serialized QShader.
void setShaderFileName(Stage stage, const QString &filename);
+ void setShaderFileName(Stage stage, const QString &filename, int viewCount);
void setShader(Stage stage, const QShader &shader);
diff --git a/src/quick/scenegraph/coreapi/qsgmaterialshader_p.h b/src/quick/scenegraph/coreapi/qsgmaterialshader_p.h
index de0161e431..8b4a4c0b2f 100644
--- a/src/quick/scenegraph/coreapi/qsgmaterialshader_p.h
+++ b/src/quick/scenegraph/coreapi/qsgmaterialshader_p.h
@@ -18,14 +18,14 @@
#include <private/qtquickglobal_p.h>
#include "qsgmaterialshader.h"
#include "qsgmaterial.h"
-#include <QtGui/private/qrhi_p.h>
-#include <QtGui/private/qshader_p.h>
+#include <rhi/qrhi.h>
+#include <rhi/qshader.h>
QT_BEGIN_NAMESPACE
class QRhiSampler;
-class Q_QUICK_PRIVATE_EXPORT QSGMaterialShaderPrivate
+class Q_QUICK_EXPORT QSGMaterialShaderPrivate
{
public:
Q_DECLARE_PUBLIC(QSGMaterialShader)
diff --git a/src/quick/scenegraph/coreapi/qsgnode.cpp b/src/quick/scenegraph/coreapi/qsgnode.cpp
index 62979cb9fc..ca1b8fc2e9 100644
--- a/src/quick/scenegraph/coreapi/qsgnode.cpp
+++ b/src/quick/scenegraph/coreapi/qsgnode.cpp
@@ -12,12 +12,11 @@
QT_BEGIN_NAMESPACE
#ifndef QT_NO_DEBUG
-static const bool qsg_leak_check = !qEnvironmentVariableIsEmpty("QML_LEAK_CHECK");
static int qt_node_count = 0;
static void qt_print_node_count()
{
- qDebug("Number of leaked nodes: %i", qt_node_count);
+ qCDebug(lcQsgLeak, "Number of leaked nodes: %i", qt_node_count);
qt_node_count = -1;
}
#endif
@@ -256,7 +255,7 @@ QSGNode::QSGNode(QSGNodePrivate &dd, NodeType type)
void QSGNode::init()
{
#ifndef QT_NO_DEBUG
- if (qsg_leak_check) {
+ if (lcQsgLeak().isDebugEnabled()) {
++qt_node_count;
static bool atexit_registered = false;
if (!atexit_registered) {
@@ -281,10 +280,10 @@ void QSGNode::init()
QSGNode::~QSGNode()
{
#ifndef QT_NO_DEBUG
- if (qsg_leak_check) {
+ if (lcQsgLeak().isDebugEnabled()) {
--qt_node_count;
if (qt_node_count < 0)
- qDebug("Node destroyed after qt_print_node_count() was called.");
+ qCDebug(lcQsgLeak, "Node destroyed after qt_print_node_count() was called.");
}
#endif
destroy();
diff --git a/src/quick/scenegraph/coreapi/qsgnode.h b/src/quick/scenegraph/coreapi/qsgnode.h
index 659d085922..50dc6021ea 100644
--- a/src/quick/scenegraph/coreapi/qsgnode.h
+++ b/src/quick/scenegraph/coreapi/qsgnode.h
@@ -175,8 +175,8 @@ private:
QSGGeometry *m_geometry;
- int m_reserved_start_index;
- int m_reserved_end_index;
+ Q_DECL_UNUSED_MEMBER int m_reserved_start_index;
+ Q_DECL_UNUSED_MEMBER int m_reserved_end_index;
const QMatrix4x4 *m_matrix;
const QSGClipNode *m_clip_list;
diff --git a/src/quick/scenegraph/coreapi/qsgnodeupdater_p.h b/src/quick/scenegraph/coreapi/qsgnodeupdater_p.h
index 6babf76395..a2bf37b538 100644
--- a/src/quick/scenegraph/coreapi/qsgnodeupdater_p.h
+++ b/src/quick/scenegraph/coreapi/qsgnodeupdater_p.h
@@ -28,7 +28,7 @@ class QSGGeometryNode;
class QMatrix4x4;
class QSGRenderNode;
-class Q_QUICK_PRIVATE_EXPORT QSGNodeUpdater
+class Q_QUICK_EXPORT QSGNodeUpdater
{
public:
QSGNodeUpdater();
diff --git a/src/quick/scenegraph/coreapi/qsgrenderer.cpp b/src/quick/scenegraph/coreapi/qsgrenderer.cpp
index 5d67b5dd74..ffa3047850 100644
--- a/src/quick/scenegraph/coreapi/qsgrenderer.cpp
+++ b/src/quick/scenegraph/coreapi/qsgrenderer.cpp
@@ -14,6 +14,13 @@ static QElapsedTimer frameTimer;
static qint64 preprocessTime;
static qint64 updatePassTime;
+Q_TRACE_POINT(qtquick, QSG_preprocess_entry)
+Q_TRACE_POINT(qtquick, QSG_preprocess_exit)
+Q_TRACE_POINT(qtquick, QSG_update_entry)
+Q_TRACE_POINT(qtquick, QSG_update_exit)
+Q_TRACE_POINT(qtquick, QSG_renderScene_entry)
+Q_TRACE_POINT(qtquick, QSG_renderScene_exit)
+
int qt_sg_envInt(const char *name, int defaultValue)
{
if (Q_LIKELY(!qEnvironmentVariableIsSet(name)))
@@ -64,6 +71,8 @@ QSGRenderer::QSGRenderer(QSGRenderContext *context)
, m_is_rendering(false)
, m_is_preprocessing(false)
{
+ m_current_projection_matrix.resize(1);
+ m_current_projection_matrix_native_ndc.resize(1);
}
@@ -103,7 +112,7 @@ void QSGRenderer::setNodeUpdater(QSGNodeUpdater *updater)
bool QSGRenderer::isMirrored() const
{
- QMatrix4x4 matrix = projectionMatrix();
+ QMatrix4x4 matrix = projectionMatrix(0);
// Mirrored relative to the usual Qt coordinate system with origin in the top left corner.
return matrix(0, 0) * matrix(1, 1) - matrix(0, 1) * matrix(1, 0) > 0;
}
diff --git a/src/quick/scenegraph/coreapi/qsgrenderer_p.h b/src/quick/scenegraph/coreapi/qsgrenderer_p.h
index 1485e903f4..28654521c9 100644
--- a/src/quick/scenegraph/coreapi/qsgrenderer_p.h
+++ b/src/quick/scenegraph/coreapi/qsgrenderer_p.h
@@ -29,10 +29,10 @@ class QRhiCommandBuffer;
class QRhiRenderPassDescriptor;
class QRhiResourceUpdateBatch;
-Q_QUICK_PRIVATE_EXPORT bool qsg_test_and_clear_fatal_render_error();
-Q_QUICK_PRIVATE_EXPORT void qsg_set_fatal_renderer_error();
+Q_QUICK_EXPORT bool qsg_test_and_clear_fatal_render_error();
+Q_QUICK_EXPORT void qsg_set_fatal_renderer_error();
-class Q_QUICK_PRIVATE_EXPORT QSGRenderTarget
+class Q_QUICK_EXPORT QSGRenderTarget
{
public:
QSGRenderTarget() { }
@@ -53,18 +53,20 @@ public:
QRhiCommandBuffer *cb = nullptr;
QPaintDevice *paintDevice = nullptr;
+
+ int multiViewCount = 0;
};
-class Q_QUICK_PRIVATE_EXPORT QSGRenderer : public QSGAbstractRenderer
+class Q_QUICK_EXPORT QSGRenderer : public QSGAbstractRenderer
{
public:
QSGRenderer(QSGRenderContext *context);
virtual ~QSGRenderer();
// Accessed by QSGMaterial[Rhi]Shader::RenderState.
- QMatrix4x4 currentProjectionMatrix() const { return m_current_projection_matrix; }
+ QMatrix4x4 currentProjectionMatrix(int index) const { return m_current_projection_matrix[index]; }
QMatrix4x4 currentModelViewMatrix() const { return m_current_model_view_matrix; }
- QMatrix4x4 currentCombinedMatrix() const { return m_current_projection_matrix * m_current_model_view_matrix; }
+ QMatrix4x4 currentCombinedMatrix(int index) const { return m_current_projection_matrix[index] * m_current_model_view_matrix; }
qreal currentOpacity() const { return m_current_opacity; }
qreal determinant() const { return m_current_determinant; }
@@ -115,8 +117,8 @@ protected:
void addNodesToPreprocess(QSGNode *node);
void removeNodesToPreprocess(QSGNode *node);
- QMatrix4x4 m_current_projection_matrix; // includes adjustment, where applicable, so can be treated as Y up in NDC always
- QMatrix4x4 m_current_projection_matrix_native_ndc; // Vulkan has Y down in normalized device coordinates, others Y up...
+ QVarLengthArray<QMatrix4x4, 1> m_current_projection_matrix; // includes adjustment, where applicable, so can be treated as Y up in NDC always
+ QVarLengthArray<QMatrix4x4, 1> m_current_projection_matrix_native_ndc; // Vulkan has Y down in normalized device coordinates, others Y up...
QMatrix4x4 m_current_model_view_matrix;
qreal m_current_opacity;
qreal m_current_determinant;
@@ -154,7 +156,7 @@ QSGMaterialShader::RenderState QSGRenderer::state(QSGMaterialShader::RenderState
}
-class Q_QUICK_PRIVATE_EXPORT QSGNodeDumper : public QSGNodeVisitor {
+class Q_QUICK_EXPORT QSGNodeDumper : public QSGNodeVisitor {
public:
static void dump(QSGNode *n);
@@ -167,8 +169,6 @@ private:
int m_indent = 0;
};
-
-
QT_END_NAMESPACE
#endif
diff --git a/src/quick/scenegraph/coreapi/qsgrendererinterface.cpp b/src/quick/scenegraph/coreapi/qsgrendererinterface.cpp
index 5245b6d950..b251d13edd 100644
--- a/src/quick/scenegraph/coreapi/qsgrendererinterface.cpp
+++ b/src/quick/scenegraph/coreapi/qsgrendererinterface.cpp
@@ -40,11 +40,12 @@ QT_BEGIN_NAMESPACE
\value Unknown An unknown graphics API is in use
\value Software The Qt Quick 2D Renderer is in use
\value OpenVG OpenVG via EGL
- \value OpenGL OpenGL ES 2.0 or higher via a graphics abstraction layer. This value was introduced in Qt 5.14.
- \value Direct3D11 Direct3D 11 via a graphics abstraction layer. This value was introduced in Qt 5.14.
- \value Vulkan Vulkan 1.0 via a graphics abstraction layer. This value was introduced in Qt 5.14.
- \value Metal Metal via a graphics abstraction layer. This value was introduced in Qt 5.14.
- \value Null Null (no output) via a graphics abstraction layer. This value was introduced in Qt 5.14.
+ \value [since 5.14] OpenGL OpenGL ES 2.0 or higher via a graphics abstraction layer.
+ \value [since 5.14] Direct3D11 Direct3D 11 via a graphics abstraction layer.
+ \value [since 6.6] Direct3D12 Direct3D 12 via a graphics abstraction layer.
+ \value [since 5.14] Vulkan Vulkan 1.0 via a graphics abstraction layer.
+ \value [since 5.14] Metal Metal via a graphics abstraction layer.
+ \value [since 5.14] Null Null (no output) via a graphics abstraction layer.
\omitvalue OpenGLRhi
\omitvalue Direct3D11Rhi
\omitvalue VulkanRhi
@@ -77,65 +78,69 @@ QT_BEGIN_NAMESPACE
\value PainterResource The resource is a pointer to the active QPainter
used by the scenegraph, when running with the software backend.
- \value RhiResource The resource is a pointer to the QRhi instance used by
- the scenegraph, when applicable. This value was introduced in Qt 5.14.
+ \value [since 5.14] RhiResource The resource is a pointer to the QRhi instance used by
+ the scenegraph, when applicable.
- \value RhiSwapchainResource The resource is a pointer to a QRhiSwapchain
+ \value [since 6.0] RhiSwapchainResource The resource is a pointer to a QRhiSwapchain
instance that is associated with the window. The value is null when the
- window is used in combination with QQuickRenderControl. This value was
- introduced in Qt 6.0.
+ window is used in combination with QQuickRenderControl.
- \value RhiRedirectCommandBuffer The resource is a pointer to a
+ \value [since 6.0] RhiRedirectCommandBuffer The resource is a pointer to a
QRhiCommandBuffer instance that is associated with the window and its
QQuickRenderControl. The value is null when the window is not associated
- with a QQuickRenderControl. This value was introduced in Qt 6.0.
+ with a QQuickRenderControl.
- \value RhiRedirectRenderTarget The resource is a pointer to a
+ \value [since 6.0] RhiRedirectRenderTarget The resource is a pointer to a
QRhiTextureRenderTarget instance that is associated with the window and its
QQuickRenderControl. The value is null when the window is not associated
with a QQuickRenderControl. Note that the value always reflects the main
texture render target and it does not depend on the Qt Quick scene, meaning
it does not take any additional texture-targeting render passes generated
- by ShaderEffect or QQuickItem layers into account. This value was
- introduced in Qt 6.0.
+ by ShaderEffect or QQuickItem layers into account.
- \value PhysicalDeviceResource The resource is a pointer to the pysical
+ \value [since 5.14] PhysicalDeviceResource The resource is a pointer to the pysical
device object used by the scenegraph, when applicable. For example, a
\c{VkPhysicalDevice *}. Note that with Vulkan the returned value is a
- pointer to the VkPhysicalDevice, not the handle itself. This value was
- introduced in Qt 5.14.
+ pointer to the VkPhysicalDevice, not the handle itself.
- \value OpenGLContextResource The resource is a pointer to the
+ \value [since 5.14] OpenGLContextResource The resource is a pointer to the
QOpenGLContext used by the scenegraph (on the render thread), when
- applicable. This value was introduced in Qt 5.14.
+ applicable.
- \value DeviceContextResource The resource is a pointer to the device
+ \value [since 5.14] DeviceContextResource The resource is a pointer to the device
context used by the scenegraph, when applicable. For example, a
- \c{ID3D11DeviceContext *}. This value was introduced in Qt 5.14.
+ \c{ID3D11DeviceContext *}.
- \value CommandEncoderResource The resource is a pointer to the currently
+ \value [since 5.14] CommandEncoderResource The resource is a pointer to the currently
active render command encoder object used by the scenegraph, when
applicable. For example, a \c{MTLRenderCommandEncoder *}. This object has
limited validity, and is only valid while the scene graph is recording a
- render pass for the next frame. This value was introduced in Qt 5.14.
+ render pass for the next frame.
- \value VulkanInstanceResource The resource is a pointer to the
- QVulkanInstance used by the scenegraph, when applicable. This value was
- introduced in Qt 5.14.
+ \value [since 5.14] VulkanInstanceResource The resource is a pointer to the
+ QVulkanInstance used by the scenegraph, when applicable.
- \value RenderPassResource The resource is a pointer to the main render pass
+ \value [since 5.14] RenderPassResource The resource is a pointer to the main render pass
used by the scenegraph, describing the color and depth/stecil attachments
and how they are used. For example, a \c{VkRenderPass *}. Note that the
value always reflects the main render target (either the on-screen window
or the texture QQuickRenderControl redirects to) and it does not depend on
the Qt Quick scene, meaning it does not take any additional
texture-targeting render passes generated by ShaderEffect or QQuickItem
- layers into account. This value was introduced in Qt 5.14.
+ layers into account.
- \value RedirectPaintDevice The resource is a pointer to QPaintDevice instance
+ \value [since 6.4] RedirectPaintDevice The resource is a pointer to QPaintDevice instance
that is associated with the window and its QQuickRenderControl. The value is
- null when the window is not associated with a QQuickRenderControl. This value
- was introduced in Qt 6.4.
+ null when the window is not associated with a QQuickRenderControl.
+
+ \value [since 6.6] GraphicsQueueFamilyIndexResource The resource is a pointer to the
+ graphics queue family index used by the scenegraph, when applicable. With
+ Vulkan, this is a pointer to a \c uint32_t index value.
+
+ \value [since 6.6] GraphicsQueueIndexResource The resource is a pointer to the graphics
+ queue index (uint32_t) used by the scenegraph, when applicable. With
+ Vulkan, this is a pointer to a \c uint32_t index value, which in practice
+ is the index of the VkQueue reported for \c CommandQueueResource.
*/
/*!
@@ -143,9 +148,8 @@ QT_BEGIN_NAMESPACE
\value UnknownShadingLanguage Not yet known due to no window and scenegraph associated
\value GLSL GLSL or GLSL ES
\value HLSL HLSL
- \value RhiShader Consumes QShader instances containing shader variants for
- multiple target languages and intermediate formats. This value was introduced in
- Qt 5.14.
+ \value [since 5.14] RhiShader Consumes QShader instances containing shader variants for
+ multiple target languages and intermediate formats.
*/
/*!
@@ -234,15 +238,12 @@ void *QSGRendererInterface::getResource(QQuickWindow *window, const char *resour
bool QSGRendererInterface::isApiRhiBased(GraphicsApi api)
{
switch (api) {
- case OpenGLRhi:
- Q_FALLTHROUGH();
- case Direct3D11Rhi:
- Q_FALLTHROUGH();
- case VulkanRhi:
- Q_FALLTHROUGH();
- case MetalRhi:
- Q_FALLTHROUGH();
- case NullRhi:
+ case OpenGL:
+ case Direct3D11:
+ case Direct3D12:
+ case Vulkan:
+ case Metal:
+ case Null:
return true;
default:
return false;
diff --git a/src/quick/scenegraph/coreapi/qsgrendererinterface.h b/src/quick/scenegraph/coreapi/qsgrendererinterface.h
index 7f08c8db34..d76bbed5cf 100644
--- a/src/quick/scenegraph/coreapi/qsgrendererinterface.h
+++ b/src/quick/scenegraph/coreapi/qsgrendererinterface.h
@@ -22,6 +22,7 @@ public:
Vulkan,
Metal,
Null,
+ Direct3D12,
OpenGLRhi = OpenGL,
Direct3D11Rhi = Direct3D11,
@@ -45,7 +46,9 @@ public:
CommandEncoderResource,
VulkanInstanceResource,
RenderPassResource,
- RedirectPaintDevice
+ RedirectPaintDevice,
+ GraphicsQueueFamilyIndexResource,
+ GraphicsQueueIndexResource,
};
enum ShaderType {
diff --git a/src/quick/scenegraph/coreapi/qsgrendernode.cpp b/src/quick/scenegraph/coreapi/qsgrendernode.cpp
index 7a4627e1b4..333d62d40b 100644
--- a/src/quick/scenegraph/coreapi/qsgrendernode.cpp
+++ b/src/quick/scenegraph/coreapi/qsgrendernode.cpp
@@ -12,6 +12,23 @@ QT_BEGIN_NAMESPACE
targeting the graphics API that is in use by the scenegraph.
\inmodule QtQuick
\since 5.8
+
+ QSGRenderNode allows creating scene graph nodes that perform their own
+ custom rendering via QRhi (the common approach from Qt 6.6 on), directly
+ via a 3D graphics API such as OpenGL, Vulkan, or Metal, or, when the \c
+ software backend is in use, via QPainter.
+
+ QSGRenderNode is the enabler for one of the three ways to integrate custom
+ 2D/3D rendering into a Qt Quick scene. The other two options are to perform
+ the rendering \c before or \c after the Qt Quick scene's own rendering,
+ or to generate a whole separate render pass targeting a dedicated render
+ target (a texture) and then have an item in the scene display the texture.
+ The QSGRenderNode-based approach is similar to the former, in the sense
+ that no additional render passes or render targets are involved, and allows
+ injecting custom rendering commands "inline" with the Qt Quick scene's
+ own rendering.
+
+ \sa {Scene Graph - Custom QSGRenderNode}
*/
QSGRenderNode::QSGRenderNode()
@@ -30,6 +47,13 @@ QSGRenderNode::QSGRenderNode()
deleted. Therefore there is no need to issue additional waits here, unless
the render() implementation is using additional command queues.
+ With QRhi and resources such as QRhiBuffer, QRhiTexture,
+ QRhiGraphicsPipeline, etc., it is often good practice to use smart
+ pointers, such as std::unique_ptr, which can often avoid the need to
+ implement a destructor, and lead to more compact source code. Keep in mind
+ however that implementing releaseResources(), most likely issuing a number
+ of reset() calls on the unique_ptrs, is still important.
+
\sa releaseResources()
*/
QSGRenderNode::~QSGRenderNode()
@@ -42,6 +66,7 @@ QSGRenderNodePrivate::QSGRenderNodePrivate()
, m_clip_list(nullptr)
, m_opacity(1)
{
+ m_projectionMatrix.resize(1);
}
/*!
@@ -49,17 +74,15 @@ QSGRenderNodePrivate::QSGRenderNodePrivate()
mask where each bit represents graphics states changed by the \l render()
function:
- \list
- \li DepthState - depth write mask, depth test enabled, depth comparison function
- \li StencilState - stencil write masks, stencil test enabled, stencil operations,
- stencil comparison functions
- \li ScissorState - scissor enabled, scissor test enabled
- \li ColorState - clear color, color write mask
- \li BlendState - blend enabled, blend function
- \li CullState - front face, cull face enabled
- \li ViewportState - viewport
- \li RenderTargetState - render target
- \endlist
+ \value DepthState depth write mask, depth test enabled, depth comparison function
+ \value StencilState stencil write masks, stencil test enabled, stencil operations,
+ stencil comparison functions
+ \value ScissorState scissor enabled, scissor test enabled
+ \value ColorState clear color, color write mask
+ \value BlendState blend enabled, blend function
+ \value CullState front face, cull face enabled
+ \value ViewportState viewport
+ \value RenderTargetState render target
With APIs other than OpenGL, the only relevant values are the ones that
correspond to dynamic state changes recorded on the command list/buffer.
@@ -73,16 +96,16 @@ QSGRenderNodePrivate::QSGRenderNodePrivate()
bindings, root signature, descriptor heaps, etc.) are always set again by
the scenegraph so render() can freely change them.
- \note RenderTargetState is no longer supported with APIs like Vulkan. This
+ RenderTargetState is no longer supported with APIs like Vulkan. This
is by nature. render() is invoked while the Qt Quick scenegraph's main
command buffer is recording a renderpass, so there is no possibility of
changing the target and starting another renderpass (on that command buffer
at least). Therefore returning a value with RenderTargetState set is not
sensible.
- The software backend exposes its QPainter and saves and restores before and
- after invoking render(). Therefore reporting any changed states from here
- is not necessary.
+ \note The \c software backend exposes its QPainter and saves and restores
+ before and after invoking render(). Therefore reporting any changed states
+ from here is not necessary.
The function is called by the renderer so it can reset the states after
rendering this node. This makes the implementation of render() simpler
@@ -92,6 +115,10 @@ QSGRenderNodePrivate::QSGRenderNodePrivate()
in render().
\note This function may be called before render().
+
+ \note With Qt 6 and QRhi-based rendering the only relevant values are
+ ViewportState and ScissorState. Other values can be returned but are
+ ignored in practice.
*/
QSGRenderNode::StateFlags QSGRenderNode::changedStates() const
{
@@ -110,6 +137,12 @@ QSGRenderNode::StateFlags QSGRenderNode::changedStates() const
The default implementation is empty.
+ When implementing a QSGRenderNode that uses QRhi to render, query the QRhi
+ object from the QQuickWindow via \l{QQuickWindow::rhi()}. To get a
+ QRhiCommandBuffer for submitting work to, call commandBuffer(). To query
+ information about the active render target, call renderTarget(). See the
+ \l{{Scene Graph - Custom QSGRenderNode}} example for details.
+
\since 6.0
*/
void QSGRenderNode::prepare()
@@ -160,57 +193,33 @@ void QSGRenderNode::prepare()
Some scenegraph backends, software in particular, use no scissor or
stencil. There the clip region is provided as an ordinary QRegion.
- With the legacy, direct OpenGL based renderer, the following states are set
- on the render thread's context before this function is called:
-
- \list
- \li glColorMask(true, true, true, true)
- \li glDepthMask(false)
- \li glDisable(GL_DEPTH_TEST)
- \li glStencilFunc(GL_EQUAL, state.stencilValue, 0xff); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP) depending on clip
- \li glScissor(state.scissorRect.x(), state.scissorRect.y(),
- state.scissorRect.width(), state.scissorRect.height()) depending on clip
- \li glEnable(GL_BLEND)
- \li glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)
- \li glDisable(GL_CULL_FACE)
- \endlist
-
- States that are not listed above, but are covered by \l StateFlags, can
- have arbitrary values.
-
- \note There is no state set with other graphics APIs, considering that many
- of them do not have a concept of the traditional OpenGL state machine.
- Rather, it is up to the implementation to create pipeline state objects
- with the desired blending, scissor, and stencil tests enabled. Note that
- this also includes OpenGL via the RHI. New QSGRenderNode implementations
- are recommended to set all scissor, stencil and blend state explicitly (as
- shown in the above list), even if they are targeting OpenGL.
-
- \l changedStates() should return which states this function changes. If a
- state is not covered by \l StateFlags, the state should be set to the
- default value according to the OpenGL specification. For other APIs, see
- the documentation for changedStates() for more information.
-
- \note Depth writes are disabled when this function is called
- (glDepthMask(false) with OpenGL). Enabling depth writes can lead to
- unexpected results, depending on the scenegraph backend in use and the
- content in the scene, so exercise caution with this.
-
- For APIs other than OpenGL, it will likely be necessary to query certain
- API-specific resources (for example, the graphics device or the command
- list/buffer to add the commands to). This is done via QSGRendererInterface.
-
- Assume nothing about the pipelines and dynamic states bound on the command
- list/buffer when this function is called.
-
- With some graphics APIs it can be necessary to reimplement prepare() in
- addition, or alternatively connect to the QQuickWindow::beforeRendering()
- signal. These are called/emitted before recording the beginning of a
- renderpass on the command buffer (vkCmdBeginRenderPass with Vulkan, or
- starting to encode via MTLRenderCommandEncoder in case of Metal. Recording
- copy operations cannot be done inside render() with such APIs. Rather, do
- such operations either in prepare() or the slot connected to
- beforeRendering (with DirectConnection).
+ When implementing a QSGRenderNode that uses QRhi to render, query the QRhi
+ object from the QQuickWindow via \l{QQuickWindow::rhi()}. To get a
+ QRhiCommandBuffer for submitting work to, call commandBuffer(). To query
+ information about the active render target, call renderTarget(). See the
+ \l{{Scene Graph - Custom QSGRenderNode}} example for details.
+
+ With Qt 6 and its QRhi-based scene graph renderer, no assumptions should be
+ made about the active (OpenGL) state when this function is called, even
+ when OpenGL is in use. Assume nothing about the pipelines and dynamic
+ states bound on the command list/buffer when this function is called.
+
+ \note Depth writes are expected to be disabled. Enabling depth writes can
+ lead to unexpected results, depending on the scenegraph backend in use and
+ the content in the scene, so exercise caution with this.
+
+ \note In Qt 6, \l changedStates() has limited use. See the documentation
+ for changedStates() for more information.
+
+ With some graphics APIs, including when using QRhi directly, it can be
+ necessary to reimplement prepare() in addition, or alternatively connect to
+ the QQuickWindow::beforeRendering() signal. These are called/emitted before
+ recording the beginning of a renderpass on the command buffer
+ (vkCmdBeginRenderPass with Vulkan, or starting to encode via
+ MTLRenderCommandEncoder in case of Metal. Recording copy operations cannot
+ be done inside render() with such APIs. Rather, do such operations either
+ in prepare() or the slot connected to beforeRendering (with
+ DirectConnection).
\sa QSGRendererInterface, QQuickWindow::rendererInterface()
*/
@@ -263,7 +272,7 @@ void QSGRenderNode::releaseResources()
\value BoundedRectRendering Indicates that the implementation of render()
does not render outside the area reported from rect() in item
coordinates. Such node implementations can lead to more efficient rendering,
- depending on the scenegraph backend. For example, the software backend can
+ depending on the scenegraph backend. For example, the \c software backend can
continue to use the more optimal partial update path when all render nodes
in the scene have this flag set.
@@ -282,9 +291,11 @@ void QSGRenderNode::releaseResources()
transparent pixels. Setting this flag can improve performance in some
cases.
- \omitvalue NoExternalRendering
+ \value NoExternalRendering Indicates that the implementation of prepare()
+ and render() use the QRhi family of APIs, instead of directly calling a 3D
+ API such as OpenGL, Vulkan, or Metal.
- \sa render(), rect()
+ \sa render(), prepare(), rect(), QRhi
*/
/*!
@@ -341,7 +352,15 @@ QRectF QSGRenderNode::rect() const
*/
const QMatrix4x4 *QSGRenderNode::projectionMatrix() const
{
- return &d->m_projectionMatrix;
+ return &d->m_projectionMatrix[0];
+}
+
+/*!
+ \internal
+ */
+const QMatrix4x4 *QSGRenderNode::projectionMatrix(int index) const
+{
+ return &d->m_projectionMatrix[index];
}
/*!
@@ -368,6 +387,59 @@ qreal QSGRenderNode::inheritedOpacity() const
return d->m_opacity;
}
+/*!
+ \return the current render target.
+
+ This is provided mainly to enable prepare() and render() implementations
+ that use QRhi accessing the QRhiRenderTarget's
+ \l{QRhiRenderPassDescriptor}{renderPassDescriptor} or
+ \l{QRhiRenderTarget::pixelSize()}{pixel size}.
+
+ To build a QRhiGraphicsPipeline, which implies having to provide a
+ QRhiRenderPassDescriptor, query the renderPassDescriptor from the render
+ target. Be aware however that the render target may change over the
+ lifetime of the custom QQuickItem and the QSGRenderNode. For example,
+ consider what happens when dynamically setting \c{layer.enabled: true} on
+ the item or an ancestor of it: this triggers rendering into a texture, not
+ directly to the window, which means the QSGRenderNode is going to work with
+ a different render target from then on. The new render target may then have
+ a different pixel format, which can make already built graphics pipelines
+ incompatible. This can be handled with logic such as the following:
+
+ \code
+ if (m_pipeline && renderTarget()->renderPassDescriptor()->serializedFormat() != m_renderPassFormat) {
+ delete m_pipeline;
+ m_pipeline = nullptr;
+ }
+ if (!m_pipeline) {
+ // Build a new QRhiGraphicsPipeline.
+ // ...
+ // Store the serialized format for fast and simple comparisons later on.
+ m_renderPassFormat = renderTarget()->renderPassDescriptor()->serializedFormat();
+ }
+ \endcode
+
+ \since 6.6
+
+ \sa commandBuffer()
+ */
+QRhiRenderTarget *QSGRenderNode::renderTarget() const
+{
+ return d->m_rt.rt;
+}
+
+/*!
+ \return the current command buffer.
+
+ \since 6.6
+
+ \sa renderTarget()
+ */
+QRhiCommandBuffer *QSGRenderNode::commandBuffer() const
+{
+ return d->m_rt.cb;
+}
+
QSGRenderNode::RenderState::~RenderState()
{
}
@@ -421,7 +493,7 @@ QSGRenderNode::RenderState::~RenderState()
\return the current clip region or null for backends where clipping is
implemented via stencil or scissoring.
- The software backend uses no projection, scissor or stencil, meaning most
+ The \c software backend uses no projection, scissor or stencil, meaning most
of the render state is not in use. However, the clip region that can be set
on the QPainter still has to be communicated since reconstructing this
manually in render() is not reasonable. It can therefore be queried via
diff --git a/src/quick/scenegraph/coreapi/qsgrendernode.h b/src/quick/scenegraph/coreapi/qsgrendernode.h
index fcf2b0e559..927a8dee59 100644
--- a/src/quick/scenegraph/coreapi/qsgrendernode.h
+++ b/src/quick/scenegraph/coreapi/qsgrendernode.h
@@ -9,6 +9,8 @@
QT_BEGIN_NAMESPACE
class QSGRenderNodePrivate;
+class QRhiRenderTarget;
+class QRhiCommandBuffer;
class Q_QUICK_EXPORT QSGRenderNode : public QSGNode
{
@@ -55,9 +57,12 @@ public:
virtual QRectF rect() const;
const QMatrix4x4 *projectionMatrix() const;
+ const QMatrix4x4 *projectionMatrix(int index) const;
const QMatrix4x4 *matrix() const;
const QSGClipNode *clipList() const;
qreal inheritedOpacity() const;
+ QRhiRenderTarget *renderTarget() const;
+ QRhiCommandBuffer *commandBuffer() const;
private:
QSGRenderNodePrivate *d;
diff --git a/src/quick/scenegraph/coreapi/qsgrendernode_p.h b/src/quick/scenegraph/coreapi/qsgrendernode_p.h
index e00415f480..8bdd457b95 100644
--- a/src/quick/scenegraph/coreapi/qsgrendernode_p.h
+++ b/src/quick/scenegraph/coreapi/qsgrendernode_p.h
@@ -21,7 +21,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QSGRenderNodePrivate
+class Q_QUICK_EXPORT QSGRenderNodePrivate
{
public:
QSGRenderNodePrivate();
@@ -32,7 +32,8 @@ public:
const QSGClipNode *m_clip_list;
qreal m_opacity;
QSGRenderTarget m_rt;
- QMatrix4x4 m_projectionMatrix;
+ QVarLengthArray<QMatrix4x4, 1> m_projectionMatrix;
+ QMatrix4x4 m_localMatrix; // ### Qt 7 m_matrix should not be a pointer
};
QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/coreapi/qsgrhivisualizer.cpp b/src/quick/scenegraph/coreapi/qsgrhivisualizer.cpp
index 6e8fcae06f..d1f4e46ed0 100644
--- a/src/quick/scenegraph/coreapi/qsgrhivisualizer.cpp
+++ b/src/quick/scenegraph/coreapi/qsgrhivisualizer.cpp
@@ -330,7 +330,7 @@ void RhiVisualizer::ChangeVis::gather(Node *n)
const QColor color = QColor::fromHsvF((visualizer->m_randomGenerator.generate() & 1023) / 1023.0f, 0.3f, 1.0f).toRgb();
const float alpha = 0.5f;
- QMatrix4x4 matrix = visualizer->m_renderer->m_current_projection_matrix;
+ QMatrix4x4 matrix = visualizer->m_renderer->m_current_projection_matrix[0];
if (n->element()->batch->root)
matrix = matrix * qsg_matrixForRoot(n->element()->batch->root);
@@ -445,7 +445,7 @@ void RhiVisualizer::BatchVis::gather(Batch *b)
if (b->positionAttribute != 0)
return;
- QMatrix4x4 matrix(visualizer->m_renderer->m_current_projection_matrix);
+ QMatrix4x4 matrix(visualizer->m_renderer->m_current_projection_matrix[0]);
if (b->root)
matrix = matrix * qsg_matrixForRoot(b->root);
@@ -575,7 +575,7 @@ void RhiVisualizer::ClipVis::gather(QSGNode *node)
{
if (node->type() == QSGNode::ClipNodeType) {
QSGClipNode *clipNode = static_cast<QSGClipNode *>(node);
- QMatrix4x4 matrix = visualizer->m_renderer->m_current_projection_matrix;
+ QMatrix4x4 matrix = visualizer->m_renderer->m_current_projection_matrix[0];
if (clipNode->matrix())
matrix = matrix * *clipNode->matrix();
@@ -674,7 +674,7 @@ void RhiVisualizer::ClipVis::render(QRhiCommandBuffer *cb)
void RhiVisualizer::OverdrawVis::gather(Node *n)
{
if (n->type() == QSGNode::GeometryNodeType && n->element()->batch) {
- QMatrix4x4 matrix = visualizer->m_renderer->m_current_projection_matrix;
+ QMatrix4x4 matrix = visualizer->m_renderer->m_current_projection_matrix[0];
matrix(2, 2) = visualizer->m_renderer->m_zRange;
matrix(2, 3) = 1.0f - n->element()->order * visualizer->m_renderer->m_zRange;
diff --git a/src/quick/scenegraph/coreapi/qsgtexture.cpp b/src/quick/scenegraph/coreapi/qsgtexture.cpp
index 31e68b9f31..c08f900f55 100644
--- a/src/quick/scenegraph/coreapi/qsgtexture.cpp
+++ b/src/quick/scenegraph/coreapi/qsgtexture.cpp
@@ -5,16 +5,17 @@
#include "qsgtexture_platform.h"
#include <private/qqmlglobal_p.h>
#include <private/qsgmaterialshader_p.h>
+#include <private/qsgrenderer_p.h>
#include <private/qquickitem_p.h> // qquickwindow_p.h cannot be included on its own due to template nonsense
#include <private/qquickwindow_p.h>
#include <QtCore/private/qnativeinterface_p.h>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) && defined(__GLIBC__)
#define CAN_BACKTRACE_EXECINFO
#endif
-#if defined(Q_OS_MAC)
+#if defined(Q_OS_APPLE)
#define CAN_BACKTRACE_EXECINFO
#endif
@@ -30,7 +31,6 @@
#ifndef QT_NO_DEBUG
Q_GLOBAL_STATIC(QSet<QSGTexture *>, qsg_valid_texture_set)
Q_GLOBAL_STATIC(QMutex, qsg_valid_texture_mutex)
-static const bool qsg_leak_check = !qEnvironmentVariableIsEmpty("QML_LEAK_CHECK");
#endif
QT_BEGIN_NAMESPACE
@@ -83,8 +83,9 @@ QSGTexturePrivate::QSGTexturePrivate(QSGTexture *t)
#endif
#ifdef Q_OS_WIN
, m_d3d11TextureAccessor(t)
+ , m_d3d12TextureAccessor(t)
#endif
-#if defined(__OBJC__)
+#if QT_CONFIG(metal)
, m_metalTextureAccessor(t)
#endif
#if QT_CONFIG(vulkan)
@@ -100,7 +101,7 @@ QSGTexturePrivate::QSGTexturePrivate(QSGTexture *t)
static int qt_debug_texture_count = 0;
-#if (defined(Q_OS_LINUX) || defined (Q_OS_MAC)) && !defined(Q_OS_ANDROID)
+#if (defined(Q_OS_LINUX) || defined (Q_OS_APPLE)) && !defined(Q_OS_ANDROID)
DEFINE_BOOL_CONFIG_OPTION(qmlDebugLeakBacktrace, QML_DEBUG_LEAK_BACKTRACE)
#define BACKTRACE_SIZE 20
@@ -116,7 +117,7 @@ static QHash<QSGTexture*, SGTextureTraceItem*> qt_debug_allocated_textures;
inline static void qt_debug_print_texture_count()
{
- qDebug("Number of leaked textures: %i", qt_debug_texture_count);
+ qCDebug(lcQsgLeak, "Number of leaked textures: %i", qt_debug_texture_count);
qt_debug_texture_count = -1;
#if defined(CAN_BACKTRACE_EXECINFO)
@@ -243,8 +244,6 @@ static void qt_debug_remove_texture(QSGTexture* texture)
\note All classes with QSG prefix should be used solely on the scene graph's
rendering thread. See \l {Scene Graph and Rendering} for more information.
-
- \sa {Scene Graph - Rendering FBOs}
*/
/*!
@@ -304,7 +303,7 @@ QSGTexture::QSGTexture()
: QObject(*(new QSGTexturePrivate(this)))
{
#ifndef QT_NO_DEBUG
- if (qsg_leak_check)
+ if (lcQsgLeak().isDebugEnabled())
qt_debug_add_texture(this);
QMutexLocker locker(qsg_valid_texture_mutex());
@@ -319,7 +318,7 @@ QSGTexture::QSGTexture(QSGTexturePrivate &dd)
: QObject(dd)
{
#ifndef QT_NO_DEBUG
- if (qsg_leak_check)
+ if (lcQsgLeak().isDebugEnabled())
qt_debug_add_texture(this);
QMutexLocker locker(qsg_valid_texture_mutex());
@@ -333,7 +332,7 @@ QSGTexture::QSGTexture(QSGTexturePrivate &dd)
QSGTexture::~QSGTexture()
{
#ifndef QT_NO_DEBUG
- if (qsg_leak_check)
+ if (lcQsgLeak().isDebugEnabled())
qt_debug_remove_texture(this);
QMutexLocker locker(qsg_valid_texture_mutex());
@@ -580,8 +579,6 @@ QSGTexture::WrapMode QSGTexture::verticalWrapMode() const
\warning This function can only be called from the rendering thread.
\since 6.0
-
- \internal
*/
QRhiTexture *QSGTexture::rhiTexture() const
{
@@ -602,8 +599,6 @@ QRhiTexture *QSGTexture::rhiTexture() const
\warning This function can only be called from the rendering thread.
\since 6.0
-
- \internal
*/
void QSGTexture::commitTextureOperations(QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates)
{
@@ -686,6 +681,7 @@ namespace QNativeInterface {
\inmodule QtQuick
\ingroup native-interfaces
\ingroup native-interfaces-qsgtexture
+ \inheaderfile QSGTexture
\brief Provides access to and enables adopting OpenGL texture objects.
\since 6.0
*/
@@ -786,6 +782,7 @@ namespace QNativeInterface {
\inmodule QtQuick
\ingroup native-interfaces
\ingroup native-interfaces-qsgtexture
+ \inheaderfile QSGTexture
\brief Provides access to and enables adopting Direct3D 11 texture objects.
\since 6.0
*/
@@ -837,6 +834,78 @@ void *QSGTexturePlatformD3D11::nativeTexture() const
return reinterpret_cast<void *>(quintptr(tex->nativeTexture().object));
return 0;
}
+
+namespace QNativeInterface {
+/*!
+ \class QNativeInterface::QSGD3D12Texture
+ \inmodule QtQuick
+ \ingroup native-interfaces
+ \ingroup native-interfaces-qsgtexture
+ \inheaderfile QSGTexture
+ \brief Provides access to and enables adopting Direct3D 12 texture objects.
+ \since 6.6
+*/
+
+/*!
+ \fn void *QNativeInterface::QSGD3D12Texture::nativeTexture() const
+ \return the ID3D12Texture object.
+ */
+
+QT_DEFINE_NATIVE_INTERFACE(QSGD3D12Texture);
+
+/*!
+ Creates a new QSGTexture wrapping an existing Direct 3D 12 \a texture object
+ for \a window.
+
+ The native object is wrapped, but not owned, by the resulting QSGTexture.
+ The caller of the function is responsible for deleting the returned
+ QSGTexture, but that will not destroy the underlying native object.
+
+ This function is currently suitable for 2D RGBA textures only.
+
+ \warning This function will return null if the scene graph has not yet been
+ initialized.
+
+ Use \a options to customize the texture attributes. Only the
+ TextureHasAlphaChannel and TextureHasMipmaps are taken into account here.
+
+ \a size specifies the size in pixels.
+
+ \a resourceState must specify the
+ \l{https://learn.microsoft.com/en-us/windows/win32/api/d3d12/ne-d3d12-d3d12_resource_states}{current state}
+ of the texture resource.
+
+ \note This function must be called on the scene graph rendering thread.
+
+ \sa QQuickWindow::sceneGraphInitialized(), QSGTexture,
+ {Scene Graph - Metal Texture Import}, {Scene Graph - Vulkan Texture Import}
+
+ \since 6.6
+ */
+QSGTexture *QSGD3D12Texture::fromNative(void *texture,
+ int resourceState,
+ QQuickWindow *window,
+ const QSize &size,
+ QQuickWindow::CreateTextureOptions options)
+{
+ return QQuickWindowPrivate::get(window)->createTextureFromNativeTexture(quint64(texture), resourceState, size, options);
+}
+} // QNativeInterface
+
+int QSGTexturePlatformD3D12::nativeResourceState() const
+{
+ if (auto *tex = m_texture->rhiTexture())
+ return tex->nativeTexture().layout;
+ return 0;
+}
+
+void *QSGTexturePlatformD3D12::nativeTexture() const
+{
+ if (auto *tex = m_texture->rhiTexture())
+ return reinterpret_cast<void *>(quintptr(tex->nativeTexture().object));
+ return 0;
+}
+
#endif // win
#if defined(__OBJC__) || defined(Q_QDOC)
@@ -846,6 +915,7 @@ namespace QNativeInterface {
\inmodule QtQuick
\ingroup native-interfaces
\ingroup native-interfaces-qsgtexture
+ \inheaderfile QSGTexture
\brief Provides access to and enables adopting Metal texture objects.
\since 6.0
*/
@@ -893,6 +963,7 @@ namespace QNativeInterface {
\inmodule QtQuick
\ingroup native-interfaces
\ingroup native-interfaces-qsgtexture
+ \inheaderfile QSGTexture
\brief Provides access to and enables adopting Vulkan image objects.
\since 6.0
*/
@@ -974,11 +1045,12 @@ void *QSGTexture::resolveInterface(const char *name, int revision) const
#if QT_CONFIG(vulkan)
QT_NATIVE_INTERFACE_RETURN_IF(QSGVulkanTexture, &dd->m_vulkanTextureAccessor);
#endif
-#if defined(__OBJC__)
+#if QT_CONFIG(metal)
QT_NATIVE_INTERFACE_RETURN_IF(QSGMetalTexture, &dd->m_metalTextureAccessor);
#endif
#if defined(Q_OS_WIN)
QT_NATIVE_INTERFACE_RETURN_IF(QSGD3D11Texture, &dd->m_d3d11TextureAccessor);
+ QT_NATIVE_INTERFACE_RETURN_IF(QSGD3D12Texture, &dd->m_d3d12TextureAccessor);
#endif
#if QT_CONFIG(opengl)
QT_NATIVE_INTERFACE_RETURN_IF(QSGOpenGLTexture, &dd->m_openglTextureAccessor);
diff --git a/src/quick/scenegraph/coreapi/qsgtexture_mac.mm b/src/quick/scenegraph/coreapi/qsgtexture_mac.mm
index fb3c9fcf04..c68a1b732f 100644
--- a/src/quick/scenegraph/coreapi/qsgtexture_mac.mm
+++ b/src/quick/scenegraph/coreapi/qsgtexture_mac.mm
@@ -6,7 +6,7 @@
#include <private/qquickitem_p.h>
#include <private/qquickwindow_p.h>
#include <QtCore/private/qnativeinterface_p.h>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
QT_BEGIN_NAMESPACE
diff --git a/src/quick/scenegraph/coreapi/qsgtexture_p.h b/src/quick/scenegraph/coreapi/qsgtexture_p.h
index 8e57a174c6..178bd23265 100644
--- a/src/quick/scenegraph/coreapi/qsgtexture_p.h
+++ b/src/quick/scenegraph/coreapi/qsgtexture_p.h
@@ -39,7 +39,7 @@ bool operator!=(const QSGSamplerDescription &a, const QSGSamplerDescription &b)
size_t qHash(const QSGSamplerDescription &s, size_t seed = 0) noexcept;
#if QT_CONFIG(opengl)
-class Q_QUICK_PRIVATE_EXPORT QSGTexturePlatformOpenGL : public QNativeInterface::QSGOpenGLTexture
+class Q_QUICK_EXPORT QSGTexturePlatformOpenGL : public QNativeInterface::QSGOpenGLTexture
{
public:
QSGTexturePlatformOpenGL(QSGTexture *t) : m_texture(t) { }
@@ -50,7 +50,7 @@ public:
#endif
#ifdef Q_OS_WIN
-class Q_QUICK_PRIVATE_EXPORT QSGTexturePlatformD3D11 : public QNativeInterface::QSGD3D11Texture
+class Q_QUICK_EXPORT QSGTexturePlatformD3D11 : public QNativeInterface::QSGD3D11Texture
{
public:
QSGTexturePlatformD3D11(QSGTexture *t) : m_texture(t) { }
@@ -58,21 +58,30 @@ public:
void *nativeTexture() const override;
};
+class Q_QUICK_EXPORT QSGTexturePlatformD3D12 : public QNativeInterface::QSGD3D12Texture
+{
+public:
+ QSGTexturePlatformD3D12(QSGTexture *t) : m_texture(t) { }
+ QSGTexture *m_texture;
+
+ int nativeResourceState() const override;
+ void *nativeTexture() const override;
+};
#endif
-#if defined(__OBJC__)
-class Q_QUICK_PRIVATE_EXPORT QSGTexturePlatformMetal : public QNativeInterface::QSGMetalTexture
+#if QT_CONFIG(metal)
+class Q_QUICK_EXPORT QSGTexturePlatformMetal : public QNativeInterface::QSGMetalTexture
{
public:
QSGTexturePlatformMetal(QSGTexture *t) : m_texture(t) { }
QSGTexture *m_texture;
- id<MTLTexture> nativeTexture() const override;
+ QT_OBJC_PROTOCOL(MTLTexture) nativeTexture() const override;
};
#endif
#if QT_CONFIG(vulkan)
-class Q_QUICK_PRIVATE_EXPORT QSGTexturePlatformVulkan : public QNativeInterface::QSGVulkanTexture
+class Q_QUICK_EXPORT QSGTexturePlatformVulkan : public QNativeInterface::QSGVulkanTexture
{
public:
QSGTexturePlatformVulkan(QSGTexture *t) : m_texture(t) { }
@@ -83,7 +92,7 @@ public:
};
#endif
-class Q_QUICK_PRIVATE_EXPORT QSGTexturePrivate : public QObjectPrivate
+class Q_QUICK_EXPORT QSGTexturePrivate : public QObjectPrivate
{
Q_DECLARE_PUBLIC(QSGTexture)
public:
@@ -110,8 +119,9 @@ public:
#endif
#ifdef Q_OS_WIN
QSGTexturePlatformD3D11 m_d3d11TextureAccessor;
+ QSGTexturePlatformD3D12 m_d3d12TextureAccessor;
#endif
-#if defined(__OBJC__)
+#if QT_CONFIG(metal)
QSGTexturePlatformMetal m_metalTextureAccessor;
#endif
#if QT_CONFIG(vulkan)
@@ -119,7 +129,7 @@ public:
#endif
};
-Q_QUICK_PRIVATE_EXPORT bool qsg_safeguard_texture(QSGTexture *);
+Q_QUICK_EXPORT bool qsg_safeguard_texture(QSGTexture *);
QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/coreapi/qsgtexture_platform.h b/src/quick/scenegraph/coreapi/qsgtexture_platform.h
index e8aef9078f..63c35c381d 100644
--- a/src/quick/scenegraph/coreapi/qsgtexture_platform.h
+++ b/src/quick/scenegraph/coreapi/qsgtexture_platform.h
@@ -15,8 +15,14 @@
#include <QtGui/qvulkaninstance.h>
#endif
-#if defined(__OBJC__) || defined(Q_QDOC)
-@protocol MTLTexture;
+#if QT_CONFIG(metal) || defined(Q_QDOC)
+# if defined(__OBJC__) || defined(Q_QDOC)
+ @protocol MTLTexture;
+# define QT_OBJC_PROTOCOL(protocol) id<protocol>
+# else
+ typedef struct objc_object *id;
+# define QT_OBJC_PROTOCOL(protocol) id
+# endif
#endif
QT_BEGIN_NAMESPACE
@@ -49,14 +55,25 @@ struct Q_QUICK_EXPORT QSGD3D11Texture
const QSize &size,
QQuickWindow::CreateTextureOptions options = {});
};
+struct Q_QUICK_EXPORT QSGD3D12Texture
+{
+ QT_DECLARE_NATIVE_INTERFACE(QSGD3D12Texture, 1, QSGTexture)
+ virtual void *nativeTexture() const = 0;
+ virtual int nativeResourceState() const = 0;
+ static QSGTexture *fromNative(void *texture,
+ int resourceState,
+ QQuickWindow *window,
+ const QSize &size,
+ QQuickWindow::CreateTextureOptions options = {});
+};
#endif
-#if defined(__OBJC__) || defined(Q_QDOC)
+#if QT_CONFIG(metal) || defined(Q_QDOC)
struct Q_QUICK_EXPORT QSGMetalTexture
{
QT_DECLARE_NATIVE_INTERFACE(QSGMetalTexture, 1, QSGTexture)
- virtual id<MTLTexture> nativeTexture() const = 0;
- static QSGTexture *fromNative(id<MTLTexture> texture,
+ virtual QT_OBJC_PROTOCOL(MTLTexture) nativeTexture() const = 0;
+ static QSGTexture *fromNative(QT_OBJC_PROTOCOL(MTLTexture) texture,
QQuickWindow *window,
const QSize &size,
QQuickWindow::CreateTextureOptions options = {});
diff --git a/src/quick/scenegraph/qsgadaptationlayer.cpp b/src/quick/scenegraph/qsgadaptationlayer.cpp
index c20a126dce..b62919c2e9 100644
--- a/src/quick/scenegraph/qsgadaptationlayer.cpp
+++ b/src/quick/scenegraph/qsgadaptationlayer.cpp
@@ -18,6 +18,13 @@
QT_BEGIN_NAMESPACE
+Q_TRACE_POINT(qtquick, QSGDistanceFieldGlyphCache_update_entry, int count)
+Q_TRACE_POINT(qtquick, QSGDistanceFieldGlyphCache_update_exit)
+Q_TRACE_POINT(qtquick, QSGDistanceFieldGlyphCache_glyphRender_entry)
+Q_TRACE_POINT(qtquick, QSGDistanceFieldGlyphCache_glyphRender_exit)
+Q_TRACE_POINT(qtquick, QSGDistanceFieldGlyphCache_glyphStore_entry)
+Q_TRACE_POINT(qtquick, QSGDistanceFieldGlyphCache_glyphStore_exit)
+
static QElapsedTimer qsg_render_timer;
QSGDistanceFieldGlyphCache::Texture QSGDistanceFieldGlyphCache::s_emptyTexture;
@@ -159,7 +166,12 @@ void QSGDistanceFieldGlyphCache::update()
distanceFields.reserve(pendingGlyphsSize);
for (int i = 0; i < pendingGlyphsSize; ++i) {
GlyphData &gd = glyphData(m_pendingGlyphs.at(i));
- distanceFields.append(QDistanceField(gd.path,
+
+ QSize size = QSize(qCeil(gd.texCoord.width + gd.texCoord.xMargin * 2),
+ qCeil(gd.texCoord.height + gd.texCoord.yMargin * 2));
+
+ distanceFields.append(QDistanceField(size,
+ gd.path,
m_pendingGlyphs.at(i),
m_doubleGlyphResolution));
gd.path = QPainterPath(); // no longer needed, so release memory used by the painter path
@@ -226,16 +238,6 @@ void QSGDistanceFieldGlyphCache::setGlyphsPosition(const QList<GlyphPosition> &g
}
}
-void QSGDistanceFieldGlyphCache::registerOwnerElement(QQuickItem *ownerElement)
-{
- Q_UNUSED(ownerElement);
-}
-
-void QSGDistanceFieldGlyphCache::unregisterOwnerElement(QQuickItem *ownerElement)
-{
- Q_UNUSED(ownerElement);
-}
-
void QSGDistanceFieldGlyphCache::processPendingGlyphs()
{
/* Intentionally empty */
diff --git a/src/quick/scenegraph/qsgadaptationlayer_p.h b/src/quick/scenegraph/qsgadaptationlayer_p.h
index 0e3d32528e..117e810411 100644
--- a/src/quick/scenegraph/qsgadaptationlayer_p.h
+++ b/src/quick/scenegraph/qsgadaptationlayer_p.h
@@ -31,7 +31,7 @@
#include <QtGui/private/qdatabuffer_p.h>
#include <private/qdistancefield_p.h>
#include <private/qintrusivelist_p.h>
-#include <QtGui/private/qshader_p.h>
+#include <rhi/qshader.h>
// ### remove
#include <QtQuick/private/qquicktext_p.h>
@@ -52,7 +52,7 @@ class QSGRenderNode;
class QSGRenderContext;
class QRhiTexture;
-class Q_QUICK_PRIVATE_EXPORT QSGNodeVisitorEx
+class Q_QUICK_EXPORT QSGNodeVisitorEx
{
public:
virtual ~QSGNodeVisitorEx();
@@ -89,7 +89,7 @@ public:
};
-class Q_QUICK_PRIVATE_EXPORT QSGVisitableNode : public QSGGeometryNode
+class Q_QUICK_EXPORT QSGVisitableNode : public QSGGeometryNode
{
public:
QSGVisitableNode() { setFlag(IsVisitableNode); }
@@ -98,7 +98,7 @@ public:
virtual void accept(QSGNodeVisitorEx *) = 0;
};
-class Q_QUICK_PRIVATE_EXPORT QSGInternalRectangleNode : public QSGVisitableNode
+class Q_QUICK_EXPORT QSGInternalRectangleNode : public QSGVisitableNode
{
public:
~QSGInternalRectangleNode() override;
@@ -110,6 +110,10 @@ public:
virtual void setGradientStops(const QGradientStops &stops) = 0;
virtual void setGradientVertical(bool vertical) = 0;
virtual void setRadius(qreal radius) = 0;
+ virtual void setTopLeftRadius(qreal radius) = 0;
+ virtual void setTopRightRadius(qreal radius) = 0;
+ virtual void setBottomLeftRadius(qreal radius) = 0;
+ virtual void setBottomRightRadius(qreal radius) = 0;
virtual void setAntialiasing(bool antialiasing) { Q_UNUSED(antialiasing); }
virtual void setAligned(bool aligned) = 0;
@@ -119,7 +123,7 @@ public:
};
-class Q_QUICK_PRIVATE_EXPORT QSGInternalImageNode : public QSGVisitableNode
+class Q_QUICK_EXPORT QSGInternalImageNode : public QSGVisitableNode
{
public:
~QSGInternalImageNode() override;
@@ -144,7 +148,7 @@ public:
void accept(QSGNodeVisitorEx *visitor) override { if (visitor->visit(this)) visitor->visitChildren(this); visitor->endVisit(this); }
};
-class Q_QUICK_PRIVATE_EXPORT QSGPainterNode : public QSGVisitableNode
+class Q_QUICK_EXPORT QSGPainterNode : public QSGVisitableNode
{
public:
~QSGPainterNode() override;
@@ -205,7 +209,7 @@ protected:
#if QT_CONFIG(quick_sprite)
-class Q_QUICK_PRIVATE_EXPORT QSGSpriteNode : public QSGVisitableNode
+class Q_QUICK_EXPORT QSGSpriteNode : public QSGVisitableNode
{
public:
~QSGSpriteNode() override;
@@ -226,7 +230,7 @@ public:
#endif
-class Q_QUICK_PRIVATE_EXPORT QSGGuiThreadShaderEffectManager : public QObject
+class Q_QUICK_EXPORT QSGGuiThreadShaderEffectManager : public QObject
{
Q_OBJECT
@@ -267,7 +271,6 @@ public:
QShader rhiShader;
Type type;
QVector<Variable> variables;
- uint constantDataSize;
// Vertex inputs are not tracked here as QSGGeometry::AttributeSet
// hardwires that anyways so it is up to the shader to provide
@@ -283,10 +286,10 @@ Q_SIGNALS:
};
#ifndef QT_NO_DEBUG_STREAM
-Q_QUICK_PRIVATE_EXPORT QDebug operator<<(QDebug debug, const QSGGuiThreadShaderEffectManager::ShaderInfo::Variable &v);
+Q_QUICK_EXPORT QDebug operator<<(QDebug debug, const QSGGuiThreadShaderEffectManager::ShaderInfo::Variable &v);
#endif
-class Q_QUICK_PRIVATE_EXPORT QSGShaderEffectNode : public QObject, public QSGVisitableNode
+class Q_QUICK_EXPORT QSGShaderEffectNode : public QObject, public QSGVisitableNode
{
Q_OBJECT
@@ -337,6 +340,7 @@ public:
ShaderSyncData vertex;
ShaderSyncData fragment;
void *materialTypeCacheKey;
+ qint8 viewCount;
};
// Each ShaderEffect item has one node (render thread) and one manager (gui thread).
@@ -353,10 +357,10 @@ Q_SIGNALS:
Q_DECLARE_OPERATORS_FOR_FLAGS(QSGShaderEffectNode::DirtyShaderFlags)
#ifndef QT_NO_DEBUG_STREAM
-Q_QUICK_PRIVATE_EXPORT QDebug operator<<(QDebug debug, const QSGShaderEffectNode::VariableData &vd);
+Q_QUICK_EXPORT QDebug operator<<(QDebug debug, const QSGShaderEffectNode::VariableData &vd);
#endif
-class Q_QUICK_PRIVATE_EXPORT QSGGlyphNode : public QSGVisitableNode
+class Q_QUICK_EXPORT QSGGlyphNode : public QSGVisitableNode
{
public:
enum AntialiasingMode
@@ -384,16 +388,12 @@ public:
virtual void update() = 0;
- void setOwnerElement(QQuickItem *ownerElement) { m_ownerElement = ownerElement; }
- QQuickItem *ownerElement() const { return m_ownerElement; }
-
void accept(QSGNodeVisitorEx *visitor) override { if (visitor->visit(this)) visitor->visitChildren(this); visitor->endVisit(this); }
protected:
QRectF m_bounding_rect;
- QQuickItem *m_ownerElement = nullptr;
};
-class Q_QUICK_PRIVATE_EXPORT QSGDistanceFieldGlyphConsumer
+class Q_QUICK_EXPORT QSGDistanceFieldGlyphConsumer
{
public:
virtual ~QSGDistanceFieldGlyphConsumer();
@@ -403,7 +403,7 @@ public:
};
typedef QIntrusiveList<QSGDistanceFieldGlyphConsumer, &QSGDistanceFieldGlyphConsumer::node> QSGDistanceFieldGlyphConsumerList;
-class Q_QUICK_PRIVATE_EXPORT QSGDistanceFieldGlyphCache
+class Q_QUICK_EXPORT QSGDistanceFieldGlyphCache
{
public:
QSGDistanceFieldGlyphCache(const QRawFont &font,
@@ -468,8 +468,6 @@ public:
void registerGlyphNode(QSGDistanceFieldGlyphConsumer *node) { m_registeredNodes.insert(node); }
void unregisterGlyphNode(QSGDistanceFieldGlyphConsumer *node) { m_registeredNodes.remove(node); }
- virtual void registerOwnerElement(QQuickItem *ownerElement);
- virtual void unregisterOwnerElement(QQuickItem *ownerElement);
virtual void processPendingGlyphs();
virtual bool eightBitFormatIsAlphaSwizzled() const = 0;
diff --git a/src/quick/scenegraph/qsgbasicglyphnode_p.h b/src/quick/scenegraph/qsgbasicglyphnode_p.h
index 5762673ce6..2063a2476c 100644
--- a/src/quick/scenegraph/qsgbasicglyphnode_p.h
+++ b/src/quick/scenegraph/qsgbasicglyphnode_p.h
@@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE
class QSGMaterial;
-class Q_QUICK_PRIVATE_EXPORT QSGBasicGlyphNode: public QSGGlyphNode
+class Q_QUICK_EXPORT QSGBasicGlyphNode: public QSGGlyphNode
{
public:
QSGBasicGlyphNode();
diff --git a/src/quick/scenegraph/qsgbasicinternalimagenode.cpp b/src/quick/scenegraph/qsgbasicinternalimagenode.cpp
index 8eb39b5f06..5c49bdb54a 100644
--- a/src/quick/scenegraph/qsgbasicinternalimagenode.cpp
+++ b/src/quick/scenegraph/qsgbasicinternalimagenode.cpp
@@ -10,13 +10,13 @@ QT_BEGIN_NAMESPACE
namespace
{
- struct SmoothVertex
+ struct SmoothImageVertex
{
float x, y, u, v;
float dx, dy, du, dv;
};
- const QSGGeometry::AttributeSet &smoothAttributeSet()
+ const QSGGeometry::AttributeSet &smoothImageAttributeSet()
{
static QSGGeometry::Attribute data[] = {
QSGGeometry::Attribute::createWithAttributeType(0, 2, QSGGeometry::FloatType, QSGGeometry::PositionAttribute),
@@ -24,7 +24,7 @@ namespace
QSGGeometry::Attribute::createWithAttributeType(2, 2, QSGGeometry::FloatType, QSGGeometry::TexCoord1Attribute),
QSGGeometry::Attribute::createWithAttributeType(3, 2, QSGGeometry::FloatType, QSGGeometry::TexCoord2Attribute)
};
- static QSGGeometry::AttributeSet attrs = { 4, sizeof(SmoothVertex), data };
+ static QSGGeometry::AttributeSet attrs = { 4, sizeof(SmoothImageVertex), data };
return attrs;
}
}
@@ -97,7 +97,7 @@ void QSGBasicInternalImageNode::setAntialiasing(bool antialiasing)
return;
m_antialiasing = antialiasing;
if (m_antialiasing) {
- setGeometry(new QSGGeometry(smoothAttributeSet(), 0));
+ setGeometry(new QSGGeometry(smoothImageAttributeSet(), 0));
setFlag(OwnsGeometry, true);
} else {
setGeometry(&m_geometry);
@@ -308,7 +308,7 @@ QSGGeometry *QSGBasicInternalImageNode::updateGeometry(const QRectF &targetRect,
if (antialiasing) {
if (!geometry || geometry->indexType() != indexType) {
- geometry = new QSGGeometry(smoothAttributeSet(),
+ geometry = new QSGGeometry(smoothImageAttributeSet(),
hCells * vCells * 4 + (hCells + vCells - 1) * 4,
hCells * vCells * 6 + (hCells + vCells) * 12,
indexType);
@@ -320,7 +320,7 @@ QSGGeometry *QSGBasicInternalImageNode::updateGeometry(const QRectF &targetRect,
Q_ASSERT(g);
g->setDrawingMode(QSGGeometry::DrawTriangles);
- SmoothVertex *vertices = reinterpret_cast<SmoothVertex *>(g->vertexData());
+ auto *vertices = reinterpret_cast<SmoothImageVertex *>(g->vertexData());
memset(vertices, 0, g->vertexCount() * g->sizeOfVertex());
void *indexData = g->indexData();
@@ -360,7 +360,7 @@ QSGGeometry *QSGBasicInternalImageNode::updateGeometry(const QRectF &targetRect,
bool isLeft = i == 0;
bool isRight = i == hCells - 1;
- SmoothVertex *v = vertices + index;
+ SmoothImageVertex *v = vertices + index;
int topLeft = index;
for (int k = (isTop || isLeft ? 2 : 1); k--; ++v, ++index) {
@@ -521,12 +521,12 @@ void QSGBasicInternalImageNode::updateGeometry()
QSGGeometry *g = geometry();
Q_ASSERT(g != &m_geometry);
if (g->indexType() != QSGGeometry::UnsignedShortType) {
- setGeometry(new QSGGeometry(smoothAttributeSet(), 0));
+ setGeometry(new QSGGeometry(smoothImageAttributeSet(), 0));
g = geometry();
}
g->allocate(8, 14);
g->setDrawingMode(QSGGeometry::DrawTriangleStrip);
- SmoothVertex *vertices = reinterpret_cast<SmoothVertex *>(g->vertexData());
+ auto *vertices = reinterpret_cast<SmoothImageVertex *>(g->vertexData());
float delta = float(qAbs(m_targetRect.width()) < qAbs(m_targetRect.height())
? m_targetRect.width() : m_targetRect.height()) * 0.5f;
float sx = float(sr.width() / m_targetRect.width());
diff --git a/src/quick/scenegraph/qsgbasicinternalimagenode_p.h b/src/quick/scenegraph/qsgbasicinternalimagenode_p.h
index c4aaba02fd..f5b43091c8 100644
--- a/src/quick/scenegraph/qsgbasicinternalimagenode_p.h
+++ b/src/quick/scenegraph/qsgbasicinternalimagenode_p.h
@@ -19,7 +19,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QSGBasicInternalImageNode : public QSGInternalImageNode
+class Q_QUICK_EXPORT QSGBasicInternalImageNode : public QSGInternalImageNode
{
public:
QSGBasicInternalImageNode();
diff --git a/src/quick/scenegraph/qsgbasicinternalrectanglenode.cpp b/src/quick/scenegraph/qsgbasicinternalrectanglenode.cpp
index 9ce0ceefbc..8d1104a407 100644
--- a/src/quick/scenegraph/qsgbasicinternalrectanglenode.cpp
+++ b/src/quick/scenegraph/qsgbasicinternalrectanglenode.cpp
@@ -74,8 +74,7 @@ namespace
}
QSGBasicInternalRectangleNode::QSGBasicInternalRectangleNode()
- : m_radius(0)
- , m_pen_width(0)
+ : QSGInternalRectangleNode()
, m_aligned(true)
, m_antialiasing(false)
, m_gradient_is_opaque(true)
@@ -155,6 +154,35 @@ void QSGBasicInternalRectangleNode::setRadius(qreal radius)
m_dirty_geometry = true;
}
+void QSGBasicInternalRectangleNode::setTopLeftRadius(qreal radius)
+{
+ if (radius == m_topLeftRadius)
+ return;
+ m_topLeftRadius = radius;
+ m_dirty_geometry = true;
+}
+void QSGBasicInternalRectangleNode::setTopRightRadius(qreal radius)
+{
+ if (radius == m_topRightRadius)
+ return;
+ m_topRightRadius = radius;
+ m_dirty_geometry = true;
+}
+void QSGBasicInternalRectangleNode::setBottomLeftRadius(qreal radius)
+{
+ if (radius == m_bottomLeftRadius)
+ return;
+ m_bottomLeftRadius = radius;
+ m_dirty_geometry = true;
+}
+void QSGBasicInternalRectangleNode::setBottomRightRadius(qreal radius)
+{
+ if (radius == m_bottomRightRadius)
+ return;
+ m_bottomRightRadius = radius;
+ m_dirty_geometry = true;
+}
+
void QSGBasicInternalRectangleNode::setAntialiasing(bool antialiasing)
{
if (!supportsAntialiasing())
@@ -217,33 +245,93 @@ void QSGBasicInternalRectangleNode::updateGeometry()
Color4ub transparent = { 0, 0, 0, 0 };
const QGradientStops &stops = m_gradient_stops;
- float length = (m_gradient_is_vertical ? height : width);
+ float gradientStart = (m_gradient_is_vertical ? m_rect.top() : m_rect.left());
+ float gradientLength = (m_gradient_is_vertical ? height : width);
float secondaryLength = (m_gradient_is_vertical ? width : height);
int nextGradientStop = 0;
- float gradientPos = penWidth / length;
+ float gradientPos = penWidth / gradientLength;
while (nextGradientStop < stops.size() && stops.at(nextGradientStop).first <= gradientPos)
++nextGradientStop;
int lastGradientStop = stops.size() - 1;
- float lastGradientPos = 1.0f - penWidth / length;
+ float lastGradientPos = 1.0f - penWidth / gradientLength;
while (lastGradientStop >= nextGradientStop && stops.at(lastGradientStop).first >= lastGradientPos)
--lastGradientStop;
int gradientIntersections = (lastGradientStop - nextGradientStop + 1);
- if (m_radius > 0) {
+ if (m_radius > 0
+ || m_topLeftRadius > 0
+ || m_topRightRadius > 0
+ || m_bottomLeftRadius > 0
+ || m_bottomRightRadius > 0) {
// Rounded corners.
- // Radius should never exceeds half of the width or half of the height
- float radius = qMin(qMin(width, height) * 0.5f, float(m_radius));
- QRectF innerRect = m_rect;
- innerRect.adjust(radius, radius, -radius, -radius);
-
- float innerRadius = radius - penWidth * 1.0f;
- float outerRadius = radius;
- float delta = qMin(width, height) * 0.5f;
+ // Radius should never exceed half the width or half the height.
+ float radiusTL = qMin(qMin(width, height) * 0.4999f, float(m_topLeftRadius < 0 ? m_radius : m_topLeftRadius));
+ float radiusTR = qMin(qMin(width, height) * 0.4999f, float(m_topRightRadius < 0 ? m_radius : m_topRightRadius));
+ float radiusBL = qMin(qMin(width, height) * 0.4999f, float(m_bottomLeftRadius < 0 ? m_radius : m_bottomLeftRadius));
+ float radiusBR = qMin(qMin(width, height) * 0.4999f, float(m_bottomRightRadius < 0 ? m_radius : m_bottomRightRadius));
+
+ // The code produces some artefacts when radius <= 0.5. A radius of half a pixel
+ // does not make much sense anyway, so we draw a normal corner in such a case.
+ if (radiusTL <= 0.5)
+ radiusTL = 0;
+ if (radiusTR <= 0.5)
+ radiusTR = 0;
+ if (radiusBL <= 0.5)
+ radiusBL = 0;
+ if (radiusBR <= 0.5)
+ radiusBR = 0;
+
+ // We want to keep a minimal inner radius in order to make the inner
+ // x-coordinates of an arc mathematically unique and identifiable.
+ const float innerRadiusTL = qMax(radiusTL - penWidth * 1.0f, 0.01);
+ const float innerRadiusTR = qMax(radiusTR - penWidth * 1.0f, 0.01);
+ const float innerRadiusBL = qMax(radiusBL - penWidth * 1.0f, 0.01);
+ const float innerRadiusBR = qMax(radiusBR - penWidth * 1.0f, 0.01);
+ const float outerRadiusTL = radiusTL;
+ const float outerRadiusTR = radiusTR;
+ const float outerRadiusBL = radiusBL;
+ const float outerRadiusBR = radiusBR;
+ const float delta = qMin(width, height) * 0.5f;
+
+ int segmentsTL = radiusTL == 0 ? 0 : qBound(3, qCeil(radiusTL * (M_PI / 6)), 18);
+ int segmentsTR = radiusTR == 0 ? 0 : qBound(3, qCeil(radiusTR * (M_PI / 6)), 18);
+ int segmentsBL = radiusBL == 0 ? 0 : qBound(3, qCeil(radiusBL * (M_PI / 6)), 18);
+ int segmentsBR = radiusBR == 0 ? 0 : qBound(3, qCeil(radiusBR * (M_PI / 6)), 18);
+
+ // If the radii on opposite sites in genraration direction are the same,
+ // we will set the segments of one side to 0 as these points would be
+ // calculated twice. Also, this optimizes for the case of similar radii
+ if (m_gradient_is_vertical) {
+ if (innerRadiusTL == innerRadiusTR) {
+ if (segmentsTL <= segmentsTR)
+ segmentsTL = 0;
+ else
+ segmentsTR = 0;
+ }
+ if (innerRadiusBL == innerRadiusBR){
+ if (segmentsBL <= segmentsBR)
+ segmentsBL = 0;
+ else
+ segmentsBR = 0;
+ }
+ } else {
+ if (innerRadiusTL == innerRadiusBL) {
+ if (segmentsTL <= segmentsBL)
+ segmentsTL = 0;
+ else
+ segmentsBL = 0;
+ }
+ if (innerRadiusTR == innerRadiusBR) {
+ if (segmentsTR <= segmentsBR)
+ segmentsTR = 0;
+ else
+ segmentsBR = 0;
+ }
+ }
- // Number of segments per corner, approximately one per 3 pixels.
- int segments = qBound(3, qCeil(outerRadius * (M_PI / 6)), 18);
+ const int sumSegments = segmentsTL + segmentsTR + segmentsBL + segmentsBR;
/*
@@ -265,8 +353,8 @@ void QSGBasicInternalRectangleNode::updateGeometry()
*/
- int innerVertexCount = (segments + 1) * 4 + gradientIntersections * 2;
- int outerVertexCount = (segments + 1) * 4;
+ const int innerVertexCount = (sumSegments + 4) * 2 + gradientIntersections * 2;
+ const int outerVertexCount = (sumSegments + 4) * 2;
int vertexCount = innerVertexCount;
if (m_antialiasing || penWidth)
vertexCount += innerVertexCount;
@@ -275,10 +363,11 @@ void QSGBasicInternalRectangleNode::updateGeometry()
if (m_antialiasing && penWidth)
vertexCount += outerVertexCount;
- int fillIndexCount = innerVertexCount;
- int innerAAIndexCount = innerVertexCount * 2 + 2;
- int borderIndexCount = innerVertexCount * 2 + 2;
- int outerAAIndexCount = outerVertexCount * 2 + 2;
+
+ const int fillIndexCount = innerVertexCount;
+ const int innerAAIndexCount = innerVertexCount * 2 + 2;
+ const int borderIndexCount = innerVertexCount * 2 + 2;
+ const int outerAAIndexCount = outerVertexCount * 2 + 2;
int indexCount = 0;
int fillHead = 0;
int innerAAHead = 0;
@@ -309,48 +398,322 @@ void QSGBasicInternalRectangleNode::updateGeometry()
quint16 *indices = g->indexDataAsUShort();
quint16 index = 0;
- float pp = 0; // previous inner primary coordinate.
- float pss = 0; // previous inner secondary start coordinate.
- float pse = 0; // previous inner secondary end coordinate.
- float angle = 0.5f * float(M_PI) / segments;
- float cosStep = qFastCos(angle);
- float sinStep = qFastSin(angle);
-
- float innerStart = (m_gradient_is_vertical ? innerRect.top() : innerRect.left());
- float innerEnd = (m_gradient_is_vertical ? innerRect.bottom() : innerRect.right());
- float innerLength = (m_gradient_is_vertical ? innerRect.height() : innerRect.width());
- float innerSecondaryStart = (m_gradient_is_vertical ? innerRect.left() : innerRect.top());
- float innerSecondaryEnd = (m_gradient_is_vertical ? innerRect.right() : innerRect.bottom());
+ float innerXPrev = 0.; // previous inner primary coordinate, both sides.
+ float innerYLeftPrev = 0.; // previous inner secondary coordinate, left.
+ float innerYRightPrev = 0.; // previous inner secondary coordinate, right.
+
+ const float angleTL = 0.5f * float(M_PI) / segmentsTL;
+ const float cosStepTL = qFastCos(angleTL);
+ const float sinStepTL = qFastSin(angleTL);
+ const float angleTR = 0.5f * float(M_PI) / segmentsTR;
+ const float cosStepTR = qFastCos(angleTR);
+ const float sinStepTR = qFastSin(angleTR);
+ const float angleBL = 0.5f * float(M_PI) / segmentsBL;
+ const float cosStepBL = qFastCos(angleBL);
+ const float sinStepBL = qFastSin(angleBL);
+ const float angleBR = 0.5f * float(M_PI) / segmentsBR;
+ const float cosStepBR = qFastCos(angleBR);
+ const float sinStepBR = qFastSin(angleBR);
+
+ //The x- and y-Axis are transposed, depending on gradient being vertical or horizontal
+ //Lets define some coordinates and radii. The first index is the part, the second index
+ //is the left or right side, as seen when moving from part0 to part1
+
+ // left vertices | right vertices
+ // |
+ // *************|**************
+ // * | | | *
+ // *--o | o--*
+ // * innerX/Y | innerX/Y *
+ // * | *
+ // * | * part 0
+ // * | *
+ // * | *
+ // * | *
+ // -----------------+--------------------> y
+ // * | *
+ // * | *
+ // * | *
+ // * | * part 1
+ // * | *
+ // * innerX/Y | innerX/Y *
+ // *--o | o--*
+ // * | | | *
+ // *************|**************
+ // |
+ // v x
+ //
+ // direction of vertex generation
+
+ const float outerXCenter[][2] = {{
+ float(m_gradient_is_vertical ? m_rect.top() + radiusTL : m_rect.left() + radiusTL),
+ float(m_gradient_is_vertical ? m_rect.top() + radiusTR : m_rect.left() + radiusBL)
+ }, {
+ float(m_gradient_is_vertical ? m_rect.bottom() - radiusBL : m_rect.right() - radiusTR),
+ float(m_gradient_is_vertical ? m_rect.bottom() - radiusBR : m_rect.right() - radiusBR)
+ }};
+
+ const float outerYCenter[][2] = {{
+ float(!m_gradient_is_vertical ? m_rect.top() + outerRadiusTL : m_rect.left() + outerRadiusTL),
+ float(!m_gradient_is_vertical ? m_rect.bottom() - outerRadiusBL : m_rect.right() - outerRadiusTR)
+ }, {
+ float(!m_gradient_is_vertical ? m_rect.top() + outerRadiusTR : m_rect.left() + outerRadiusBL),
+ float(!m_gradient_is_vertical ? m_rect.bottom() - outerRadiusBR : m_rect.right() - outerRadiusBR)
+ }};
+
+ const float innerXCenter[][2] = { {
+ float(m_gradient_is_vertical ? m_rect.top() + innerRadiusTL + penWidth : m_rect.left() + innerRadiusTL + penWidth),
+ float(m_gradient_is_vertical ? m_rect.top() + innerRadiusTR + penWidth: m_rect.left() + innerRadiusBL + penWidth)
+ }, {
+ float(m_gradient_is_vertical ? m_rect.bottom() - innerRadiusBL - penWidth: m_rect.right() - innerRadiusTR - penWidth),
+ float(m_gradient_is_vertical ? m_rect.bottom() - innerRadiusBR - penWidth: m_rect.right() - innerRadiusBR - penWidth)
+ }};
+
+ const float innerYCenter[][2] = { {
+ float(!m_gradient_is_vertical ? m_rect.top() + innerRadiusTL + penWidth : m_rect.left() + innerRadiusTL + penWidth),
+ float(!m_gradient_is_vertical ? m_rect.bottom() - innerRadiusBL - penWidth : m_rect.right() - innerRadiusTR - penWidth)
+ },{
+ float(!m_gradient_is_vertical ? m_rect.top() + innerRadiusTR + penWidth : m_rect.left() + innerRadiusBL + penWidth),
+ float(!m_gradient_is_vertical ? m_rect.bottom() - innerRadiusBR - penWidth : m_rect.right() - innerRadiusBR - penWidth)
+ }};
+
+ const float innerRadius[][2] = {{
+ innerRadiusTL,
+ !m_gradient_is_vertical ? innerRadiusBL : innerRadiusTR
+ }, {
+ !m_gradient_is_vertical ? innerRadiusTR : innerRadiusBL,
+ innerRadiusBR
+ }};
+
+ const float outerRadius[][2] = {{
+ outerRadiusTL,
+ !m_gradient_is_vertical ? outerRadiusBL : outerRadiusTR
+ }, {
+ !m_gradient_is_vertical ? outerRadiusTR : outerRadiusBL,
+ outerRadiusBR
+ }};
+
+ const int segments[][2] = {{
+ segmentsTL,
+ !m_gradient_is_vertical ? segmentsBL : segmentsTR
+ }, {
+ !m_gradient_is_vertical ? segmentsTR : segmentsBL,
+ segmentsBR
+ }};
+
+ const float cosStep[][2] = {{
+ cosStepTL,
+ !m_gradient_is_vertical ? cosStepBL : cosStepTR
+ }, {
+ !m_gradient_is_vertical ? cosStepTR : cosStepBL,
+ cosStepBR
+ }};
+
+ const float sinStep[][2] = {{
+ sinStepTL,
+ !m_gradient_is_vertical ? sinStepBL : sinStepTR
+ }, {
+ !m_gradient_is_vertical ? sinStepTR : sinStepBL,
+ sinStepBR
+ }};
+
+ auto fillColorFromX = [&](float x) {
+
+ float t = (x - gradientStart) / gradientLength;
+ t = qBound(0.0, t, 1.0);
+
+ int i = 1;
+ if (t < stops.first().first)
+ return colorToColor4ub(stops.first().second);
+ while (i < stops.size()) {
+ const QGradientStop &prev = stops.at(i - 1);
+ const QGradientStop &next = stops.at(i);
+ if (prev.first <= t && next.first > t) {
+ t = (t - prev.first) / (next.first - prev.first);
+ return colorToColor4ub(prev.second) * (1. - t) + colorToColor4ub(next.second) * t; }
+ i++;
+ }
+ return colorToColor4ub(stops.last().second);
+ };
for (int part = 0; part < 2; ++part) {
- float c = 1 - part;
- float s = part;
- for (int i = 0; i <= segments; ++i) {
- float p, ss, se;
- if (innerRadius > 0) {
- p = (part ? innerEnd : innerStart) - innerRadius * c; // current inner primary coordinate.
- ss = innerSecondaryStart - innerRadius * s; // current inner secondary start coordinate.
- se = innerSecondaryEnd + innerRadius * s; // current inner secondary end coordinate.
- gradientPos = ((part ? innerLength : 0) + radius - innerRadius * c) / length;
+ // cosine of the angle of the current segment, starting at 1 for part 0 and 0 for part 1
+ float cosSegmentAngleLeft = 1. - part;
+ // sine of the angle of the current segment
+ float sinSegmentAngleLeft = part;
+
+ float cosSegmentAngleRight = 1. - part;
+ float sinSegmentAngleRight = part;
+
+ bool advanceLeft = true;
+
+ // We draw both the left part and the right part of the rectangle at the same time.
+ // We also draw a vertex on the left side for every vertex on the right side. This
+ // syncronisation is required to make sure that all gradient stops can be inserted.
+ for (int iLeft = 0, iRight = 0; iLeft <= segments[part][0] || iRight <= segments[part][1]; ) {
+
+ float xLeft, yLeft,
+ xRight, yRight;
+
+ float outerXLeft, outerYLeft,
+ outerXRight, outerYRight;
+
+ float sinAngleLeft, cosAngleLeft,
+ sinAngleRight, cosAngleRight;
+
+ // calculate inner x-coordinates
+ xLeft = innerXCenter[part][0] - innerRadius[part][0] * cosSegmentAngleLeft;
+ xRight = innerXCenter[part][1] - innerRadius[part][1] * cosSegmentAngleRight;
+
+ // calcuate inner y-coordinates
+ yLeft = innerYCenter[part][0] - innerRadius[part][0] * sinSegmentAngleLeft;
+ yRight = innerYCenter[part][1] + innerRadius[part][1] * sinSegmentAngleRight;
+
+ // Synchronize left and right hand x-coordinates. This is required to
+ // make sure that we can insert all gradient stops that require exactly two triangles at
+ // every x-coordinate. Take the smaller of both x-coordinates and then find the matching
+ // y-coordinates.
+ if ((iLeft <= segments[part][0] && xLeft <= xRight) || iRight > segments[part][1]) {
+ advanceLeft = true;
} else {
- p = (part ? innerEnd + innerRadius : innerStart - innerRadius); // current inner primary coordinate.
- ss = innerSecondaryStart - innerRadius; // current inner secondary start coordinate.
- se = innerSecondaryEnd + innerRadius; // current inner secondary end coordinate.
- gradientPos = ((part ? innerLength + innerRadius : -innerRadius) + radius) / length;
+ advanceLeft = false;
}
- float outerEdge = (part ? innerEnd : innerStart) - outerRadius * c; // current outer primary coordinate.
- float outerSecondaryStart = innerSecondaryStart - outerRadius * s; // current outer secondary start coordinate.
- float outerSecondaryEnd = innerSecondaryEnd + outerRadius * s; // current outer secondary end coordinate.
+ // Inner: Find the matching y-coordinates for the x-coordinate found above.
+ // Outer: Also set the sine and cosine to make sure that outer vertices are
+ // drawn correctly.
+ if (innerRadius[part][0] == innerRadius[part][1]) {
+ // Special case of equal radii. Optimize to avoid performance regression:
+ // Left and right is always equal and we can just copy the angles and
+ // mirror the coordinates.
+ if (advanceLeft) {
+ if (outerRadius[part][0] == 0) {
+ sinAngleLeft = 1.;
+ cosAngleLeft = part ? -1. : 1.;
+ } else {
+ sinAngleLeft = sinSegmentAngleLeft;
+ cosAngleLeft = cosSegmentAngleLeft;
+ }
+ if (outerRadius[part][1] == 0) {
+ sinAngleRight = 1.;
+ cosAngleRight = part ? -1. : 1.;
+ } else {
+ sinAngleRight = sinSegmentAngleLeft;
+ cosAngleRight = cosSegmentAngleLeft;
+ }
+ xRight = xLeft;
+ yRight = innerYCenter[part][1] + innerRadius[part][1] * sinAngleRight;
+ } else {
+ if (outerRadius[part][0] == 0) {
+ sinAngleLeft = 1.;
+ cosAngleLeft = part ? -1. : 1.;
+ } else {
+ sinAngleLeft = sinSegmentAngleRight;
+ cosAngleLeft = cosSegmentAngleRight;
+ }
+ if (outerRadius[part][1] == 0) {
+ sinAngleRight = 1.;
+ cosAngleRight = part ? -1. : 1.;
+ } else {
+ sinAngleRight = sinSegmentAngleRight;
+ cosAngleRight = cosSegmentAngleRight;
+ }
+ xLeft = xRight;
+ yLeft = innerYCenter[part][0] - innerRadius[part][0] * sinAngleLeft;
+ }
+ } else if (advanceLeft) {
+ if (outerRadius[part][0] == 0) {
+ sinAngleLeft = 1.;
+ cosAngleLeft = part ? -1. : 1.;
+ } else {
+ sinAngleLeft = sinSegmentAngleLeft;
+ cosAngleLeft = cosSegmentAngleLeft;
+ }
+ if (outerRadius[part][1] == 0) {
+ // Outer: If the outer radius is zero we can return both sin and cos = 1
+ // to form a nice corner. Inner: Accept the x-coordinate from the other
+ // side and match the y-coordinate
+ sinAngleRight = 1.;
+ cosAngleRight = part ? -1. : 1.;
+ xRight = xLeft;
+ yRight = innerYCenter[part][1] + innerRadius[part][1] * sinAngleRight;
+ } else if (xLeft >= innerXCenter[0][1] && xLeft <= innerXCenter[1][1]) {
+ // Outer: If we are on the straight line between the inner centers, we can
+ // just return sin = 1 and cos = 0. Inner: Accept the x-coordinate from the
+ // other side and match the y-coordinate
+ sinAngleRight = 1.;
+ cosAngleRight = 0.;
+ xRight = xLeft;
+ yRight = innerYCenter[part][1] + innerRadius[part][1] * sinAngleRight;
+ } else {
+ // Inner: If we are on the rounded part of the oposite side, we have to find a vertex
+ // on that curve that matches the x-coordinate we selected.
+ // We always select the smaller x-coordinate and can therefore use a linear
+ // interpolation between the last point on this side and the point on this side
+ // that was not accepted because it was too large.
+ if (xRight != innerXPrev) {
+ float t = (xLeft - innerXPrev) / (xRight - innerXPrev);
+ yRight = innerYRightPrev * (1. - t) + yRight * t;
+ xRight = xLeft;
+ }
+ // Outer: With the coordinates from the interpolation we can calculate the sine
+ // and cosine of the respective angle quickly.
+ sinAngleRight = (yRight - innerYCenter[part][1]) / innerRadius[part][1];
+ cosAngleRight = -(xRight - innerXCenter[part][1]) / innerRadius[part][1];
+ }
+ } else {
+ // same as above but for the other side.
+ if (outerRadius[part][1] == 0) {
+ sinAngleRight = 1.;
+ cosAngleRight = part ? -1. : 1.;
+ } else {
+ sinAngleRight = sinSegmentAngleRight;
+ cosAngleRight = cosSegmentAngleRight;
+ }
+ if (outerRadius[part][0] == 0) {
+ sinAngleLeft = 1.;
+ cosAngleLeft = part ? -1. : 1.;
+ xLeft = xRight;
+ yLeft = innerYCenter[part][0] - innerRadius[part][0] * sinAngleLeft;
+ } else if (xRight >= innerXCenter[0][0] && xRight <= innerXCenter[1][0]) {
+ sinAngleLeft = 1.;
+ cosAngleLeft = 0.;
+ xLeft = xRight;
+ yLeft = innerYCenter[part][0] - innerRadius[part][0] * sinAngleLeft;
+ } else {
+ if (xLeft != innerXPrev) {
+ float t = (xRight - innerXPrev) / (xLeft - innerXPrev);
+ yLeft = innerYLeftPrev * (1. - t) + yLeft * t;
+ xLeft = xRight;
+ }
+ sinAngleLeft = -(yLeft - innerYCenter[part][0]) / innerRadius[part][0];
+ cosAngleLeft = -(xLeft - innerXCenter[part][0]) / innerRadius[part][0];
+ }
+ }
+
+ gradientPos = (xLeft - gradientStart) / gradientLength;
+
+ // calculate the matching outer coordinates
+ outerXLeft = outerXCenter[part][0] - outerRadius[part][0] * cosAngleLeft;
+ outerYLeft = outerYCenter[part][0] - outerRadius[part][0] * sinAngleLeft;
+ outerXRight = outerXCenter[part][1] - outerRadius[part][1] * cosAngleRight;
+ outerYRight = outerYCenter[part][1] + outerRadius[part][1] * sinAngleRight;
+
+ // insert gradient stops as required
while (nextGradientStop <= lastGradientStop && stops.at(nextGradientStop).first <= gradientPos) {
- // Insert vertices at gradient stops.
- float gp = (innerStart - radius) + stops.at(nextGradientStop).first * length;
- float t = (gp - pp) / (p - pp);
- float gis = pss * (1 - t) + t * ss; // gradient inner start
- float gie = pse * (1 - t) + t * se; // gradient inner end
+ float gradientX;
+ float gradientYLeft;
+ float gradientYRight;
+
+ // Insert vertices at gradient stops
+ gradientX = gradientStart + stops.at(nextGradientStop).first * gradientLength;
+ // bilinear interpolation of known vertices
+ float t = (gradientX - innerXPrev) / (xLeft - innerXPrev);
+ gradientYLeft = innerYLeftPrev * (1. - t) + t * yLeft;
+ gradientYRight = innerYRightPrev * (1. - t) + t * yRight;
- fillColor = colorToColor4ub(stops.at(nextGradientStop).second);
+ fillColor = fillColorFromX(gradientX);
if (hasFill) {
indices[fillHead++] = index;
@@ -373,39 +736,35 @@ void QSGBasicInternalRectangleNode::updateGeometry()
indices[innerAATail++] = index + 3;
bool lower = stops.at(nextGradientStop).first > 0.5f;
- float dp = lower ? qMin(0.0f, length - gp - delta) : qMax(0.0f, delta - gp);
- smoothVertices[index++].set(gp, gie, fillColor, dp, secondaryLength - gie - delta, m_gradient_is_vertical);
- smoothVertices[index++].set(gp, gis, fillColor, dp, delta - gis, m_gradient_is_vertical);
+ float dp = lower ? qMin(0.0f, gradientLength - gradientX - delta) : qMax(0.0f, delta - gradientX);
+ smoothVertices[index++].set(gradientX, gradientYRight, fillColor, dp, secondaryLength - gradientYRight - delta, m_gradient_is_vertical);
+ smoothVertices[index++].set(gradientX, gradientYLeft, fillColor, dp, delta - gradientYLeft, m_gradient_is_vertical);
if (penWidth) {
- smoothVertices[index++].set(gp, gie, borderColor, -0.49f * penWidth * c, 0.49f * penWidth * s, m_gradient_is_vertical);
- smoothVertices[index++].set(gp, gis, borderColor, -0.49f * penWidth * c, -0.49f * penWidth * s, m_gradient_is_vertical);
+ smoothVertices[index++].set(gradientX, gradientYRight, borderColor, -0.49f * penWidth * cosAngleRight, 0.49f * penWidth * sinAngleRight, m_gradient_is_vertical);
+ smoothVertices[index++].set(gradientX, gradientYLeft, borderColor, -0.49f * penWidth * cosAngleLeft, -0.49f * penWidth * sinAngleLeft, m_gradient_is_vertical);
} else {
dp = lower ? delta : -delta;
- smoothVertices[index++].set(gp, gie, transparent, dp, delta, m_gradient_is_vertical);
- smoothVertices[index++].set(gp, gis, transparent, dp, -delta, m_gradient_is_vertical);
+ smoothVertices[index++].set(gradientX, gradientYRight, transparent, dp, delta, m_gradient_is_vertical);
+ smoothVertices[index++].set(gradientX, gradientYLeft, transparent, dp, -delta, m_gradient_is_vertical);
}
} else {
- vertices[index++].set(gp, gie, fillColor, m_gradient_is_vertical);
- vertices[index++].set(gp, gis, fillColor, m_gradient_is_vertical);
+ vertices[index++].set(gradientX, gradientYRight, fillColor, m_gradient_is_vertical);
+ vertices[index++].set(gradientX, gradientYLeft, fillColor, m_gradient_is_vertical);
if (penWidth) {
- vertices[index++].set(gp, gie, borderColor, m_gradient_is_vertical);
- vertices[index++].set(gp, gis, borderColor, m_gradient_is_vertical);
+ vertices[index++].set(gradientX, gradientYRight, borderColor, m_gradient_is_vertical);
+ vertices[index++].set(gradientX, gradientYLeft, borderColor, m_gradient_is_vertical);
}
}
- ++nextGradientStop;
+
+ innerXPrev = gradientX;
+ innerYLeftPrev = gradientYLeft;
+ innerYRightPrev = gradientYRight;
+
+ nextGradientStop++;
}
if (!stops.isEmpty()) {
- if (nextGradientStop == 0) {
- fillColor = colorToColor4ub(stops.at(0).second);
- } else if (nextGradientStop == stops.size()) {
- fillColor = colorToColor4ub(stops.last().second);
- } else {
- const QGradientStop &prev = stops.at(nextGradientStop - 1);
- const QGradientStop &next = stops.at(nextGradientStop);
- float t = (gradientPos - prev.first) / (next.first - prev.first);
- fillColor = colorToColor4ub(prev.second) * (1 - t) + colorToColor4ub(next.second) * t;
- }
+ fillColor = fillColorFromX(xLeft);
}
if (hasFill) {
@@ -426,48 +785,57 @@ void QSGBasicInternalRectangleNode::updateGeometry()
indices[innerAATail++] = index + 1;
indices[innerAATail++] = index + 3;
- float dp = part ? qMin(0.0f, length - p - delta) : qMax(0.0f, delta - p);
- smoothVertices[index++].set(p, se, fillColor, dp, secondaryLength - se - delta, m_gradient_is_vertical);
- smoothVertices[index++].set(p, ss, fillColor, dp, delta - ss, m_gradient_is_vertical);
+ float dp = part ? qMin(0.0f, gradientLength - xRight - delta) : qMax(0.0f, delta - xRight);
+ smoothVertices[index++].set(xRight, yRight, fillColor, dp, secondaryLength - yRight - delta, m_gradient_is_vertical);
+ smoothVertices[index++].set(xLeft, yLeft, fillColor, dp, delta - yLeft, m_gradient_is_vertical);
dp = part ? delta : -delta;
if (penWidth) {
- smoothVertices[index++].set(p, se, borderColor, -0.49f * penWidth * c, 0.49f * penWidth * s, m_gradient_is_vertical);
- smoothVertices[index++].set(p, ss, borderColor, -0.49f * penWidth * c, -0.49f * penWidth * s, m_gradient_is_vertical);
- smoothVertices[index++].set(outerEdge, outerSecondaryEnd, borderColor, 0.49f * penWidth * c, -0.49f * penWidth * s, m_gradient_is_vertical);
- smoothVertices[index++].set(outerEdge, outerSecondaryStart, borderColor, 0.49f * penWidth * c, 0.49f * penWidth * s, m_gradient_is_vertical);
- smoothVertices[index++].set(outerEdge, outerSecondaryEnd, transparent, dp, delta, m_gradient_is_vertical);
- smoothVertices[index++].set(outerEdge, outerSecondaryStart, transparent, dp, -delta, m_gradient_is_vertical);
+ smoothVertices[index++].set(xRight, yRight, borderColor, -0.49f * penWidth * cosAngleRight, 0.49f * penWidth * sinAngleRight, m_gradient_is_vertical);
+ smoothVertices[index++].set(xLeft, yLeft, borderColor, -0.49f * penWidth * cosAngleLeft, -0.49f * penWidth * sinAngleLeft, m_gradient_is_vertical);
+ smoothVertices[index++].set(outerXRight, outerYRight, borderColor, 0.49f * penWidth * cosAngleRight, -0.49f * penWidth * sinAngleRight, m_gradient_is_vertical);
+ smoothVertices[index++].set(outerXLeft, outerYLeft, borderColor, 0.49f * penWidth * cosAngleLeft, 0.49f * penWidth * sinAngleLeft, m_gradient_is_vertical);
+ smoothVertices[index++].set(outerXRight, outerYRight, transparent, dp, delta, m_gradient_is_vertical);
+ smoothVertices[index++].set(outerXLeft, outerYLeft, transparent, dp, -delta, m_gradient_is_vertical);
indices[--outerAAHead] = index - 2;
indices[--outerAAHead] = index - 4;
indices[outerAATail++] = index - 3;
indices[outerAATail++] = index - 1;
} else {
- smoothVertices[index++].set(p, se, transparent, dp, delta, m_gradient_is_vertical);
- smoothVertices[index++].set(p, ss, transparent, dp, -delta, m_gradient_is_vertical);
+ smoothVertices[index++].set(xRight, yRight, transparent, dp, delta, m_gradient_is_vertical);
+ smoothVertices[index++].set(xLeft, yLeft, transparent, dp, -delta, m_gradient_is_vertical);
}
} else {
- vertices[index++].set(p, se, fillColor, m_gradient_is_vertical);
- vertices[index++].set(p, ss, fillColor, m_gradient_is_vertical);
+ vertices[index++].set(xRight, yRight, fillColor, m_gradient_is_vertical);
+ vertices[index++].set(xLeft, yLeft, fillColor, m_gradient_is_vertical);
if (penWidth) {
- vertices[index++].set(p, se, borderColor, m_gradient_is_vertical);
- vertices[index++].set(p, ss, borderColor, m_gradient_is_vertical);
- vertices[index++].set(outerEdge, outerSecondaryEnd, borderColor, m_gradient_is_vertical);
- vertices[index++].set(outerEdge, outerSecondaryStart, borderColor, m_gradient_is_vertical);
+ vertices[index++].set(xRight, yRight, borderColor, m_gradient_is_vertical);
+ vertices[index++].set(xLeft, yLeft, borderColor, m_gradient_is_vertical);
+ vertices[index++].set(outerXRight, outerYRight, borderColor, m_gradient_is_vertical);
+ vertices[index++].set(outerXLeft, outerYLeft, borderColor, m_gradient_is_vertical);
}
}
- pp = p;
- pss = ss;
- pse = se;
+ innerXPrev = xLeft;
+ innerYLeftPrev = yLeft;
+ innerYRightPrev = yRight;
- // Rotate
- qreal tmp = c;
- c = c * cosStep - s * sinStep;
- s = s * cosStep + tmp * sinStep;
+ // Advance the point. This corresponds to a rotation of the respective segment
+ if (advanceLeft) {
+ iLeft++;
+ qreal tmp = cosSegmentAngleLeft;
+ cosSegmentAngleLeft = cosSegmentAngleLeft * cosStep[part][0] - sinSegmentAngleLeft * sinStep[part][0];
+ sinSegmentAngleLeft = sinSegmentAngleLeft * cosStep[part][0] + tmp * sinStep[part][0];
+ } else {
+ iRight++;
+ qreal tmp = cosSegmentAngleRight;
+ cosSegmentAngleRight = cosSegmentAngleRight * cosStep[part][1] - sinSegmentAngleRight * sinStep[part][1];
+ sinSegmentAngleRight = sinSegmentAngleRight * cosStep[part][1] + tmp * sinStep[part][1];
+ }
}
}
+
Q_ASSERT(index == vertexCount);
// Close the triangle strips.
@@ -552,11 +920,11 @@ void QSGBasicInternalRectangleNode::updateGeometry()
for (int part = -1; part <= 1; part += 2) {
float innerEdge = (part == 1 ? innerEnd : innerStart);
float outerEdge = (part == 1 ? outerEnd : outerStart);
- gradientPos = (innerEdge - innerStart + penWidth) / length;
+ gradientPos = (innerEdge - innerStart + penWidth) / gradientLength;
while (nextGradientStop <= lastGradientStop && stops.at(nextGradientStop).first <= gradientPos) {
// Insert vertices at gradient stops.
- float gp = (innerStart - penWidth) + stops.at(nextGradientStop).first * length;
+ float gp = (innerStart - penWidth) + stops.at(nextGradientStop).first * gradientLength;
fillColor = colorToColor4ub(stops.at(nextGradientStop).second);
@@ -581,7 +949,7 @@ void QSGBasicInternalRectangleNode::updateGeometry()
indices[innerAATail++] = index + 3;
bool lower = stops.at(nextGradientStop).first > 0.5f;
- float dp = lower ? qMin(0.0f, length - gp - delta) : qMax(0.0f, delta - gp);
+ float dp = lower ? qMin(0.0f, gradientLength - gp - delta) : qMax(0.0f, delta - gp);
smoothVertices[index++].set(gp, innerSecondaryEnd, fillColor, dp, secondaryLength - innerSecondaryEnd - delta, m_gradient_is_vertical);
smoothVertices[index++].set(gp, innerSecondaryStart, fillColor, dp, delta - innerSecondaryStart, m_gradient_is_vertical);
if (penWidth) {
@@ -633,7 +1001,7 @@ void QSGBasicInternalRectangleNode::updateGeometry()
indices[innerAATail++] = index + 1;
indices[innerAATail++] = index + 3;
- float dp = part == 1 ? qMin(0.0f, length - innerEdge - delta) : qMax(0.0f, delta - innerEdge);
+ float dp = part == 1 ? qMin(0.0f, gradientLength - innerEdge - delta) : qMax(0.0f, delta - innerEdge);
smoothVertices[index++].set(innerEdge, innerSecondaryEnd, fillColor, dp, secondaryLength - innerSecondaryEnd - delta, m_gradient_is_vertical);
smoothVertices[index++].set(innerEdge, innerSecondaryStart, fillColor, dp, delta - innerSecondaryStart, m_gradient_is_vertical);
diff --git a/src/quick/scenegraph/qsgbasicinternalrectanglenode_p.h b/src/quick/scenegraph/qsgbasicinternalrectanglenode_p.h
index 808d1535e4..b806904f07 100644
--- a/src/quick/scenegraph/qsgbasicinternalrectanglenode_p.h
+++ b/src/quick/scenegraph/qsgbasicinternalrectanglenode_p.h
@@ -20,7 +20,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QSGBasicInternalRectangleNode : public QSGInternalRectangleNode
+class Q_QUICK_EXPORT QSGBasicInternalRectangleNode : public QSGInternalRectangleNode
{
public:
QSGBasicInternalRectangleNode();
@@ -32,6 +32,10 @@ public:
void setGradientStops(const QGradientStops &stops) override;
void setGradientVertical(bool vertical) override;
void setRadius(qreal radius) override;
+ void setTopLeftRadius(qreal radius) override;
+ void setTopRightRadius(qreal radius) override;
+ void setBottomLeftRadius(qreal radius) override;
+ void setBottomRightRadius(qreal radius) override;
void setAntialiasing(bool antialiasing) override;
void setAligned(bool aligned) override;
void update() override;
@@ -48,8 +52,12 @@ protected:
QGradientStops m_gradient_stops;
QColor m_color;
QColor m_border_color;
- qreal m_radius;
- qreal m_pen_width;
+ float m_radius = 0.0f;
+ float m_topLeftRadius = -1.0f;
+ float m_topRightRadius = -1.0f;
+ float m_bottomLeftRadius = -1.0f;
+ float m_bottomRightRadius = -1.0f;
+ float m_pen_width = 0.0f;
uint m_aligned : 1;
uint m_antialiasing : 1;
diff --git a/src/quick/scenegraph/qsgcontext.cpp b/src/quick/scenegraph/qsgcontext.cpp
index f56e9898c6..01e0707556 100644
--- a/src/quick/scenegraph/qsgcontext.cpp
+++ b/src/quick/scenegraph/qsgcontext.cpp
@@ -4,8 +4,9 @@
#include <QtQuick/private/qsgcontext_p.h>
#include <QtQuick/private/qsgtexture_p.h>
#include <QtQuick/private/qsgrenderer_p.h>
-#include <QtQuick/private/qquickpixmapcache_p.h>
+#include <QtQuick/private/qquickpixmap_p.h>
#include <QtQuick/private/qsgadaptationlayer_p.h>
+#include <QtQuick/private/qsginternaltextnode_p.h>
#include <QGuiApplication>
#include <QScreen>
@@ -63,6 +64,13 @@ Q_LOGGING_CATEGORY(QSG_LOG_TIME_GLYPH, "qt.scenegraph.time.glyph")
// Timing inside the renderer base class
Q_LOGGING_CATEGORY(QSG_LOG_TIME_RENDERER, "qt.scenegraph.time.renderer")
+// Leak checks
+Q_LOGGING_CATEGORY(lcQsgLeak, "qt.scenegraph.leaks")
+
+// Applicable for render loops that install their own animation driver, such as
+// the 'threaded' loop. This env.var. is documented in the scenegraph docs.
+DEFINE_BOOL_CONFIG_OPTION(useElapsedTimerBasedAnimationDriver, QSG_USE_SIMPLE_ANIMATION_DRIVER);
+
bool qsg_useConsistentTiming()
{
int use = -1;
@@ -76,21 +84,9 @@ bool qsg_useConsistentTiming()
class QSGAnimationDriver : public QAnimationDriver
{
- Q_OBJECT
public:
- enum Mode {
- VSyncMode,
- TimerMode
- };
-
QSGAnimationDriver(QObject *parent)
: QAnimationDriver(parent)
- , m_time(0)
- , m_vsync(0)
- , m_mode(VSyncMode)
- , m_lag(0)
- , m_bad(0)
- , m_good(0)
{
QScreen *screen = QGuiApplication::primaryScreen();
if (screen) {
@@ -103,6 +99,35 @@ public:
} else {
m_vsync = 16.67f;
}
+ }
+
+ float vsyncInterval() const { return m_vsync; }
+
+ virtual bool isVSyncDependent() const = 0;
+
+protected:
+ float m_vsync = 0;
+};
+
+// default as in default for the threaded render loop
+class QSGDefaultAnimationDriver : public QSGAnimationDriver
+{
+ Q_OBJECT
+public:
+ enum Mode {
+ VSyncMode,
+ TimerMode
+ };
+
+ QSGDefaultAnimationDriver(QObject *parent)
+ : QSGAnimationDriver(parent)
+ , m_time(0)
+ , m_mode(VSyncMode)
+ , m_lag(0)
+ , m_bad(0)
+ , m_good(0)
+ {
+ QScreen *screen = QGuiApplication::primaryScreen();
if (screen && !qsg_useConsistentTiming()) {
if (m_vsync <= 0)
m_mode = TimerMode;
@@ -192,10 +217,12 @@ public:
advanceAnimation();
}
- float vsyncInterval() const { return m_vsync; } // this should always return something sane, regardless of m_mode
+ bool isVSyncDependent() const override
+ {
+ return true;
+ }
double m_time;
- float m_vsync;
Mode m_mode;
QElapsedTimer m_timer;
QElapsedTimer m_wallTime;
@@ -204,6 +231,81 @@ public:
int m_good;
};
+// Advance based on QElapsedTimer. (so like the TimerMode of QSGDefaultAnimationDriver)
+// Does not depend on vsync-based throttling.
+//
+// NB this is not the same as not installing a QAnimationDriver: the built-in
+// approach in QtCore is to rely on 16 ms timer events which are potentially a
+// lot less accurate.
+//
+// This has the benefits of:
+// - not needing any of the infrastructure for falling back to a
+// QTimer when there are multiple windows,
+// - needing no heuristics trying determine if vsync-based throttling
+// is missing or broken,
+// - being compatible with any kind of temporal drifts in vsync throttling
+// which is reportedly happening in various environments and platforms
+// still,
+// - not being tied to the primary screen's refresh rate, i.e. this is
+// correct even if the window is on some secondary screen with a
+// different refresh rate,
+// - not having to worry about the potential effects of variable refresh
+// rate solutions,
+// - render thread animators work correctly regardless of vsync.
+//
+// On the downside, some animations might appear less smooth (compared to the
+// ideal single window case of QSGDefaultAnimationDriver).
+//
+class QSGElapsedTimerAnimationDriver : public QSGAnimationDriver
+{
+public:
+ QSGElapsedTimerAnimationDriver(QObject *parent)
+ : QSGAnimationDriver(parent)
+ {
+ qCDebug(QSG_LOG_INFO, "Animation Driver: using QElapsedTimer, thread %p %s",
+ QThread::currentThread(),
+ QThread::currentThread() == qGuiApp->thread() ? "(gui/main thread)" : "(render thread)");
+ }
+
+ void start() override
+ {
+ m_wallTime.restart();
+ QAnimationDriver::start();
+ }
+
+ qint64 elapsed() const override
+ {
+ return m_wallTime.elapsed();
+ }
+
+ void advance() override
+ {
+ advanceAnimation();
+ }
+
+ bool isVSyncDependent() const override
+ {
+ return false;
+ }
+
+private:
+ QElapsedTimer m_wallTime;
+};
+
+QSGRenderContext::FontKey::FontKey(const QRawFont &font, int quality)
+{
+ QFontEngine *fe = QRawFontPrivate::get(font)->fontEngine;
+ if (fe != nullptr)
+ faceId = fe->faceId();
+ style = font.style();
+ weight = font.weight();
+ renderTypeQuality = quality;
+ if (faceId.filename.isEmpty()) {
+ familyName = font.familyName();
+ styleName = font.styleName();
+ }
+}
+
/*!
\class QSGContext
@@ -245,6 +347,16 @@ QSGInternalRectangleNode *QSGContext::createInternalRectangleNode(const QRectF &
return node;
}
+QSGInternalTextNode *QSGContext::createInternalTextNode(QSGRenderContext *renderContext)
+{
+ return new QSGInternalTextNode(renderContext);
+}
+
+QSGTextNode *QSGContext::createTextNode(QSGRenderContext *renderContext)
+{
+ return createInternalTextNode(renderContext);
+}
+
/*!
Creates a new shader effect helper instance. This function is called on the
GUI thread, unlike the others. This is necessary in order to provide
@@ -270,7 +382,10 @@ QSGShaderEffectNode *QSGContext::createShaderEffectNode(QSGRenderContext *)
*/
QAnimationDriver *QSGContext::createAnimationDriver(QObject *parent)
{
- return new QSGAnimationDriver(parent);
+ if (useElapsedTimerBasedAnimationDriver())
+ return new QSGElapsedTimerAnimationDriver(parent);
+
+ return new QSGDefaultAnimationDriver(parent);
}
/*!
@@ -282,6 +397,14 @@ float QSGContext::vsyncIntervalForAnimationDriver(QAnimationDriver *driver)
return static_cast<QSGAnimationDriver *>(driver)->vsyncInterval();
}
+/*!
+ \return true if \a driver relies on vsync-based throttling in some form.
+ */
+bool QSGContext::isVSyncDependent(QAnimationDriver *driver)
+{
+ return static_cast<QSGAnimationDriver *>(driver)->isVSyncDependent();
+}
+
QSize QSGContext::minimumFBOSize() const
{
return QSize(1, 1);
@@ -365,6 +488,16 @@ void QSGRenderContext::preprocess()
}
/*!
+ Factory function for curve atlases that can be used to provide geometry for the curve
+ renderer for a given font.
+*/
+QSGCurveGlyphAtlas *QSGRenderContext::curveGlyphAtlas(const QRawFont &font)
+{
+ Q_UNUSED(font);
+ return nullptr;
+}
+
+/*!
Factory function for scene graph backends of the distance-field glyph cache.
*/
QSGDistanceFieldGlyphCache *QSGRenderContext::distanceFieldGlyphCache(const QRawFont &, int)
@@ -380,7 +513,13 @@ void QSGRenderContext::invalidateGlyphCaches()
void QSGRenderContext::registerFontengineForCleanup(QFontEngine *engine)
{
engine->ref.ref();
- m_fontEnginesToClean << engine;
+ m_fontEnginesToClean[engine]++;
+}
+
+void QSGRenderContext::unregisterFontengineForCleanup(QFontEngine *engine)
+{
+ m_fontEnginesToClean[engine]--;
+ Q_ASSERT(m_fontEnginesToClean.value(engine) >= 0);
}
QRhi *QSGRenderContext::rhi() const
diff --git a/src/quick/scenegraph/qsgcontext_p.h b/src/quick/scenegraph/qsgcontext_p.h
index 742b3b1448..fc2e6ef1ad 100644
--- a/src/quick/scenegraph/qsgcontext_p.h
+++ b/src/quick/scenegraph/qsgcontext_p.h
@@ -24,15 +24,22 @@
#include <private/qtquickglobal_p.h>
#include <private/qrawfont_p.h>
+#include <private/qfontengine_p.h>
#include <QtQuick/qsgnode.h>
#include <QtQuick/qsgrendererinterface.h>
+#include <QtQuick/qsgtextnode.h>
+
+#include <QtCore/qpointer.h>
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcQsgLeak)
+
class QSGContextPrivate;
class QSGInternalRectangleNode;
class QSGInternalImageNode;
+class QSGInternalTextNode;
class QSGPainterNode;
class QSGGlyphNode;
class QSGRenderer;
@@ -50,6 +57,7 @@ class QSGRendererInterface;
class QSGShaderEffectNode;
class QSGGuiThreadShaderEffectManager;
class QSGRectangleNode;
+class QSGTextNode;
class QSGImageNode;
class QSGNinePatchNode;
class QSGSpriteNode;
@@ -60,6 +68,8 @@ class QRhiRenderTarget;
class QRhiRenderPassDescriptor;
class QRhiCommandBuffer;
class QQuickGraphicsConfiguration;
+class QQuickItem;
+class QSGCurveGlyphAtlas;
Q_DECLARE_LOGGING_CATEGORY(QSG_LOG_TIME_RENDERLOOP)
Q_DECLARE_LOGGING_CATEGORY(QSG_LOG_TIME_COMPILATION)
@@ -70,7 +80,7 @@ Q_DECLARE_LOGGING_CATEGORY(QSG_LOG_TIME_RENDERER)
Q_DECLARE_LOGGING_CATEGORY(QSG_LOG_INFO)
Q_DECLARE_LOGGING_CATEGORY(QSG_LOG_RENDERLOOP)
-class Q_QUICK_PRIVATE_EXPORT QSGContext : public QObject
+class Q_QUICK_EXPORT QSGContext : public QObject
{
Q_OBJECT
@@ -91,8 +101,9 @@ public:
QSGInternalRectangleNode *createInternalRectangleNode(const QRectF &rect, const QColor &c);
virtual QSGInternalRectangleNode *createInternalRectangleNode() = 0;
virtual QSGInternalImageNode *createInternalImageNode(QSGRenderContext *renderContext) = 0;
+ virtual QSGInternalTextNode *createInternalTextNode(QSGRenderContext *renderContext);
virtual QSGPainterNode *createPainterNode(QQuickPaintedItem *item) = 0;
- virtual QSGGlyphNode *createGlyphNode(QSGRenderContext *rc, bool preferNativeGlyphNode, int renderTypeQuality) = 0;
+ virtual QSGGlyphNode *createGlyphNode(QSGRenderContext *rc, QSGTextNode::RenderType renderType, int renderTypeQuality) = 0;
virtual QSGLayer *createLayer(QSGRenderContext *renderContext) = 0;
virtual QSGGuiThreadShaderEffectManager *createGuiThreadShaderEffectManager();
virtual QSGShaderEffectNode *createShaderEffectNode(QSGRenderContext *renderContext);
@@ -101,12 +112,14 @@ public:
#endif
virtual QAnimationDriver *createAnimationDriver(QObject *parent);
virtual float vsyncIntervalForAnimationDriver(QAnimationDriver *driver);
+ virtual bool isVSyncDependent(QAnimationDriver *driver);
virtual QSize minimumFBOSize() const;
virtual QSurfaceFormat defaultSurfaceFormat() const = 0;
virtual QSGRendererInterface *rendererInterface(QSGRenderContext *renderContext);
+ virtual QSGTextNode *createTextNode(QSGRenderContext *renderContext);
virtual QSGRectangleNode *createRectangleNode() = 0;
virtual QSGImageNode *createImageNode() = 0;
virtual QSGNinePatchNode *createNinePatchNode() = 0;
@@ -119,7 +132,7 @@ public:
static QString backend();
};
-class Q_QUICK_PRIVATE_EXPORT QSGRenderContext : public QObject
+class Q_QUICK_EXPORT QSGRenderContext : public QObject
{
Q_OBJECT
public:
@@ -157,6 +170,7 @@ public:
virtual void preprocess();
virtual void invalidateGlyphCaches();
virtual QSGDistanceFieldGlyphCache *distanceFieldGlyphCache(const QRawFont &font, int renderTypeQuality);
+ virtual QSGCurveGlyphAtlas *curveGlyphAtlas(const QRawFont &font);
QSGTexture *textureForFactory(QQuickTextureFactory *factory, QQuickWindow *window);
virtual QSGTexture *createTexture(const QImage &image, uint flags = CreateTexture_Alpha) const = 0;
@@ -165,6 +179,7 @@ public:
virtual int maxTextureSize() const = 0;
+ void unregisterFontengineForCleanup(QFontEngine *engine);
void registerFontengineForCleanup(QFontEngine *engine);
virtual QRhi *rhi() const;
@@ -178,17 +193,48 @@ public Q_SLOTS:
void textureFactoryDestroyed(QObject *o);
protected:
+ struct FontKey {
+ FontKey(const QRawFont &font, int renderTypeQuality);
+
+ QFontEngine::FaceId faceId;
+ QFont::Style style;
+ int weight;
+ int renderTypeQuality;
+ QString familyName;
+ QString styleName;
+ };
+ friend bool operator==(const QSGRenderContext::FontKey &f1, const QSGRenderContext::FontKey &f2);
+ friend size_t qHash(const QSGRenderContext::FontKey &f, size_t seed);
+
// Hold m_sg with QPointer in the rare case it gets deleted before us.
QPointer<QSGContext> m_sg;
QMutex m_mutex;
QHash<QObject *, QSGTexture *> m_textures;
QSet<QSGTexture *> m_texturesToDelete;
- QHash<QString, QSGDistanceFieldGlyphCache *> m_glyphCaches;
+ QHash<FontKey, QSGDistanceFieldGlyphCache *> m_glyphCaches;
- QSet<QFontEngine *> m_fontEnginesToClean;
+ // References to font engines that are currently in use by native rendering glyph nodes
+ // and which must be kept alive as long as they are used in the render thread.
+ QHash<QFontEngine *, int> m_fontEnginesToClean;
};
+inline bool operator ==(const QSGRenderContext::FontKey &f1, const QSGRenderContext::FontKey &f2)
+{
+ return f1.faceId == f2.faceId
+ && f1.style == f2.style
+ && f1.weight == f2.weight
+ && f1.renderTypeQuality == f2.renderTypeQuality
+ && f1.familyName == f2.familyName
+ && f1.styleName == f2.styleName;
+}
+
+inline size_t qHash(const QSGRenderContext::FontKey &f, size_t seed = 0)
+{
+ return qHashMulti(seed, f.faceId, f.renderTypeQuality, f.familyName, f.styleName, f.style, f.weight);
+}
+
+
QT_END_NAMESPACE
#endif // QSGCONTEXT_H
diff --git a/src/quick/scenegraph/qsgcontextplugin.cpp b/src/quick/scenegraph/qsgcontextplugin.cpp
index e21f2fbb6f..9dbf263765 100644
--- a/src/quick/scenegraph/qsgcontextplugin.cpp
+++ b/src/quick/scenegraph/qsgcontextplugin.cpp
@@ -16,8 +16,6 @@
QT_BEGIN_NAMESPACE
-Q_DECLARE_LOGGING_CATEGORY(QSG_LOG_INFO)
-
QSGContextPlugin::QSGContextPlugin(QObject *parent)
: QObject(parent)
{
@@ -103,7 +101,7 @@ QSGAdaptationBackendData *contextFactory()
// caused by run time hocus pocus. If one wants to use the software backend
// in a GL or Vulkan capable Qt build (or on Windows or Apple platforms), it
// has to be requested explicitly.
-#if !QT_CONFIG(opengl) && !QT_CONFIG(vulkan) && !defined(Q_OS_WIN) && !defined(Q_OS_MACOS) && !defined(Q_OS_IOS)
+#if !QT_CONFIG(opengl) && !QT_CONFIG(vulkan) && !QT_CONFIG(metal) && !defined(Q_OS_WIN)
if (requestedBackend.isEmpty())
requestedBackend = QLatin1String("software");
#endif
diff --git a/src/quick/scenegraph/qsgcontextplugin_p.h b/src/quick/scenegraph/qsgcontextplugin_p.h
index 853641b509..966cf0b978 100644
--- a/src/quick/scenegraph/qsgcontextplugin_p.h
+++ b/src/quick/scenegraph/qsgcontextplugin_p.h
@@ -26,7 +26,7 @@ class QSGContext;
class QSGRenderLoop;
-struct Q_QUICK_PRIVATE_EXPORT QSGContextFactoryInterface : public QFactoryInterface
+struct Q_QUICK_EXPORT QSGContextFactoryInterface : public QFactoryInterface
{
enum Flag {
SupportsShaderEffectNode = 0x01
@@ -46,7 +46,7 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(QSGContextFactoryInterface::Flags)
"org.qt-project.Qt.QSGContextFactoryInterface"
Q_DECLARE_INTERFACE(QSGContextFactoryInterface, QSGContextFactoryInterface_iid)
-class Q_QUICK_PRIVATE_EXPORT QSGContextPlugin : public QObject, public QSGContextFactoryInterface
+class Q_QUICK_EXPORT QSGContextPlugin : public QObject, public QSGContextFactoryInterface
{
Q_OBJECT
Q_INTERFACES(QSGContextFactoryInterface:QFactoryInterface)
diff --git a/src/quick/scenegraph/qsgcurveabstractnode_p.h b/src/quick/scenegraph/qsgcurveabstractnode_p.h
new file mode 100644
index 0000000000..aadc17d3f0
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurveabstractnode_p.h
@@ -0,0 +1,32 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGCURVEABSTRACTNODE_P_H
+#define QSGCURVEABSTRACTNODE_P_H
+
+#include <QtGui/qcolor.h>
+#include <QtQuick/qsgnode.h>
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+QT_BEGIN_NAMESPACE
+
+class QSGCurveAbstractNode : public QSGGeometryNode
+{
+public:
+ virtual void setColor(QColor col) = 0;
+ virtual void cookGeometry() = 0;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGCURVEABSTRACTNODE_P_H
diff --git a/src/quick/scenegraph/qsgcurvefillnode.cpp b/src/quick/scenegraph/qsgcurvefillnode.cpp
new file mode 100644
index 0000000000..0a4a42341e
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurvefillnode.cpp
@@ -0,0 +1,61 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsgcurvefillnode_p.h"
+#include "qsgcurvefillnode_p_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QSGCurveFillNode::QSGCurveFillNode()
+{
+ setFlag(OwnsGeometry, true);
+ setFlag(UsePreprocess, true);
+ setGeometry(new QSGGeometry(attributes(), 0, 0));
+
+ updateMaterial();
+}
+
+void QSGCurveFillNode::updateMaterial()
+{
+ m_material.reset(new QSGCurveFillMaterial(this));
+ setMaterial(m_material.data());
+}
+
+void QSGCurveFillNode::cookGeometry()
+{
+ QSGGeometry *g = geometry();
+ if (g->indexType() != QSGGeometry::UnsignedIntType) {
+ g = new QSGGeometry(attributes(),
+ m_uncookedVertexes.size(),
+ m_uncookedIndexes.size(),
+ QSGGeometry::UnsignedIntType);
+ setGeometry(g);
+ } else {
+ g->allocate(m_uncookedVertexes.size(), m_uncookedIndexes.size());
+ }
+
+ g->setDrawingMode(QSGGeometry::DrawTriangles);
+ memcpy(g->vertexData(),
+ m_uncookedVertexes.constData(),
+ g->vertexCount() * g->sizeOfVertex());
+ memcpy(g->indexData(),
+ m_uncookedIndexes.constData(),
+ g->indexCount() * g->sizeOfIndex());
+
+ m_uncookedIndexes.clear();
+ m_uncookedVertexes.clear();
+}
+
+const QSGGeometry::AttributeSet &QSGCurveFillNode::attributes()
+{
+ static QSGGeometry::Attribute data[] = {
+ QSGGeometry::Attribute::createWithAttributeType(0, 2, QSGGeometry::FloatType, QSGGeometry::PositionAttribute),
+ QSGGeometry::Attribute::createWithAttributeType(1, 3, QSGGeometry::FloatType, QSGGeometry::TexCoordAttribute),
+ QSGGeometry::Attribute::createWithAttributeType(2, 4, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute),
+ QSGGeometry::Attribute::createWithAttributeType(3, 2, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute),
+ };
+ static QSGGeometry::AttributeSet attrs = { 4, sizeof(CurveNodeVertex), data };
+ return attrs;
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgcurvefillnode_p.cpp b/src/quick/scenegraph/qsgcurvefillnode_p.cpp
new file mode 100644
index 0000000000..331620cf94
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurvefillnode_p.cpp
@@ -0,0 +1,396 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsgcurvefillnode_p_p.h"
+#include "qsgcurvefillnode_p.h"
+#include "util/qsggradientcache_p.h"
+
+#include <private/qsgtexture_p.h>
+#include <private/qsgplaintexture_p.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace {
+
+ class QSGCurveFillMaterialShader : public QSGMaterialShader
+ {
+ public:
+ QSGCurveFillMaterialShader(QGradient::Type gradientType,
+ bool useTextureFill,
+ bool useDerivatives,
+ int viewCount);
+
+ bool updateUniformData(RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) override;
+ void updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
+ QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
+ };
+
+ QSGCurveFillMaterialShader::QSGCurveFillMaterialShader(QGradient::Type gradientType,
+ bool useTextureFill,
+ bool useDerivatives,
+ int viewCount)
+ {
+ QString baseName = QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shapecurve");
+
+ if (gradientType == QGradient::LinearGradient) {
+ baseName += QStringLiteral("_lg");
+ } else if (gradientType == QGradient::RadialGradient) {
+ baseName += QStringLiteral("_rg");
+ } else if (gradientType == QGradient::ConicalGradient) {
+ baseName += QStringLiteral("_cg");
+ } else if (useTextureFill) {
+ baseName += QStringLiteral("_tf");
+ }
+
+ if (useDerivatives)
+ baseName += QStringLiteral("_derivatives");
+
+ setShaderFileName(VertexStage, baseName + QStringLiteral(".vert.qsb"), viewCount);
+ setShaderFileName(FragmentStage, baseName + QStringLiteral(".frag.qsb"), viewCount);
+ }
+
+ void QSGCurveFillMaterialShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
+ QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
+ {
+ Q_UNUSED(oldMaterial);
+ QSGCurveFillMaterial *m = static_cast<QSGCurveFillMaterial *>(newMaterial);
+ const QSGCurveFillNode *node = m->node();
+ if (binding != 1
+ || (node->gradientType() == QGradient::NoGradient && node->fillTextureProvider() == nullptr)) {
+ return;
+ }
+
+ QSGTexture *t = nullptr;
+ if (node->gradientType() != QGradient::NoGradient) {
+ const QSGGradientCacheKey cacheKey(node->fillGradient()->stops,
+ node->fillGradient()->spread);
+ t = QSGGradientCache::cacheForRhi(state.rhi())->get(cacheKey);
+ } else if (node->fillTextureProvider() != nullptr) {
+ t = node->fillTextureProvider()->texture();
+ if (t != nullptr && t->isAtlasTexture()) {
+ // Create a non-atlas copy to make texture coordinate wrapping work. This
+ // texture copy is owned by the QSGTexture so memory is managed with the original
+ // texture provider.
+ QSGTexture *newTexture = t->removedFromAtlas(state.resourceUpdateBatch());
+ if (newTexture != nullptr)
+ t = newTexture;
+ }
+
+ }
+
+ if (t != nullptr) {
+ t->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
+ } else {
+ if (m->dummyTexture() == nullptr) {
+ QSGPlainTexture *dummyTexture = new QSGPlainTexture;
+ dummyTexture->setFiltering(QSGTexture::Nearest);
+ dummyTexture->setHorizontalWrapMode(QSGTexture::Repeat);
+ dummyTexture->setVerticalWrapMode(QSGTexture::Repeat);
+ QImage img(128, 128, QImage::Format_ARGB32_Premultiplied);
+ img.fill(0);
+ dummyTexture->setImage(img);
+ dummyTexture->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
+
+ m->setDummyTexture(dummyTexture);
+ }
+
+ t = m->dummyTexture();
+ }
+
+ *texture = t;
+ }
+
+ bool QSGCurveFillMaterialShader::updateUniformData(RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect)
+ {
+ bool changed = false;
+ QByteArray *buf = state.uniformData();
+ Q_ASSERT(buf->size() >= 80);
+ const int matrixCount = qMin(state.projectionMatrixCount(), newEffect->viewCount());
+
+ int offset = 0;
+ float matrixScale = 0.0f;
+ if (state.isMatrixDirty()) {
+ for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
+ const QMatrix4x4 m = state.combinedMatrix(viewIndex);
+ memcpy(buf->data() + offset + viewIndex * 64, m.constData(), 64);
+ }
+
+ matrixScale = qSqrt(qAbs(state.determinant()));
+ memcpy(buf->data() + offset + newEffect->viewCount() * 64, &matrixScale, 4);
+
+ changed = true;
+ }
+ offset += newEffect->viewCount() * 64 + 4;
+
+ if (state.isOpacityDirty()) {
+ const float opacity = state.opacity();
+ memcpy(buf->data() + offset, &opacity, 4);
+ changed = true;
+ }
+ offset += 4;
+
+ QSGCurveFillMaterial *newMaterial = static_cast<QSGCurveFillMaterial *>(newEffect);
+ QSGCurveFillMaterial *oldMaterial = static_cast<QSGCurveFillMaterial *>(oldEffect);
+
+ QSGCurveFillNode *newNode = newMaterial != nullptr ? newMaterial->node() : nullptr;
+ QSGCurveFillNode *oldNode = oldMaterial != nullptr ? oldMaterial->node() : nullptr;
+
+ if (newNode == nullptr)
+ return changed;
+
+ if (oldNode == nullptr || oldNode->debug() != newNode->debug()) {
+ float debug = newNode->debug();
+ memcpy(buf->data() + offset, &debug, 4);
+ changed = true;
+ }
+ offset += 8;
+
+ if (newNode->gradientType() == QGradient::NoGradient
+ && newNode->fillTextureProvider() == nullptr) {
+ Q_ASSERT(buf->size() >= offset + 16);
+
+ QVector4D newColor = QVector4D(newNode->color().redF(),
+ newNode->color().greenF(),
+ newNode->color().blueF(),
+ newNode->color().alphaF());
+ QVector4D oldColor = oldNode != nullptr
+ ? QVector4D(oldNode->color().redF(),
+ oldNode->color().greenF(),
+ oldNode->color().blueF(),
+ oldNode->color().alphaF())
+ : QVector4D{};
+
+ if (oldNode == nullptr || oldColor != newColor) {
+ memcpy(buf->data() + offset, &newColor, 16);
+ changed = true;
+ }
+
+ offset += 16;
+ } else {
+ Q_ASSERT(buf->size() >= offset + 64);
+
+ if (!oldNode || *oldNode->fillTransform() != *newNode->fillTransform()) {
+ memcpy(buf->data() + offset, newNode->fillTransform()->invertedData(), 64);
+ changed = true;
+ }
+
+ offset += 64;
+ }
+
+ if (newNode->gradientType() == QGradient::NoGradient
+ && newNode->fillTextureProvider() != nullptr) {
+ Q_ASSERT(buf->size() >= offset + 8);
+ const QSizeF newTextureSize = newNode->fillTextureProvider()->texture() != nullptr
+ ? newNode->fillTextureProvider()->texture()->textureSize()
+ : QSizeF(0, 0);
+ const QVector2D newBoundsSize(newTextureSize.width() / state.devicePixelRatio(),
+ newTextureSize.height() / state.devicePixelRatio());
+ const QVector2D oldBoundsSize = oldNode != nullptr
+ ? oldNode->boundsSize()
+ : QVector2D{};
+
+ if (oldEffect == nullptr || newBoundsSize != oldBoundsSize) {
+ newNode->setBoundsSize(newBoundsSize);
+ memcpy(buf->data() + offset, &newBoundsSize, 8);
+ changed = true;
+ }
+ offset += 8;
+
+ } else if (newNode->gradientType() == QGradient::LinearGradient) {
+ Q_ASSERT(buf->size() >= offset + 8 + 8);
+
+ QVector2D newGradientStart = QVector2D(newNode->fillGradient()->a);
+ QVector2D oldGradientStart = oldNode != nullptr
+ ? QVector2D(oldNode->fillGradient()->a)
+ : QVector2D{};
+
+ if (newGradientStart != oldGradientStart || oldEffect == nullptr) {
+ memcpy(buf->data() + offset, &newGradientStart, 8);
+ changed = true;
+ }
+ offset += 8;
+
+ QVector2D newGradientEnd = QVector2D(newNode->fillGradient()->b);
+ QVector2D oldGradientEnd = oldNode!= nullptr
+ ? QVector2D(oldNode->fillGradient()->b)
+ : QVector2D{};
+
+ if (newGradientEnd != oldGradientEnd || oldEffect == nullptr) {
+ memcpy(buf->data() + offset, &newGradientEnd, 8);
+ changed = true;
+ }
+
+ offset += 8;
+ } else if (newNode->gradientType() == QGradient::RadialGradient) {
+ Q_ASSERT(buf->size() >= offset + 8 + 8 + 4 + 4);
+
+ QVector2D newFocalPoint = QVector2D(newNode->fillGradient()->b);
+ QVector2D oldFocalPoint = oldNode != nullptr
+ ? QVector2D(oldNode->fillGradient()->b)
+ : QVector2D{};
+ if (oldNode == nullptr || newFocalPoint != oldFocalPoint) {
+ memcpy(buf->data() + offset, &newFocalPoint, 8);
+ changed = true;
+ }
+ offset += 8;
+
+ QVector2D newCenterPoint = QVector2D(newNode->fillGradient()->a);
+ QVector2D oldCenterPoint = oldNode != nullptr
+ ? QVector2D(oldNode->fillGradient()->a)
+ : QVector2D{};
+
+ QVector2D newCenterToFocal = newCenterPoint - newFocalPoint;
+ QVector2D oldCenterToFocal = oldCenterPoint - oldFocalPoint;
+ if (oldNode == nullptr || newCenterToFocal != oldCenterToFocal) {
+ memcpy(buf->data() + offset, &newCenterToFocal, 8);
+ changed = true;
+ }
+ offset += 8;
+
+ float newCenterRadius = newNode->fillGradient()->v0;
+ float oldCenterRadius = oldNode != nullptr
+ ? oldNode->fillGradient()->v0
+ : 0.0f;
+ if (oldNode == nullptr || !qFuzzyCompare(newCenterRadius, oldCenterRadius)) {
+ memcpy(buf->data() + offset, &newCenterRadius, 4);
+ changed = true;
+ }
+ offset += 4;
+
+ float newFocalRadius = newNode->fillGradient()->v1;
+ float oldFocalRadius = oldNode != nullptr
+ ? oldNode->fillGradient()->v1
+ : 0.0f;
+ if (oldNode == nullptr || !qFuzzyCompare(newFocalRadius, oldFocalRadius)) {
+ memcpy(buf->data() + offset, &newFocalRadius, 4);
+ changed = true;
+ }
+ offset += 4;
+
+ } else if (newNode->gradientType() == QGradient::ConicalGradient) {
+ Q_ASSERT(buf->size() >= offset + 8 + 4);
+
+ QVector2D newFocalPoint = QVector2D(newNode->fillGradient()->a);
+ QVector2D oldFocalPoint = oldNode != nullptr
+ ? QVector2D(oldNode->fillGradient()->a)
+ : QVector2D{};
+ if (oldNode == nullptr || newFocalPoint != oldFocalPoint) {
+ memcpy(buf->data() + offset, &newFocalPoint, 8);
+ changed = true;
+ }
+ offset += 8;
+
+ float newAngle = newNode->fillGradient()->v0;
+ float oldAngle = oldNode != nullptr
+ ? oldNode->fillGradient()->v0
+ : 0.0f;
+ if (oldNode == nullptr || !qFuzzyCompare(newAngle, oldAngle)) {
+ newAngle = -qDegreesToRadians(newAngle);
+ memcpy(buf->data() + offset, &newAngle, 4);
+ changed = true;
+ }
+ offset += 4;
+ }
+
+ return changed;
+ }
+
+}
+
+QSGCurveFillMaterial::QSGCurveFillMaterial(QSGCurveFillNode *node)
+ : m_node(node)
+{
+ setFlag(Blending, true);
+ setFlag(RequiresDeterminant, true);
+}
+
+QSGCurveFillMaterial::~QSGCurveFillMaterial()
+{
+ delete m_dummyTexture;
+}
+
+int QSGCurveFillMaterial::compare(const QSGMaterial *other) const
+{
+ if (other->type() != type())
+ return (type() - other->type());
+
+ const QSGCurveFillMaterial *otherMaterial =
+ static_cast<const QSGCurveFillMaterial *>(other);
+
+ QSGCurveFillNode *a = node();
+ QSGCurveFillNode *b = otherMaterial->node();
+ if (a == b)
+ return 0;
+
+ if (a->gradientType() == QGradient::NoGradient && a->fillTextureProvider() == nullptr) {
+ if (int d = a->color().red() - b->color().red())
+ return d;
+ if (int d = a->color().green() - b->color().green())
+ return d;
+ if (int d = a->color().blue() - b->color().blue())
+ return d;
+ if (int d = a->color().alpha() - b->color().alpha())
+ return d;
+ } else {
+ if (a->gradientType() != QGradient::NoGradient) {
+ const QSGGradientCache::GradientDesc &ga = *a->fillGradient();
+ const QSGGradientCache::GradientDesc &gb = *b->fillGradient();
+
+ if (int d = ga.a.x() - gb.a.x())
+ return d;
+ if (int d = ga.a.y() - gb.a.y())
+ return d;
+ if (int d = ga.b.x() - gb.b.x())
+ return d;
+ if (int d = ga.b.y() - gb.b.y())
+ return d;
+
+ if (int d = ga.v0 - gb.v0)
+ return d;
+ if (int d = ga.v1 - gb.v1)
+ return d;
+
+ if (int d = ga.spread - gb.spread)
+ return d;
+
+ if (int d = ga.stops.size() - gb.stops.size())
+ return d;
+
+ for (int i = 0; i < ga.stops.size(); ++i) {
+ if (int d = ga.stops[i].first - gb.stops[i].first)
+ return d;
+ if (int d = ga.stops[i].second.rgba() - gb.stops[i].second.rgba())
+ return d;
+ }
+ }
+
+ if (int d = a->fillTransform()->compareTo(*b->fillTransform()))
+ return d;
+ }
+
+ const qintptr diff = qintptr(a->fillTextureProvider()) - qintptr(b->fillTextureProvider());
+ return diff < 0 ? -1 : (diff > 0 ? 1 : 0);
+}
+
+QSGMaterialType *QSGCurveFillMaterial::type() const
+{
+ static QSGMaterialType type[5];
+ uint index = node()->gradientType();
+ Q_ASSERT((index & ~3) == 0); // Only two first bits for gradient type
+
+ if (node()->gradientType() == QGradient::NoGradient && node()->fillTextureProvider() != nullptr)
+ index = 5;
+
+ return &type[index];
+}
+
+QSGMaterialShader *QSGCurveFillMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
+{
+ return new QSGCurveFillMaterialShader(node()->gradientType(),
+ node()->gradientType() == QGradient::NoGradient
+ && node()->fillTextureProvider() != nullptr,
+ renderMode == QSGRendererInterface::RenderMode3D,
+ viewCount());
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgcurvefillnode_p.h b/src/quick/scenegraph/qsgcurvefillnode_p.h
new file mode 100644
index 0000000000..441576a814
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurvefillnode_p.h
@@ -0,0 +1,273 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGCURVEFILLNODE_P_H
+#define QSGCURVEFILLNODE_P_H
+
+#include <QtGui/qbrush.h>
+
+#include <QtQuick/qtquickexports.h>
+#include <QtQuick/private/qsggradientcache_p.h>
+#include <QtQuick/private/qsgtransform_p.h>
+#include <QtQuick/qsgnode.h>
+#include <QtQuick/qsgtextureprovider.h>
+
+#include "qsgcurveabstractnode_p.h"
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+QT_BEGIN_NAMESPACE
+
+class QSGTextureProvider;
+
+class Q_QUICK_EXPORT QSGCurveFillNode : public QObject, public QSGCurveAbstractNode
+{
+ Q_OBJECT
+public:
+ QSGCurveFillNode();
+
+ void setColor(QColor col) override
+ {
+ m_color = col;
+ markDirty(DirtyMaterial);
+ }
+
+ QColor color() const
+ {
+ return m_color;
+ }
+
+ void setFillTextureProvider(QSGTextureProvider *provider)
+ {
+ if (provider == m_textureProvider)
+ return;
+
+ if (m_textureProvider != nullptr) {
+ disconnect(m_textureProvider, &QSGTextureProvider::textureChanged,
+ this, &QSGCurveFillNode::handleTextureChanged);
+ disconnect(m_textureProvider, &QSGTextureProvider::destroyed,
+ this, &QSGCurveFillNode::handleTextureProviderDestroyed);
+ }
+
+ m_textureProvider = provider;
+ markDirty(DirtyMaterial);
+
+ if (m_textureProvider != nullptr) {
+ connect(m_textureProvider, &QSGTextureProvider::textureChanged,
+ this, &QSGCurveFillNode::handleTextureChanged);
+ connect(m_textureProvider, &QSGTextureProvider::destroyed,
+ this, &QSGCurveFillNode::handleTextureProviderDestroyed);
+ }
+ }
+
+
+ QSGTextureProvider *fillTextureProvider() const
+ {
+ return m_textureProvider;
+ }
+
+ void setFillGradient(const QSGGradientCache::GradientDesc &fillGradient)
+ {
+ m_fillGradient = fillGradient;
+ markDirty(DirtyMaterial);
+ }
+
+ const QSGGradientCache::GradientDesc *fillGradient() const
+ {
+ return &m_fillGradient;
+ }
+
+ void setGradientType(QGradient::Type type)
+ {
+ m_gradientType = type;
+ markDirty(DirtyMaterial);
+ }
+
+ QGradient::Type gradientType() const
+ {
+ return m_gradientType;
+ }
+
+ void setFillTransform(const QSGTransform &transform)
+ {
+ m_fillTransform = transform;
+ markDirty(DirtyMaterial);
+ }
+
+ const QSGTransform *fillTransform() const
+ {
+ return &m_fillTransform;
+ }
+
+ float debug() const
+ {
+ return m_debug;
+ }
+
+ void setDebug(float newDebug)
+ {
+ m_debug = newDebug;
+ }
+
+ void appendTriangle(const std::array<QVector2D, 3> &v, // triangle vertices
+ const std::array<QVector2D, 3> &n, // vertex normals
+ std::function<QVector3D(QVector2D)> uvForPoint
+ )
+ {
+ QVector3D uv1 = uvForPoint(v[0]);
+ QVector3D uv2 = uvForPoint(v[1]);
+ QVector3D uv3 = uvForPoint(v[2]);
+
+ QVector2D duvdx = QVector2D(uvForPoint(v[0] + QVector2D(1, 0))) - QVector2D(uv1);
+ QVector2D duvdy = QVector2D(uvForPoint(v[0] + QVector2D(0, 1))) - QVector2D(uv1);
+
+ m_uncookedIndexes.append(m_uncookedVertexes.size());
+ m_uncookedVertexes.append( { v[0].x(), v[0].y(),
+ uv1.x(), uv1.y(), uv1.z(),
+ duvdx.x(), duvdx.y(),
+ duvdy.x(), duvdy.y(),
+ n[0].x(), n[0].y()
+ });
+
+ m_uncookedIndexes.append(m_uncookedVertexes.size());
+ m_uncookedVertexes.append( { v[1].x(), v[1].y(),
+ uv2.x(), uv2.y(), uv2.z(),
+ duvdx.x(), duvdx.y(),
+ duvdy.x(), duvdy.y(),
+ n[1].x(), n[1].y()
+ });
+
+ m_uncookedIndexes.append(m_uncookedVertexes.size());
+ m_uncookedVertexes.append( { v[2].x(), v[2].y(),
+ uv3.x(), uv3.y(), uv3.z(),
+ duvdx.x(), duvdx.y(),
+ duvdy.x(), duvdy.y(),
+ n[2].x(), n[2].y()
+ });
+ }
+
+ void appendTriangle(const QVector2D &v1,
+ const QVector2D &v2,
+ const QVector2D &v3,
+ const QVector3D &uv1,
+ const QVector3D &uv2,
+ const QVector3D &uv3,
+ const QVector2D &n1,
+ const QVector2D &n2,
+ const QVector2D &n3,
+ const QVector2D &duvdx,
+ const QVector2D &duvdy)
+ {
+ m_uncookedIndexes.append(m_uncookedVertexes.size());
+ m_uncookedVertexes.append( { v1.x(), v1.y(),
+ uv1.x(), uv1.y(), uv1.z(),
+ duvdx.x(), duvdx.y(),
+ duvdy.x(), duvdy.y(),
+ n1.x(), n1.y()
+ });
+
+ m_uncookedIndexes.append(m_uncookedVertexes.size());
+ m_uncookedVertexes.append( { v2.x(), v2.y(),
+ uv2.x(), uv2.y(), uv2.z(),
+ duvdx.x(), duvdx.y(),
+ duvdy.x(), duvdy.y(),
+ n2.x(), n2.y()
+ });
+
+ m_uncookedIndexes.append(m_uncookedVertexes.size());
+ m_uncookedVertexes.append( { v3.x(), v3.y(),
+ uv3.x(), uv3.y(), uv3.z(),
+ duvdx.x(), duvdx.y(),
+ duvdy.x(), duvdy.y(),
+ n3.x(), n3.y()
+ });
+ }
+
+ void appendTriangle(const QVector2D &v1,
+ const QVector2D &v2,
+ const QVector2D &v3,
+ std::function<QVector3D(QVector2D)> uvForPoint)
+ {
+ appendTriangle({v1, v2, v3}, {}, uvForPoint);
+ }
+
+ QVector<quint32> uncookedIndexes() const
+ {
+ return m_uncookedIndexes;
+ }
+
+ void cookGeometry() override;
+
+ void reserve(qsizetype size)
+ {
+ m_uncookedIndexes.reserve(size);
+ m_uncookedVertexes.reserve(size);
+ }
+
+ void preprocess() override
+ {
+ if (m_textureProvider != nullptr) {
+ if (QSGDynamicTexture *texture = qobject_cast<QSGDynamicTexture *>(m_textureProvider->texture()))
+ texture->updateTexture();
+ }
+ }
+
+ QVector2D boundsSize() const
+ {
+ return m_boundsSize;
+ }
+
+ void setBoundsSize(const QVector2D &boundsSize)
+ {
+ m_boundsSize = boundsSize;
+ }
+
+private Q_SLOTS:
+ void handleTextureChanged()
+ {
+ markDirty(DirtyMaterial);
+ }
+
+ void handleTextureProviderDestroyed()
+ {
+ m_textureProvider = nullptr;
+ markDirty(DirtyMaterial);
+ }
+
+private:
+ struct CurveNodeVertex
+ {
+ float x, y, u, v, w;
+ float dudx, dvdx, dudy, dvdy; // Size of pixel in curve space (must be same for all vertices in triangle)
+ float nx, ny; // normal vector describing the direction to shift the vertex for AA
+ };
+
+ void updateMaterial();
+ static const QSGGeometry::AttributeSet &attributes();
+
+ QScopedPointer<QSGMaterial> m_material;
+
+ QVector<CurveNodeVertex> m_uncookedVertexes;
+ QVector<quint32> m_uncookedIndexes;
+
+ QSGGradientCache::GradientDesc m_fillGradient;
+ QSGTextureProvider *m_textureProvider = nullptr;
+ QVector2D m_boundsSize;
+ QSGTransform m_fillTransform;
+ QColor m_color = Qt::white;
+ QGradient::Type m_gradientType = QGradient::NoGradient;
+ float m_debug = 0.0f;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGCURVEFILLNODE_P_H
diff --git a/src/quick/scenegraph/qsgcurvefillnode_p_p.h b/src/quick/scenegraph/qsgcurvefillnode_p_p.h
new file mode 100644
index 0000000000..9cc80f3dca
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurvefillnode_p_p.h
@@ -0,0 +1,57 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGCURVEFILLNODE_P_P_H
+#define QSGCURVEFILLNODE_P_P_H
+
+#include <QtQuick/qtquickexports.h>
+#include <QtQuick/qsgmaterial.h>
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+QT_BEGIN_NAMESPACE
+
+class QSGCurveFillNode;
+class QSGPlainTexture;
+class Q_QUICK_EXPORT QSGCurveFillMaterial : public QSGMaterial
+{
+public:
+ QSGCurveFillMaterial(QSGCurveFillNode *node);
+ ~QSGCurveFillMaterial() override;
+ int compare(const QSGMaterial *other) const override;
+
+ QSGCurveFillNode *node() const
+ {
+ return m_node;
+ }
+
+ QSGPlainTexture *dummyTexture() const
+ {
+ return m_dummyTexture;
+ }
+
+ void setDummyTexture(QSGPlainTexture *dummyTexture)
+ {
+ m_dummyTexture = dummyTexture;
+ }
+
+private:
+ QSGMaterialType *type() const override;
+ QSGMaterialShader *createShader(QSGRendererInterface::RenderMode renderMode) const override;
+
+ QSGCurveFillNode *m_node;
+ QSGPlainTexture *m_dummyTexture = nullptr;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGCURVEFILLNODE_P_P_H
diff --git a/src/quick/scenegraph/qsgcurveglyphatlas.cpp b/src/quick/scenegraph/qsgcurveglyphatlas.cpp
new file mode 100644
index 0000000000..410ce2dd26
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurveglyphatlas.cpp
@@ -0,0 +1,142 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsgcurveglyphatlas_p.h"
+#include "qsgcurvefillnode_p.h"
+#include "qsgcurvestrokenode_p.h"
+#include "qsgcurveprocessor_p.h"
+#include "util/qquadpath_p.h"
+
+#include <QtGui/qrawfont.h>
+#include <QtGui/qpainterpath.h>
+
+QT_BEGIN_NAMESPACE
+
+QSGCurveGlyphAtlas::QSGCurveGlyphAtlas(const QRawFont &font)
+ : m_font(font)
+{
+ // The font size used for the curve atlas currently affects the outlines, since we don't
+ // really support cosmetic outlines. Therefore we need to pick one which gives large enough
+ // triangles relative to glyph size that we can reuse the same triangles for any font size.
+ // When experimenting, 10 works for all font sizes we tested, so we currently default to this
+ // but allow overriding it.
+ static int curveGlyphAtlasFontSize = qEnvironmentVariableIntValue("QSGCURVEGLYPHATLAS_FONT_SIZE");
+ m_font.setPixelSize(curveGlyphAtlasFontSize > 0 ? qreal(curveGlyphAtlasFontSize) : 10.0);
+}
+
+QSGCurveGlyphAtlas::~QSGCurveGlyphAtlas()
+{
+}
+
+void QSGCurveGlyphAtlas::populate(const QList<glyph_t> &glyphs)
+{
+ for (glyph_t glyphIndex : glyphs) {
+ if (!m_glyphs.contains(glyphIndex)) {
+ QPainterPath path = m_font.pathForGlyph(glyphIndex);
+ QQuadPath quadPath = QQuadPath::fromPainterPath(path);
+ quadPath.setFillRule(Qt::WindingFill);
+
+ Glyph glyph;
+
+ QSGCurveProcessor::processStroke(quadPath, 2, 2, Qt::MiterJoin, Qt::FlatCap,
+ [&glyph](const std::array<QVector2D, 3> &s,
+ const std::array<QVector2D, 3> &p,
+ const std::array<QVector2D, 3> &n,
+ bool isLine) {
+ glyph.strokeVertices.append(s.at(0));
+ glyph.strokeVertices.append(s.at(1));
+ glyph.strokeVertices.append(s.at(2));
+
+ glyph.strokeUvs.append(p.at(0));
+ glyph.strokeUvs.append(p.at(1));
+ glyph.strokeUvs.append(p.at(2));
+
+ glyph.strokeNormals.append(n.at(0));
+ glyph.strokeNormals.append(n.at(1));
+ glyph.strokeNormals.append(n.at(2));
+
+ glyph.strokeElementIsLine.append(isLine);
+ });
+
+ quadPath = quadPath.subPathsClosed();
+ quadPath.addCurvatureData(); // ### Since the inside of glyphs is defined by order of
+ // vertices, this step could be simplified
+ QSGCurveProcessor::solveOverlaps(quadPath);
+
+ QSGCurveProcessor::processFill(quadPath,
+ Qt::WindingFill,
+ [&glyph](const std::array<QVector2D, 3> &v,
+ const std::array<QVector2D, 3> &n,
+ QSGCurveProcessor::uvForPointCallback uvForPoint)
+ {
+ glyph.vertices.append(v.at(0));
+ glyph.vertices.append(v.at(1));
+ glyph.vertices.append(v.at(2));
+
+ QVector3D uv1 = uvForPoint(v.at(0));
+ glyph.uvs.append(uv1);
+ glyph.uvs.append(uvForPoint(v.at(1)));
+ glyph.uvs.append(uvForPoint(v.at(2)));
+
+ glyph.normals.append(n.at(0));
+ glyph.normals.append(n.at(1));
+ glyph.normals.append(n.at(2));
+
+ glyph.duvdx.append(QVector2D(uvForPoint(v.at(0) + QVector2D(1, 0))) - QVector2D(uv1));
+ glyph.duvdy.append(QVector2D(uvForPoint(v.at(0) + QVector2D(0, 1))) - QVector2D(uv1));
+ });
+
+ m_glyphs.insert(glyphIndex, glyph);
+ }
+ }
+}
+
+void QSGCurveGlyphAtlas::addStroke(QSGCurveStrokeNode *node,
+ glyph_t glyphIndex,
+ const QPointF &position) const
+{
+ const Glyph &glyph = m_glyphs[glyphIndex];
+
+ const QVector2D v(position);
+ for (qsizetype i = glyph.strokeElementIsLine.size() - 1; i >= 0; --i) {
+ QVector2D v1 = glyph.strokeVertices.at(i * 3 + 0) + v;
+ QVector2D v2 = glyph.strokeVertices.at(i * 3 + 1) + v;
+ QVector2D v3 = glyph.strokeVertices.at(i * 3 + 2) + v;
+ if (glyph.strokeElementIsLine.at(i)) {
+ node->appendTriangle({ v1, v2, v3 },
+ std::array<QVector2D, 2>({ glyph.strokeUvs.at(i * 3 + 0) + v, glyph.strokeUvs.at(i * 3 + 2) + v }),
+ { glyph.strokeNormals.at(i * 3 + 0), glyph.strokeNormals.at(i * 3 + 1), glyph.strokeNormals.at(i * 3 + 2) });
+ } else {
+ node->appendTriangle({ v1, v2, v3 },
+ { glyph.strokeUvs.at(i * 3 + 0) + v, glyph.strokeUvs.at(i * 3 + 1) + v, glyph.strokeUvs.at(i * 3 + 2) + v },
+ { glyph.strokeNormals.at(i * 3 + 0), glyph.strokeNormals.at(i * 3 + 1), glyph.strokeNormals.at(i * 3 + 2) });
+
+ }
+ }
+}
+
+void QSGCurveGlyphAtlas::addGlyph(QSGCurveFillNode *node,
+ glyph_t glyphIndex,
+ const QPointF &position,
+ qreal pixelSize) const
+{
+ const Glyph &glyph = m_glyphs[glyphIndex];
+
+ const float scaleFactor = pixelSize / m_font.pixelSize();
+ const QVector2D v(position);
+ for (qsizetype i = 0; i < glyph.vertices.size() / 3; ++i) {
+ node->appendTriangle(scaleFactor * glyph.vertices.at(i * 3 + 0) + v,
+ scaleFactor * glyph.vertices.at(i * 3 + 1) + v,
+ scaleFactor * glyph.vertices.at(i * 3 + 2) + v,
+ glyph.uvs.at(i * 3 + 0),
+ glyph.uvs.at(i * 3 + 1),
+ glyph.uvs.at(i * 3 + 2),
+ glyph.normals.at(i * 3 + 0),
+ glyph.normals.at(i * 3 + 1),
+ glyph.normals.at(i * 3 + 2),
+ glyph.duvdx.at(i) / scaleFactor,
+ glyph.duvdy.at(i) / scaleFactor);
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgcurveglyphatlas_p.h b/src/quick/scenegraph/qsgcurveglyphatlas_p.h
new file mode 100644
index 0000000000..c82f1921f9
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurveglyphatlas_p.h
@@ -0,0 +1,69 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGCURVEGLYPHATLAS_P_H
+#define QSGCURVEGLYPHATLAS_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtGui/qrawfont.h>
+#include <QtGui/private/qtextengine_p.h>
+#include <QtQuick/qtquickexports.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSGCurveFillNode;
+class QSGCurveStrokeNode;
+
+class Q_QUICK_EXPORT QSGCurveGlyphAtlas
+{
+public:
+ QSGCurveGlyphAtlas(const QRawFont &font);
+ virtual ~QSGCurveGlyphAtlas();
+
+ void populate(const QList<glyph_t> &glyphs);
+ void addGlyph(QSGCurveFillNode *node,
+ glyph_t glyph,
+ const QPointF &position,
+ qreal pixelSize) const;
+ void addStroke(QSGCurveStrokeNode *node,
+ glyph_t glyph,
+ const QPointF &position) const;
+
+ qreal fontSize() const
+ {
+ return m_font.pixelSize();
+ }
+
+private:
+ struct Glyph
+ {
+ QList<QVector2D> vertices;
+ QList<QVector3D> uvs;
+ QList<QVector2D> normals;
+ QList<QVector2D> duvdx;
+ QList<QVector2D> duvdy;
+
+ QList<QVector2D> strokeVertices;
+ QList<QVector2D> strokeUvs;
+ QList<QVector2D> strokeNormals;
+ QList<bool> strokeElementIsLine;
+ };
+
+ QHash<glyph_t, Glyph> m_glyphs;
+ QRawFont m_font;
+};
+
+QT_END_NAMESPACE
+
+
+#endif // QSGCURVEGLYPHATLAS_P_H
diff --git a/src/quick/scenegraph/qsgcurveglyphnode.cpp b/src/quick/scenegraph/qsgcurveglyphnode.cpp
new file mode 100644
index 0000000000..c5252083d3
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurveglyphnode.cpp
@@ -0,0 +1,164 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsgcurveglyphnode_p.h"
+#include "qsgcurveglyphatlas_p.h"
+#include "qsgcurvefillnode_p.h"
+#include "qsgcurvestrokenode_p.h"
+
+#include <private/qsgcurveabstractnode_p.h>
+#include <private/qsgcontext_p.h>
+#include <private/qsgtexturematerial_p.h>
+
+#include <private/qrawfont_p.h>
+#include <QtGui/qcolor.h>
+
+QT_BEGIN_NAMESPACE
+
+QSGCurveGlyphNode::QSGCurveGlyphNode(QSGRenderContext *context)
+ : m_context(context)
+ , m_geometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 0)
+ , m_dirtyGeometry(false)
+{
+ setFlag(UsePreprocess);
+ setFlag(OwnsMaterial);
+
+ // #### To avoid asserts: we should probably merge this with QSGCurveFillNode
+ setGeometry(&m_geometry);
+ setMaterial(new QSGTextureMaterial);
+}
+
+QSGCurveGlyphNode::~QSGCurveGlyphNode()
+{
+}
+
+void QSGCurveGlyphNode::setPreferredAntialiasingMode(AntialiasingMode mode)
+{
+ Q_UNUSED(mode);
+}
+
+void QSGCurveGlyphNode::setColor(const QColor &color)
+{
+ m_color = color;
+ if (m_glyphNode != nullptr)
+ m_glyphNode->setColor(color);
+}
+
+void QSGCurveGlyphNode::setStyleColor(const QColor &styleColor)
+{
+ m_styleColor = styleColor;
+ if (m_styleNode != nullptr)
+ m_styleNode->setColor(styleColor);
+}
+
+void QSGCurveGlyphNode::setStyle(QQuickText::TextStyle style)
+{
+ if (m_style != style) {
+ m_style = style;
+ m_dirtyGeometry = true;
+ update();
+ }
+}
+
+void QSGCurveGlyphNode::setGlyphs(const QPointF &position, const QGlyphRun &glyphs)
+{
+ m_glyphs = glyphs;
+
+ QRawFont font = glyphs.rawFont();
+ m_fontSize = font.pixelSize();
+ m_position = QPointF(position.x(), position.y() - font.ascent());
+
+
+ m_dirtyGeometry = true;
+
+#ifdef QSG_RUNTIME_DESCRIPTION
+ qsgnode_set_description(this, QString::number(glyphs.glyphIndexes().count())
+ + QStringLiteral(" curve glyphs: ")
+ + m_glyphs.rawFont().familyName()
+ + QStringLiteral(" ")
+ + QString::number(m_glyphs.rawFont().pixelSize()));
+#endif
+}
+
+void QSGCurveGlyphNode::update()
+{
+ markDirty(DirtyGeometry);
+}
+
+void QSGCurveGlyphNode::preprocess()
+{
+ if (m_dirtyGeometry)
+ updateGeometry();
+}
+
+void QSGCurveGlyphNode::updateGeometry()
+{
+ delete m_glyphNode;
+ m_glyphNode = nullptr;
+
+ delete m_styleNode;
+ m_styleNode = nullptr;
+
+ QSGCurveGlyphAtlas *curveGlyphAtlas = m_context->curveGlyphAtlas(m_glyphs.rawFont());
+ curveGlyphAtlas->populate(m_glyphs.glyphIndexes());
+
+ m_glyphNode = new QSGCurveFillNode;
+ m_glyphNode->setColor(m_color);
+
+ QPointF offset;
+
+ float fontScale = float(m_fontSize / curveGlyphAtlas->fontSize());
+ QSGCurveFillNode *raisedSunkenStyleNode = nullptr;
+ QSGCurveStrokeNode *outlineNode = nullptr;
+ if (m_style == QQuickText::Raised || m_style == QQuickText::Sunken) {
+ raisedSunkenStyleNode = new QSGCurveFillNode;
+ raisedSunkenStyleNode ->setColor(m_styleColor);
+
+ offset = m_style == QQuickText::Raised ? QPointF(0.0f, 1.0f) : QPointF(0.0f, -1.0f);
+ m_styleNode = raisedSunkenStyleNode;
+ } else if (m_style == QQuickText::Outline) {
+ outlineNode = new QSGCurveStrokeNode;
+ outlineNode->setColor(m_styleColor);
+ outlineNode->setStrokeWidth(2 / fontScale);
+ outlineNode->setLocalScale(fontScale);
+
+ m_styleNode = outlineNode;
+ }
+
+ const QVector<quint32> indexes = m_glyphs.glyphIndexes();
+ const QVector<QPointF> positions = m_glyphs.positions();
+ for (qsizetype i = 0; i < indexes.size(); ++i) {
+ if (i == 0)
+ m_baseLine = positions.at(i);
+ curveGlyphAtlas->addGlyph(m_glyphNode,
+ indexes.at(i),
+ m_position + positions.at(i),
+ m_fontSize);
+ if (raisedSunkenStyleNode != nullptr) {
+ curveGlyphAtlas->addGlyph(raisedSunkenStyleNode,
+ indexes.at(i),
+ m_position + positions.at(i) + offset,
+ m_fontSize);
+ }
+ if (outlineNode != nullptr) {
+ // Since the stroke node will scale everything by fontScale internally (the
+ // shader does not support pre-transforming the vertices), we have to also first
+ // do the inverse scale on the glyph position to get the correct position.
+ curveGlyphAtlas->addStroke(outlineNode,
+ indexes.at(i),
+ (m_position + positions.at(i)) / fontScale);
+ }
+ }
+
+ if (m_styleNode != nullptr) {
+ m_styleNode->cookGeometry();
+ appendChildNode(m_styleNode);
+ }
+
+ m_glyphNode->cookGeometry();
+ appendChildNode(m_glyphNode);
+
+ m_dirtyGeometry = false;
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgcurveglyphnode_p.h b/src/quick/scenegraph/qsgcurveglyphnode_p.h
new file mode 100644
index 0000000000..cb634112d0
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurveglyphnode_p.h
@@ -0,0 +1,68 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGCURVEGLYPHNODE_P_H
+#define QSGCURVEGLYPHNODE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qtquickexports.h>
+#include <private/qsgadaptationlayer_p.h>
+#include <private/qsgbasicglyphnode_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSGCurveGlyphAtlas;
+class QSGCurveFillNode;
+class QSGCurveAbstractNode;
+
+class Q_QUICK_EXPORT QSGCurveGlyphNode : public QSGGlyphNode
+{
+public:
+ QSGCurveGlyphNode(QSGRenderContext *context);
+ ~QSGCurveGlyphNode();
+ void setGlyphs(const QPointF &position, const QGlyphRun &glyphs) override;
+ void update() override;
+ void preprocess() override;
+ void setPreferredAntialiasingMode(AntialiasingMode) override;
+ void updateGeometry();
+ void setColor(const QColor &color) override;
+ void setStyle(QQuickText::TextStyle style) override;
+
+ void setStyleColor(const QColor &color) override;
+ QPointF baseLine() const override { return m_baseLine; }
+
+private:
+ QSGRenderContext *m_context;
+ QSGGeometry m_geometry;
+ QColor m_color = Qt::black;
+
+ struct GlyphInfo {
+ QVector<quint32> indexes;
+ QVector<QPointF> positions;
+ };
+
+ uint m_dirtyGeometry: 1;
+ qreal m_fontSize = 0.0f;
+ QGlyphRun m_glyphs;
+ QQuickText::TextStyle m_style;
+ QColor m_styleColor;
+ QPointF m_baseLine;
+ QPointF m_position;
+
+ QSGCurveFillNode *m_glyphNode = nullptr;
+ QSGCurveAbstractNode *m_styleNode = nullptr;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/quick/scenegraph/qsgcurveprocessor.cpp b/src/quick/scenegraph/qsgcurveprocessor.cpp
new file mode 100644
index 0000000000..b647b9ffdf
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurveprocessor.cpp
@@ -0,0 +1,1887 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsgcurveprocessor_p.h"
+
+#include <QtGui/private/qtriangulator_p.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qhash.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcSGCurveProcessor, "qt.quick.curveprocessor");
+Q_STATIC_LOGGING_CATEGORY(lcSGCurveIntersectionSolver, "qt.quick.curveprocessor.intersections");
+
+namespace {
+// Input coordinate space is pre-mapped so that (0, 0) maps to [0, 0] in uv space.
+// v1 maps to [1,0], v2 maps to [0,1]. p is the point to be mapped to uv in this space (i.e. vector from p0)
+static inline QVector2D uvForPoint(QVector2D v1, QVector2D v2, QVector2D p)
+{
+ double divisor = v1.x() * v2.y() - v2.x() * v1.y();
+
+ float u = (p.x() * v2.y() - p.y() * v2.x()) / divisor;
+ float v = (p.y() * v1.x() - p.x() * v1.y()) / divisor;
+
+ return {u, v};
+}
+
+// Find uv coordinates for the point p, for a quadratic curve from p0 to p2 with control point p1
+// also works for a line from p0 to p2, where p1 is on the inside of the path relative to the line
+static inline QVector2D curveUv(QVector2D p0, QVector2D p1, QVector2D p2, QVector2D p)
+{
+ QVector2D v1 = 2 * (p1 - p0);
+ QVector2D v2 = p2 - v1 - p0;
+ return uvForPoint(v1, v2, p - p0);
+}
+
+static QVector3D elementUvForPoint(const QQuadPath::Element& e, QVector2D p)
+{
+ auto uv = curveUv(e.startPoint(), e.referencePoint(), e.endPoint(), p);
+ if (e.isLine())
+ return { uv.x(), uv.y(), 0.0f };
+ else
+ return { uv.x(), uv.y(), e.isConvex() ? -1.0f : 1.0f };
+}
+
+static inline QVector2D calcNormalVector(QVector2D a, QVector2D b)
+{
+ auto v = b - a;
+ return {v.y(), -v.x()};
+}
+
+// The sign of the return value indicates which side of the line defined by a and n the point p falls
+static inline float testSideOfLineByNormal(QVector2D a, QVector2D n, QVector2D p)
+{
+ float dot = QVector2D::dotProduct(p - a, n);
+ return dot;
+};
+
+static inline float determinant(const QVector2D &p1, const QVector2D &p2, const QVector2D &p3)
+{
+ return p1.x() * (p2.y() - p3.y())
+ + p2.x() * (p3.y() - p1.y())
+ + p3.x() * (p1.y() - p2.y());
+}
+
+/*
+ Clever triangle overlap algorithm. Stack Overflow says:
+
+ You can prove that the two triangles do not collide by finding an edge (out of the total 6
+ edges that make up the two triangles) that acts as a separating line where all the vertices
+ of one triangle lie on one side and the vertices of the other triangle lie on the other side.
+ If you can find such an edge then it means that the triangles do not intersect otherwise the
+ triangles are colliding.
+*/
+using TrianglePoints = std::array<QVector2D, 3>;
+using LinePoints = std::array<QVector2D, 2>;
+
+// The sign of the determinant tells the winding order: positive means counter-clockwise
+
+static inline double determinant(const TrianglePoints &p)
+{
+ return determinant(p[0], p[1], p[2]);
+}
+
+// Fix the triangle so that the determinant is positive
+static void fixWinding(TrianglePoints &p)
+{
+ double det = determinant(p);
+ if (det < 0.0) {
+ qSwap(p[0], p[1]);
+ }
+}
+
+// Return true if the determinant is negative, i.e. if the winding order is opposite of the triangle p1,p2,p3.
+// This means that p is strictly on the other side of p1-p2 relative to p3 [where p1,p2,p3 is a triangle with
+// a positive determinant].
+bool checkEdge(QVector2D &p1, QVector2D &p2, QVector2D &p, float epsilon)
+{
+ return determinant(p1, p2, p) <= epsilon;
+}
+
+// Check if lines l1 and l2 are intersecting and return the respective value. Solutions are stored to
+// the optional pointer solution.
+bool lineIntersection(const LinePoints &l1, const LinePoints &l2, QList<QPair<float, float>> *solution = nullptr)
+{
+ constexpr double eps2 = 1e-5; // Epsilon for parameter space t1-t2
+
+ // see https://www.wolframalpha.com/input?i=solve%28A+%2B+t+*+B+%3D+C+%2B+s*D%3B+E+%2B+t+*+F+%3D+G+%2B+s+*+H+for+s+and+t%29
+ const float A = l1[0].x();
+ const float B = l1[1].x() - l1[0].x();
+ const float C = l2[0].x();
+ const float D = l2[1].x() - l2[0].x();
+ const float E = l1[0].y();
+ const float F = l1[1].y() - l1[0].y();
+ const float G = l2[0].y();
+ const float H = l2[1].y() - l2[0].y();
+
+ float det = D * F - B * H;
+
+ if (det == 0)
+ return false;
+
+ float s = (F * (A - C) - B * (E - G)) / det;
+ float t = (H * (A - C) - D * (E - G)) / det;
+
+ // Intersections at 0 count. Intersections at 1 do not.
+ bool intersecting = (s >= 0 && s <= 1. - eps2 && t >= 0 && t <= 1. - eps2);
+
+ if (solution && intersecting)
+ solution->append(QPair<float, float>(t, s));
+
+ return intersecting;
+}
+
+
+bool checkTriangleOverlap(TrianglePoints &triangle1, TrianglePoints &triangle2, float epsilon = 1.0/32)
+{
+ // See if there is an edge of triangle1 such that all vertices in triangle2 are on the opposite side
+ fixWinding(triangle1);
+ for (int i = 0; i < 3; i++) {
+ int ni = (i + 1) % 3;
+ if (checkEdge(triangle1[i], triangle1[ni], triangle2[0], epsilon) &&
+ checkEdge(triangle1[i], triangle1[ni], triangle2[1], epsilon) &&
+ checkEdge(triangle1[i], triangle1[ni], triangle2[2], epsilon))
+ return false;
+ }
+
+ // See if there is an edge of triangle2 such that all vertices in triangle1 are on the opposite side
+ fixWinding(triangle2);
+ for (int i = 0; i < 3; i++) {
+ int ni = (i + 1) % 3;
+
+ if (checkEdge(triangle2[i], triangle2[ni], triangle1[0], epsilon) &&
+ checkEdge(triangle2[i], triangle2[ni], triangle1[1], epsilon) &&
+ checkEdge(triangle2[i], triangle2[ni], triangle1[2], epsilon))
+ return false;
+ }
+
+ return true;
+}
+
+bool checkLineTriangleOverlap(TrianglePoints &triangle, LinePoints &line, float epsilon = 1.0/32)
+{
+ // See if all vertices of the triangle are on the same side of the line
+ bool s1 = determinant(line[0], line[1], triangle[0]) < 0;
+ auto s2 = determinant(line[0], line[1], triangle[1]) < 0;
+ auto s3 = determinant(line[0], line[1], triangle[2]) < 0;
+ // If all determinants have the same sign, then there is no overlap
+ if (s1 == s2 && s2 == s3) {
+ return false;
+ }
+ // See if there is an edge of triangle1 such that both vertices in line are on the opposite side
+ fixWinding(triangle);
+ for (int i = 0; i < 3; i++) {
+ int ni = (i + 1) % 3;
+ if (checkEdge(triangle[i], triangle[ni], line[0], epsilon) &&
+ checkEdge(triangle[i], triangle[ni], line[1], epsilon))
+ return false;
+ }
+
+ return true;
+}
+
+static bool isOverlap(const QQuadPath &path, int e1, int e2)
+{
+ const QQuadPath::Element &element1 = path.elementAt(e1);
+ const QQuadPath::Element &element2 = path.elementAt(e2);
+
+ if (element1.isLine()) {
+ LinePoints line1{ element1.startPoint(), element1.endPoint() };
+ if (element2.isLine()) {
+ LinePoints line2{ element2.startPoint(), element2.endPoint() };
+ return lineIntersection(line1, line2);
+ } else {
+ TrianglePoints t2{ element2.startPoint(), element2.controlPoint(), element2.endPoint() };
+ return checkLineTriangleOverlap(t2, line1);
+ }
+ } else {
+ TrianglePoints t1{ element1.startPoint(), element1.controlPoint(), element1.endPoint() };
+ if (element2.isLine()) {
+ LinePoints line{ element2.startPoint(), element2.endPoint() };
+ return checkLineTriangleOverlap(t1, line);
+ } else {
+ TrianglePoints t2{ element2.startPoint(), element2.controlPoint(), element2.endPoint() };
+ return checkTriangleOverlap(t1, t2);
+ }
+ }
+
+ return false;
+}
+
+static float angleBetween(const QVector2D v1, const QVector2D v2)
+{
+ float dot = v1.x() * v2.x() + v1.y() * v2.y();
+ float cross = v1.x() * v2.y() - v1.y() * v2.x();
+ //TODO: Optimization: Maybe we don't need the atan2 here.
+ return atan2(cross, dot);
+}
+
+static bool isIntersecting(const TrianglePoints &t1, const TrianglePoints &t2, QList<QPair<float, float>> *solutions = nullptr)
+{
+ constexpr double eps = 1e-5; // Epsilon for coordinate space x-y
+ constexpr double eps2 = 1e-5; // Epsilon for parameter space t1-t2
+ constexpr int maxIterations = 7; // Maximum iterations allowed for Newton
+
+ // Convert to double to get better accuracy.
+ QPointF td1[3] = { t1[0].toPointF(), t1[1].toPointF(), t1[2].toPointF() };
+ QPointF td2[3] = { t2[0].toPointF(), t2[1].toPointF(), t2[2].toPointF() };
+
+ // F = P1(t1) - P2(t2) where P1 and P2 are bezier curve functions.
+ // F = (0, 0) at the intersection.
+ // t is the vector of bezier curve parameters for curves P1 and P2
+ auto F = [=](QPointF t) { return
+ td1[0] * (1 - t.x()) * (1. - t.x()) + 2 * td1[1] * (1. - t.x()) * t.x() + td1[2] * t.x() * t.x() -
+ td2[0] * (1 - t.y()) * (1. - t.y()) - 2 * td2[1] * (1. - t.y()) * t.y() - td2[2] * t.y() * t.y();};
+
+ // J is the Jacobi Matrix dF/dt where F and t are both vectors of dimension 2.
+ // Storing in a QLineF for simplicity.
+ auto J = [=](QPointF t) { return QLineF(
+ td1[0].x() * (-2 * (1-t.x())) + 2 * td1[1].x() * (1 - 2 * t.x()) + td1[2].x() * 2 * t.x(),
+ -td2[0].x() * (-2 * (1-t.y())) - 2 * td2[1].x() * (1 - 2 * t.y()) - td2[2].x() * 2 * t.y(),
+ td1[0].y() * (-2 * (1-t.x())) + 2 * td1[1].y() * (1 - 2 * t.x()) + td1[2].y() * 2 * t.x(),
+ -td2[0].y() * (-2 * (1-t.y())) - 2 * td2[1].y() * (1 - 2 * t.y()) - td2[2].y() * 2 * t.y());};
+
+ // solve the equation A(as 2x2 matrix)*x = b. Returns x.
+ auto solve = [](QLineF A, QPointF b) {
+ // invert A
+ const double det = A.x1() * A.y2() - A.y1() * A.x2();
+ QLineF Ainv(A.y2() / det, -A.y1() / det, -A.x2() / det, A.x1() / det);
+ // return A^-1 * b
+ return QPointF(Ainv.x1() * b.x() + Ainv.y1() * b.y(),
+ Ainv.x2() * b.x() + Ainv.y2() * b.y());
+ };
+
+#ifdef INTERSECTION_EXTRA_DEBUG
+ qCDebug(lcSGCurveIntersectionSolver) << "Checking" << t1[0] << t1[1] << t1[2];
+ qCDebug(lcSGCurveIntersectionSolver) << " vs" << t2[0] << t2[1] << t2[2];
+#endif
+
+ // TODO: Try to figure out reasonable starting points to reach all 4 possible intersections.
+ // This works but is kinda brute forcing it.
+ constexpr std::array tref = { QPointF{0.0, 0.0}, QPointF{0.5, 0.0}, QPointF{1.0, 0.0},
+ QPointF{0.0, 0.5}, QPointF{0.5, 0.5}, QPointF{1.0, 0.5},
+ QPointF{0.0, 1.0}, QPointF{0.5, 1.0}, QPointF{1.0, 1.0} };
+
+ for (auto t : tref) {
+ double err = 1;
+ QPointF fval = F(t);
+ int i = 0;
+
+ // TODO: Try to abort sooner, e.g. when falling out of the interval [0-1]?
+ while (err > eps && i < maxIterations) { // && t.x() >= 0 && t.x() <= 1 && t.y() >= 0 && t.y() <= 1) {
+ t = t - solve(J(t), fval);
+ fval = F(t);
+ err = qAbs(fval.x()) + qAbs(fval.y()); // Using the Manhatten length as an error indicator.
+ i++;
+#ifdef INTERSECTION_EXTRA_DEBUG
+ qCDebug(lcSGCurveIntersectionSolver) << " Newton iteration" << i << "t =" << t << "F =" << fval << "Error =" << err;
+#endif
+ }
+ // Intersections at 0 count. Intersections at 1 do not.
+ if (err < eps && t.x() >=0 && t.x() <= 1. - 10 * eps2 && t.y() >= 0 && t.y() <= 1. - 10 * eps2) {
+#ifdef INTERSECTION_EXTRA_DEBUG
+ qCDebug(lcSGCurveIntersectionSolver) << " Newton solution (after" << i << ")=" << t << "(" << F(t) << ")";
+#endif
+ if (solutions) {
+ bool append = true;
+ for (auto solution : *solutions) {
+ if (qAbs(solution.first - t.x()) < 10 * eps2 && qAbs(solution.second - t.y()) < 10 * eps2) {
+ append = false;
+ break;
+ }
+ }
+ if (append)
+ solutions->append({t.x(), t.y()});
+ }
+ else
+ return true;
+ }
+ }
+ if (solutions)
+ return solutions->size() > 0;
+ else
+ return false;
+}
+
+static bool isIntersecting(const QQuadPath &path, int e1, int e2, QList<QPair<float, float>> *solutions = nullptr)
+{
+
+ const QQuadPath::Element &elem1 = path.elementAt(e1);
+ const QQuadPath::Element &elem2 = path.elementAt(e2);
+
+ if (elem1.isLine() && elem2.isLine()) {
+ return lineIntersection(LinePoints {elem1.startPoint(), elem1.endPoint() },
+ LinePoints {elem2.startPoint(), elem2.endPoint() },
+ solutions);
+ } else {
+ return isIntersecting(TrianglePoints { elem1.startPoint(), elem1.controlPoint(), elem1.endPoint() },
+ TrianglePoints { elem2.startPoint(), elem2.controlPoint(), elem2.endPoint() },
+ solutions);
+ }
+}
+
+struct TriangleData
+{
+ TrianglePoints points;
+ int pathElementIndex;
+ TrianglePoints normals;
+};
+
+// Returns a normalized vector that is perpendicular to baseLine, pointing to the right
+inline QVector2D normalVector(QVector2D baseLine)
+{
+ QVector2D normal = QVector2D(-baseLine.y(), baseLine.x()).normalized();
+ return normal;
+}
+
+// Returns a vector that is normal to the path and pointing to the right. If endSide is false
+// the vector is normal to the start point, otherwise to the end point
+QVector2D normalVector(const QQuadPath::Element &element, bool endSide = false)
+{
+ if (element.isLine())
+ return normalVector(element.endPoint() - element.startPoint());
+ else if (!endSide)
+ return normalVector(element.controlPoint() - element.startPoint());
+ else
+ return normalVector(element.endPoint() - element.controlPoint());
+}
+
+// Returns a vector that is parallel to the path. If endSide is false
+// the vector starts at the start point and points forward,
+// otherwise it starts at the end point and points backward
+QVector2D tangentVector(const QQuadPath::Element &element, bool endSide = false)
+{
+ if (element.isLine()) {
+ if (!endSide)
+ return element.endPoint() - element.startPoint();
+ else
+ return element.startPoint() - element.endPoint();
+ } else {
+ if (!endSide)
+ return element.controlPoint() - element.startPoint();
+ else
+ return element.controlPoint() - element.endPoint();
+ }
+}
+
+// Really simplistic O(n^2) triangulator - only intended for five points
+QList<TriangleData> simplePointTriangulator(const QList<QVector2D> &pts, const QList<QVector2D> &normals, int elementIndex)
+{
+ int count = pts.size();
+ Q_ASSERT(count >= 3);
+ Q_ASSERT(normals.size() == count);
+
+ // First we find the convex hull: it's always in positive determinant winding order
+ QList<int> hull;
+ float det1 = determinant(pts[0], pts[1], pts[2]);
+ if (det1 > 0)
+ hull << 0 << 1 << 2;
+ else
+ hull << 2 << 1 << 0;
+ auto connectableInHull = [&](int idx) -> QList<int> {
+ QList<int> r;
+ const int n = hull.size();
+ const auto &pt = pts[idx];
+ for (int i = 0; i < n; ++i) {
+ const auto &i1 = hull.at(i);
+ const auto &i2 = hull.at((i+1) % n);
+ if (determinant(pts[i1], pts[i2], pt) < 0.0f)
+ r << i;
+ }
+ return r;
+ };
+ for (int i = 3; i < count; ++i) {
+ auto visible = connectableInHull(i);
+ if (visible.isEmpty())
+ continue;
+ int visCount = visible.count();
+ int hullCount = hull.count();
+ // Find where the visible part of the hull starts. (This is the part we need to triangulate to,
+ // and the part we're going to replace. "visible" contains the start point of the line segments that are visible from p.
+ int boundaryStart = visible[0];
+ for (int j = 0; j < visCount - 1; ++j) {
+ if ((visible[j] + 1) % hullCount != visible[j+1]) {
+ boundaryStart = visible[j + 1];
+ break;
+ }
+ }
+ // Finally replace the points that are now inside the hull
+ // We insert the new point after boundaryStart, and before boundaryStart + visCount (modulo...)
+ // and remove the points in between
+ int pointsToKeep = hullCount - visCount + 1;
+ QList<int> newHull;
+ newHull << i;
+ for (int j = 0; j < pointsToKeep; ++j) {
+ newHull << hull.at((j + boundaryStart + visCount) % hullCount);
+ }
+ hull = newHull;
+ }
+
+ // Now that we have a convex hull, we can trivially triangulate it
+ QList<TriangleData> ret;
+ for (int i = 1; i < hull.size() - 1; ++i) {
+ int i0 = hull[0];
+ int i1 = hull[i];
+ int i2 = hull[i+1];
+ ret.append({{pts[i0], pts[i1], pts[i2]}, elementIndex, {normals[i0], normals[i1], normals[i2]}});
+ }
+ return ret;
+}
+
+
+inline bool needsSplit(const QQuadPath::Element &el)
+{
+ Q_ASSERT(!el.isLine());
+ const auto v1 = el.controlPoint() - el.startPoint();
+ const auto v2 = el.endPoint() - el.controlPoint();
+ float cos = QVector2D::dotProduct(v1, v2) / (v1.length() * v2.length());
+ return cos < 0.9;
+}
+
+
+inline void splitElementIfNecessary(QQuadPath *path, int index, int level) {
+ if (level > 0 && needsSplit(path->elementAt(index))) {
+ path->splitElementAt(index);
+ splitElementIfNecessary(path, path->indexOfChildAt(index, 0), level - 1);
+ splitElementIfNecessary(path, path->indexOfChildAt(index, 1), level - 1);
+ }
+}
+
+static QQuadPath subdivide(const QQuadPath &path, int subdivisions)
+{
+ QQuadPath newPath = path;
+ newPath.iterateElements([&](QQuadPath::Element &e, int index) {
+ if (!e.isLine())
+ splitElementIfNecessary(&newPath, index, subdivisions);
+ });
+
+ return newPath;
+}
+
+static QList<TriangleData> customTriangulator2(const QQuadPath &path, float penWidth, Qt::PenJoinStyle joinStyle, Qt::PenCapStyle capStyle, float miterLimit)
+{
+ const bool bevelJoin = joinStyle == Qt::BevelJoin;
+ const bool roundJoin = joinStyle == Qt::RoundJoin;
+ const bool miterJoin = !bevelJoin && !roundJoin;
+
+ const bool roundCap = capStyle == Qt::RoundCap;
+ const bool squareCap = capStyle == Qt::SquareCap;
+ // We can't use the simple miter for miter joins, since the shader currently only supports round joins
+ const bool simpleMiter = joinStyle == Qt::RoundJoin;
+
+ Q_ASSERT(miterLimit > 0 || !miterJoin);
+ float inverseMiterLimit = miterJoin ? 1.0f / miterLimit : 1.0;
+
+ const float penFactor = penWidth / 2;
+
+ // Returns {inner1, inner2, outer1, outer2, outerMiter}
+ // where foo1 is for the end of element1 and foo2 is for the start of element2
+ // and inner1 == inner2 unless we had to give up finding a decent point
+ auto calculateJoin = [&](const QQuadPath::Element *element1, const QQuadPath::Element *element2,
+ bool &outerBisectorWithinMiterLimit, bool &innerIsRight, bool &giveUp) -> std::array<QVector2D, 5>
+ {
+ outerBisectorWithinMiterLimit = true;
+ innerIsRight = true;
+ giveUp = false;
+ if (!element1) {
+ Q_ASSERT(element2);
+ QVector2D n = normalVector(*element2);
+ return {n, n, -n, -n, -n};
+ }
+ if (!element2) {
+ Q_ASSERT(element1);
+ QVector2D n = normalVector(*element1, true);
+ return {n, n, -n, -n, -n};
+ }
+
+ Q_ASSERT(element1->endPoint() == element2->startPoint());
+
+ const auto p1 = element1->isLine() ? element1->startPoint() : element1->controlPoint();
+ const auto p2 = element1->endPoint();
+ const auto p3 = element2->isLine() ? element2->endPoint() : element2->controlPoint();
+
+ const auto v1 = (p1 - p2).normalized();
+ const auto v2 = (p3 - p2).normalized();
+ const auto b = (v1 + v2);
+
+ constexpr float epsilon = 1.0f / 32.0f;
+ bool smoothJoin = qAbs(b.x()) < epsilon && qAbs(b.y()) < epsilon;
+
+ if (smoothJoin) {
+ // v1 and v2 are almost parallel and pointing in opposite directions
+ // angle bisector formula will give an almost null vector: use normal of bisector of normals instead
+ QVector2D n1(-v1.y(), v1.x());
+ QVector2D n2(-v2.y(), v2.x());
+ QVector2D n = (n2 - n1).normalized();
+ return {n, n, -n, -n, -n};
+ }
+ // Calculate the length of the bisector, so it will cover the entire miter.
+ // Using the identity sin(x/2) == sqrt((1 - cos(x)) / 2), and the fact that the
+ // dot product of two unit vectors is the cosine of the angle between them
+ // The length of the miter is w/sin(x/2) where x is the angle between the two elements
+
+ const auto bisector = b.normalized();
+ float cos2x = QVector2D::dotProduct(v1, v2);
+ cos2x = qMin(1.0f, cos2x); // Allow for float inaccuracy
+ float sine = sqrt((1.0f - cos2x) / 2);
+ innerIsRight = determinant(p1, p2, p3) > 0;
+ sine = qMax(sine, 0.01f); // Avoid divide by zero
+ float length = penFactor / sine;
+
+ // Check if bisector is longer than one of the lines it's trying to bisect
+
+ auto tooLong = [](QVector2D p1, QVector2D p2, QVector2D n, float length, float margin) -> bool {
+ auto v = p2 - p1;
+ // It's too long if the projection onto the bisector is longer than the bisector
+ // and the projection onto the normal to the bisector is shorter
+ // than the pen margin (that projection is just v - proj)
+ // (we're adding a 10% safety margin to make room for AA -- not exact)
+ auto projLen = QVector2D::dotProduct(v, n);
+ return projLen * 0.9f < length && (v - n * projLen).length() * 0.9 < margin;
+ };
+
+
+ // The angle bisector of the tangent lines is not correct for curved lines. We could fix this by calculating
+ // the exact intersection point, but for now just give up and use the normals.
+
+ giveUp = !element1->isLine() || !element2->isLine()
+ || tooLong(p1, p2, bisector, length, penFactor)
+ || tooLong(p3, p2, bisector, length, penFactor);
+ outerBisectorWithinMiterLimit = sine >= inverseMiterLimit / 2.0f;
+ bool simpleJoin = simpleMiter && outerBisectorWithinMiterLimit && !giveUp;
+ const QVector2D bn = bisector / sine;
+
+ if (simpleJoin)
+ return {bn, bn, -bn, -bn, -bn}; // We only have one inner and one outer point TODO: change inner point when conflict/curve
+ const QVector2D n1 = normalVector(*element1, true);
+ const QVector2D n2 = normalVector(*element2);
+ if (giveUp) {
+ if (innerIsRight)
+ return {n1, n2, -n1, -n2, -bn};
+ else
+ return {-n1, -n2, n1, n2, -bn};
+
+ } else {
+ if (innerIsRight)
+ return {bn, bn, -n1, -n2, -bn};
+ else
+ return {bn, bn, n1, n2, -bn};
+ }
+ };
+
+ QList<TriangleData> ret;
+
+ auto triangulateCurve = [&](int idx, const QVector2D &p1, const QVector2D &p2, const QVector2D &p3, const QVector2D &p4,
+ const QVector2D &n1, const QVector2D &n2, const QVector2D &n3, const QVector2D &n4)
+ {
+ const auto &element = path.elementAt(idx);
+ Q_ASSERT(!element.isLine());
+ const auto &s = element.startPoint();
+ const auto &c = element.controlPoint();
+ const auto &e = element.endPoint();
+ // TODO: Don't flatten the path in addCurveStrokeNodes, but iterate over the children here instead
+ bool controlPointOnRight = determinant(s, c, e) > 0;
+ QVector2D startNormal = normalVector(element);
+ QVector2D endNormal = normalVector(element, true);
+ QVector2D controlPointNormal = (startNormal + endNormal).normalized();
+ if (controlPointOnRight)
+ controlPointNormal = -controlPointNormal;
+ QVector2D p5 = c + controlPointNormal * penFactor; // This is too simplistic
+ TrianglePoints t1{p1, p2, p5};
+ TrianglePoints t2{p3, p4, p5};
+ bool simpleCase = !checkTriangleOverlap(t1, t2);
+
+ if (simpleCase) {
+ ret.append({{p1, p2, p5}, idx, {n1, n2, controlPointNormal}});
+ ret.append({{p3, p4, p5}, idx, {n3, n4, controlPointNormal}});
+ if (controlPointOnRight) {
+ ret.append({{p1, p3, p5}, idx, {n1, n3, controlPointNormal}});
+ } else {
+ ret.append({{p2, p4, p5}, idx, {n2, n4, controlPointNormal}});
+ }
+ } else {
+ ret.append(simplePointTriangulator({p1, p2, p5, p3, p4}, {n1, n2, controlPointNormal, n3, n4}, idx));
+ }
+ };
+
+ // Each element is calculated independently, so we don't have to special-case closed sub-paths.
+ // Take care so the end points of one element are precisely equal to the start points of the next.
+ // Any additional triangles needed for joining are added at the end of the current element.
+
+ int count = path.elementCount();
+ int subStart = 0;
+ while (subStart < count) {
+ int subEnd = subStart;
+ for (int i = subStart + 1; i < count; ++i) {
+ const auto &e = path.elementAt(i);
+ if (e.isSubpathStart()) {
+ subEnd = i - 1;
+ break;
+ }
+ if (i == count - 1) {
+ subEnd = i;
+ break;
+ }
+ }
+ bool closed = path.elementAt(subStart).startPoint() == path.elementAt(subEnd).endPoint();
+ const int subCount = subEnd - subStart + 1;
+
+ auto addIdx = [&](int idx, int delta) -> int {
+ int subIdx = idx - subStart;
+ if (closed)
+ subIdx = (subIdx + subCount + delta) % subCount;
+ else
+ subIdx += delta;
+ return subStart + subIdx;
+ };
+ auto elementAt = [&](int idx, int delta) -> const QQuadPath::Element * {
+ int subIdx = idx - subStart;
+ if (closed) {
+ subIdx = (subIdx + subCount + delta) % subCount;
+ return &path.elementAt(subStart + subIdx);
+ }
+ subIdx += delta;
+ if (subIdx >= 0 && subIdx < subCount)
+ return &path.elementAt(subStart + subIdx);
+ return nullptr;
+ };
+
+ for (int i = subStart; i <= subEnd; ++i) {
+ const auto &element = path.elementAt(i);
+ const auto *nextElement = elementAt(i, +1);
+ const auto *prevElement = elementAt(i, -1);
+
+ const auto &s = element.startPoint();
+ const auto &e = element.endPoint();
+
+ bool startInnerIsRight;
+ bool startBisectorWithinMiterLimit; // Not used
+ bool giveUpOnStartJoin; // Not used
+ auto startJoin = calculateJoin(prevElement, &element,
+ startBisectorWithinMiterLimit, startInnerIsRight,
+ giveUpOnStartJoin);
+ const QVector2D &startInner = startJoin[1];
+ const QVector2D &startOuter = startJoin[3];
+
+ bool endInnerIsRight;
+ bool endBisectorWithinMiterLimit;
+ bool giveUpOnEndJoin;
+ auto endJoin = calculateJoin(&element, nextElement,
+ endBisectorWithinMiterLimit, endInnerIsRight,
+ giveUpOnEndJoin);
+ QVector2D endInner = endJoin[0];
+ QVector2D endOuter = endJoin[2];
+ QVector2D nextOuter = endJoin[3];
+ QVector2D outerB = endJoin[4];
+
+ QVector2D p1, p2, p3, p4;
+ QVector2D n1, n2, n3, n4;
+
+ if (startInnerIsRight) {
+ n1 = startInner;
+ n2 = startOuter;
+ } else {
+ n1 = startOuter;
+ n2 = startInner;
+ }
+
+ p1 = s + n1 * penFactor;
+ p2 = s + n2 * penFactor;
+
+ // repeat logic above for the other end:
+ if (endInnerIsRight) {
+ n3 = endInner;
+ n4 = endOuter;
+ } else {
+ n3 = endOuter;
+ n4 = endInner;
+ }
+
+ p3 = e + n3 * penFactor;
+ p4 = e + n4 * penFactor;
+
+ // End caps
+
+ if (!prevElement) {
+ QVector2D capSpace = tangentVector(element).normalized() * -penFactor;
+ if (roundCap) {
+ p1 += capSpace;
+ p2 += capSpace;
+ } else if (squareCap) {
+ QVector2D c1 = p1 + capSpace;
+ QVector2D c2 = p2 + capSpace;
+ ret.append({{p1, s, c1}, -1, {}});
+ ret.append({{c1, s, c2}, -1, {}});
+ ret.append({{p2, s, c2}, -1, {}});
+ }
+ }
+ if (!nextElement) {
+ QVector2D capSpace = tangentVector(element, true).normalized() * -penFactor;
+ if (roundCap) {
+ p3 += capSpace;
+ p4 += capSpace;
+ } else if (squareCap) {
+ QVector2D c3 = p3 + capSpace;
+ QVector2D c4 = p4 + capSpace;
+ ret.append({{p3, e, c3}, -1, {}});
+ ret.append({{c3, e, c4}, -1, {}});
+ ret.append({{p4, e, c4}, -1, {}});
+ }
+ }
+
+ if (element.isLine()) {
+ ret.append({{p1, p2, p3}, i, {n1, n2, n3}});
+ ret.append({{p2, p3, p4}, i, {n2, n3, n4}});
+ } else {
+ triangulateCurve(i, p1, p2, p3, p4, n1, n2, n3, n4);
+ }
+
+ bool trivialJoin = simpleMiter && endBisectorWithinMiterLimit && !giveUpOnEndJoin;
+ if (!trivialJoin && nextElement) {
+ // inside of join (opposite of bevel) is defined by
+ // triangle s, e, next.e
+ bool innerOnRight = endInnerIsRight;
+
+ const auto outer1 = e + endOuter * penFactor;
+ const auto outer2 = e + nextOuter * penFactor;
+ //const auto inner = e + endInner * penFactor;
+
+ if (bevelJoin || (miterJoin && !endBisectorWithinMiterLimit)) {
+ ret.append({{outer1, e, outer2}, -1, {}});
+ } else if (roundJoin) {
+ ret.append({{outer1, e, outer2}, i, {}});
+ QVector2D nn = calcNormalVector(outer1, outer2).normalized() * penFactor;
+ if (!innerOnRight)
+ nn = -nn;
+ ret.append({{outer1, outer1 + nn, outer2}, i, {}});
+ ret.append({{outer1 + nn, outer2, outer2 + nn}, i, {}});
+
+ } else if (miterJoin) {
+ QVector2D outer = e + outerB * penFactor;
+ ret.append({{outer1, e, outer}, -2, {}});
+ ret.append({{outer, e, outer2}, -2, {}});
+ }
+
+ if (!giveUpOnEndJoin) {
+ QVector2D inner = e + endInner * penFactor;
+ ret.append({{inner, e, outer1}, i, {endInner, {}, endOuter}});
+ // The remaining triangle ought to be done by nextElement, but we don't have start join logic there (yet)
+ int nextIdx = addIdx(i, +1);
+ ret.append({{inner, e, outer2}, nextIdx, {endInner, {}, nextOuter}});
+ }
+ }
+ }
+ subStart = subEnd + 1;
+ }
+ return ret;
+}
+
+// TODO: we could optimize by preprocessing e1, since we call this function multiple times on the same
+// elements
+// Returns true if a change was made
+static bool handleOverlap(QQuadPath &path, int e1, int e2, int recursionLevel = 0)
+{
+ // Splitting lines is not going to help with overlap, since we assume that lines don't intersect
+ if (path.elementAt(e1).isLine() && path.elementAt(e1).isLine())
+ return false;
+
+ if (!isOverlap(path, e1, e2)) {
+ return false;
+ }
+
+ if (recursionLevel > 8) {
+ qCDebug(lcSGCurveProcessor) << "Triangle overlap: recursion level" << recursionLevel << "aborting!";
+ return false;
+ }
+
+ if (path.elementAt(e1).childCount() > 1) {
+ auto e11 = path.indexOfChildAt(e1, 0);
+ auto e12 = path.indexOfChildAt(e1, 1);
+ handleOverlap(path, e11, e2, recursionLevel + 1);
+ handleOverlap(path, e12, e2, recursionLevel + 1);
+ } else if (path.elementAt(e2).childCount() > 1) {
+ auto e21 = path.indexOfChildAt(e2, 0);
+ auto e22 = path.indexOfChildAt(e2, 1);
+ handleOverlap(path, e1, e21, recursionLevel + 1);
+ handleOverlap(path, e1, e22, recursionLevel + 1);
+ } else {
+ path.splitElementAt(e1);
+ auto e11 = path.indexOfChildAt(e1, 0);
+ auto e12 = path.indexOfChildAt(e1, 1);
+ bool overlap1 = isOverlap(path, e11, e2);
+ bool overlap2 = isOverlap(path, e12, e2);
+ if (!overlap1 && !overlap2)
+ return true; // no more overlap: success!
+
+ // We need to split more:
+ if (path.elementAt(e2).isLine()) {
+ // Splitting a line won't help, so we just split e1 further
+ if (overlap1)
+ handleOverlap(path, e11, e2, recursionLevel + 1);
+ if (overlap2)
+ handleOverlap(path, e12, e2, recursionLevel + 1);
+ } else {
+ // See if splitting e2 works:
+ path.splitElementAt(e2);
+ auto e21 = path.indexOfChildAt(e2, 0);
+ auto e22 = path.indexOfChildAt(e2, 1);
+ if (overlap1) {
+ handleOverlap(path, e11, e21, recursionLevel + 1);
+ handleOverlap(path, e11, e22, recursionLevel + 1);
+ }
+ if (overlap2) {
+ handleOverlap(path, e12, e21, recursionLevel + 1);
+ handleOverlap(path, e12, e22, recursionLevel + 1);
+ }
+ }
+ }
+ return true;
+}
+}
+
+// Returns true if the path was changed
+bool QSGCurveProcessor::solveOverlaps(QQuadPath &path)
+{
+ bool changed = false;
+ if (path.testHint(QQuadPath::PathNonOverlappingControlPointTriangles))
+ return false;
+
+ const auto candidates = findOverlappingCandidates(path);
+ for (auto candidate : candidates)
+ changed = handleOverlap(path, candidate.first, candidate.second) || changed;
+
+ path.setHint(QQuadPath::PathNonOverlappingControlPointTriangles);
+ return changed;
+}
+
+// A fast algorithm to find path elements that might overlap. We will only check the overlap of the
+// triangles that define the elements.
+// We will order the elements first and then pool them depending on their x-values. This should
+// reduce the complexity to O(n log n), where n is the number of elements in the path.
+QList<QPair<int, int>> QSGCurveProcessor::findOverlappingCandidates(const QQuadPath &path)
+{
+ struct BRect { float xmin; float xmax; float ymin; float ymax; };
+
+ // Calculate all bounding rectangles
+ QVarLengthArray<int, 64> elementStarts, elementEnds;
+ QVarLengthArray<BRect, 64> boundingRects;
+ elementStarts.reserve(path.elementCount());
+ boundingRects.reserve(path.elementCount());
+ for (int i = 0; i < path.elementCount(); i++) {
+ QQuadPath::Element e = path.elementAt(i);
+
+ BRect bR{qMin(qMin(e.startPoint().x(), e.controlPoint().x()), e.endPoint().x()),
+ qMax(qMax(e.startPoint().x(), e.controlPoint().x()), e.endPoint().x()),
+ qMin(qMin(e.startPoint().y(), e.controlPoint().y()), e.endPoint().y()),
+ qMax(qMax(e.startPoint().y(), e.controlPoint().y()), e.endPoint().y())};
+ boundingRects.append(bR);
+ elementStarts.append(i);
+ }
+
+ // Sort the bounding rectangles by x-startpoint and x-endpoint
+ auto compareXmin = [&](int i, int j){return boundingRects.at(i).xmin < boundingRects.at(j).xmin;};
+ auto compareXmax = [&](int i, int j){return boundingRects.at(i).xmax < boundingRects.at(j).xmax;};
+ std::sort(elementStarts.begin(), elementStarts.end(), compareXmin);
+ elementEnds = elementStarts;
+ std::sort(elementEnds.begin(), elementEnds.end(), compareXmax);
+
+ QList<int> bRpool;
+ QList<QPair<int, int>> overlappingBB;
+
+ // Start from x = xmin and move towards xmax. Add a rectangle to the pool and check for
+ // intersections with all other rectangles in the pool. If a rectangles xmax is smaller
+ // than the new xmin, it can be removed from the pool.
+ int firstElementEnd = 0;
+ for (const int addIndex : std::as_const(elementStarts)) {
+ const BRect &newR = boundingRects.at(addIndex);
+ // First remove elements from the pool that cannot touch the new one
+ // because xmax is too small
+ while (bRpool.size() && firstElementEnd < elementEnds.size()) {
+ int removeIndex = elementEnds.at(firstElementEnd);
+ if (bRpool.contains(removeIndex) && newR.xmin > boundingRects.at(removeIndex).xmax) {
+ bRpool.removeOne(removeIndex);
+ firstElementEnd++;
+ } else {
+ break;
+ }
+ }
+
+ // Now compare the new element with all elements in the pool.
+ for (int j = 0; j < bRpool.size(); j++) {
+ int i = bRpool.at(j);
+ const BRect &r1 = boundingRects.at(i);
+ // We don't have to check for x because the pooling takes care of it.
+ //if (r1.xmax <= newR.xmin || newR.xmax <= r1.xmin)
+ // continue;
+
+ bool isNeighbor = false;
+ if (i - addIndex == 1) {
+ if (!path.elementAt(addIndex).isSubpathEnd())
+ isNeighbor = true;
+ } else if (addIndex - i == 1) {
+ if (!path.elementAt(i).isSubpathEnd())
+ isNeighbor = true;
+ }
+ // Neighbors need to be completely different (otherwise they just share a point)
+ if (isNeighbor && (r1.ymax <= newR.ymin || newR.ymax <= r1.ymin))
+ continue;
+ // Non-neighbors can also just touch
+ if (!isNeighbor && (r1.ymax < newR.ymin || newR.ymax < r1.ymin))
+ continue;
+ // If the bounding boxes are overlapping it is a candidate for an intersection.
+ overlappingBB.append(QPair<int, int>(i, addIndex));
+ }
+ bRpool.append(addIndex); //Add the new element to the pool.
+ }
+ return overlappingBB;
+}
+
+// Remove paths that are nested inside another path and not required to fill the path correctly
+bool QSGCurveProcessor::removeNestedSubpaths(QQuadPath &path)
+{
+ // Ensure that the path is not intersecting first
+ Q_ASSERT(path.testHint(QQuadPath::PathNonIntersecting));
+
+ if (path.fillRule() != Qt::WindingFill) {
+ // If the fillingRule is odd-even, all internal subpaths matter
+ return false;
+ }
+
+ // Store the starting and end elements of the subpaths to be able
+ // to jump quickly between them.
+ QList<int> subPathStartPoints;
+ QList<int> subPathEndPoints;
+ for (int i = 0; i < path.elementCount(); i++) {
+ if (path.elementAt(i).isSubpathStart())
+ subPathStartPoints.append(i);
+ if (path.elementAt(i).isSubpathEnd()) {
+ subPathEndPoints.append(i);
+ }
+ }
+ const int subPathCount = subPathStartPoints.size();
+
+ // If there is only one subpath, none have to be removed
+ if (subPathStartPoints.size() < 2)
+ return false;
+
+ // We set up a matrix that tells us which path is nested in which other path.
+ QList<bool> isInside;
+ bool isAnyInside = false;
+ isInside.reserve(subPathStartPoints.size() * subPathStartPoints.size());
+ for (int i = 0; i < subPathCount; i++) {
+ for (int j = 0; j < subPathCount; j++) {
+ if (i == j) {
+ isInside.append(false);
+ } else {
+ isInside.append(path.contains(path.elementAt(subPathStartPoints.at(i)).startPoint(),
+ subPathStartPoints.at(j), subPathEndPoints.at(j)));
+ if (isInside.last())
+ isAnyInside = true;
+ }
+ }
+ }
+
+ // If no nested subpaths are present we can return early.
+ if (!isAnyInside)
+ return false;
+
+ // To find out which paths are filled and which not, we first calculate the
+ // rotation direction (clockwise - counterclockwise).
+ QList<bool> clockwise;
+ clockwise.reserve(subPathCount);
+ for (int i = 0; i < subPathCount; i++) {
+ float sumProduct = 0;
+ for (int j = subPathStartPoints.at(i); j <= subPathEndPoints.at(i); j++) {
+ const QVector2D startPoint = path.elementAt(j).startPoint();
+ const QVector2D endPoint = path.elementAt(j).endPoint();
+ sumProduct += (endPoint.x() - startPoint.x()) * (endPoint.y() + startPoint.y());
+ }
+ clockwise.append(sumProduct > 0);
+ }
+
+ // Set up a list that tells us which paths create filling and which path create holes.
+ // Holes in Holes and fillings in fillings can then be removed.
+ QList<bool> isFilled;
+ isFilled.reserve(subPathStartPoints.size() );
+ for (int i = 0; i < subPathCount; i++) {
+ int crossings = clockwise.at(i) ? 1 : -1;
+ for (int j = 0; j < subPathStartPoints.size(); j++) {
+ if (isInside.at(i * subPathCount + j))
+ crossings += clockwise.at(j) ? 1 : -1;
+ }
+ isFilled.append(crossings != 0);
+ }
+
+ // A helper function to find the most inner subpath that is around a subpath.
+ // Returns -1 if the subpath is a toplevel subpath.
+ auto findClosestOuterSubpath = [&](int subPath) {
+ // All paths that contain the current subPath are candidates.
+ QList<int> candidates;
+ for (int i = 0; i < subPathStartPoints.size(); i++) {
+ if (isInside.at(subPath * subPathCount + i))
+ candidates.append(i);
+ }
+ int maxNestingLevel = -1;
+ int maxNestingLevelIndex = -1;
+ for (int i = 0; i < candidates.size(); i++) {
+ int nestingLevel = 0;
+ for (int j = 0; j < candidates.size(); j++) {
+ if (isInside.at(candidates.at(i) * subPathCount + candidates.at(j))) {
+ nestingLevel++;
+ }
+ }
+ if (nestingLevel > maxNestingLevel) {
+ maxNestingLevel = nestingLevel;
+ maxNestingLevelIndex = candidates.at(i);
+ }
+ }
+ return maxNestingLevelIndex;
+ };
+
+ bool pathChanged = false;
+ QQuadPath fixedPath;
+ fixedPath.setPathHints(path.pathHints());
+
+ // Go through all subpaths and find the closest surrounding subpath.
+ // If it is doing the same (create filling or create hole) we can remove it.
+ for (int i = 0; i < subPathCount; i++) {
+ int j = findClosestOuterSubpath(i);
+ if (j >= 0 && isFilled.at(i) == isFilled.at(j)) {
+ pathChanged = true;
+ } else {
+ for (int k = subPathStartPoints.at(i); k <= subPathEndPoints.at(i); k++)
+ fixedPath.addElement(path.elementAt(k));
+ }
+ }
+
+ if (pathChanged)
+ path = fixedPath;
+ return pathChanged;
+}
+
+// Returns true if the path was changed
+bool QSGCurveProcessor::solveIntersections(QQuadPath &path, bool removeNestedPaths)
+{
+ if (path.testHint(QQuadPath::PathNonIntersecting)) {
+ if (removeNestedPaths)
+ return removeNestedSubpaths(path);
+ else
+ return false;
+ }
+
+ if (path.elementCount() < 2) {
+ path.setHint(QQuadPath::PathNonIntersecting);
+ return false;
+ }
+
+ struct IntersectionData { int e1; int e2; float t1; float t2; bool in1 = false, in2 = false, out1 = false, out2 = false; };
+ QList<IntersectionData> intersections;
+
+ // Helper function to mark an intersection as handled when the
+ // path i is processed moving forward/backward
+ auto markIntersectionAsHandled = [=](IntersectionData *data, int i, bool forward) {
+ if (data->e1 == i) {
+ if (forward)
+ data->out1 = true;
+ else
+ data->in1 = true;
+ } else if (data->e2 == i){
+ if (forward)
+ data->out2 = true;
+ else
+ data->in2 = true;
+ } else {
+ Q_UNREACHABLE();
+ }
+ };
+
+ // First make a O(n log n) search for candidates.
+ const QList<QPair<int, int>> candidates = findOverlappingCandidates(path);
+ // Then check the candidates for actual intersections.
+ for (const auto &candidate : candidates) {
+ QList<QPair<float,float>> res;
+ int e1 = candidate.first;
+ int e2 = candidate.second;
+ if (isIntersecting(path, e1, e2, &res)) {
+ for (const auto &r : res)
+ intersections.append({e1, e2, r.first, r.second});
+ }
+ }
+
+ qCDebug(lcSGCurveIntersectionSolver) << "----- Checking for Intersections -----";
+ qCDebug(lcSGCurveIntersectionSolver) << "Found" << intersections.length() << "intersections";
+ if (lcSGCurveIntersectionSolver().isDebugEnabled()) {
+ for (const auto &i : intersections) {
+ auto p1 = path.elementAt(i.e1).pointAtFraction(i.t1);
+ auto p2 = path.elementAt(i.e2).pointAtFraction(i.t2);
+ qCDebug(lcSGCurveIntersectionSolver) << " between" << i.e1 << "and" << i.e2 << "at" << i.t1 << "/" << i.t2 << "->" << p1 << "/" << p2;
+ }
+ }
+
+ if (intersections.isEmpty()) {
+ path.setHint(QQuadPath::PathNonIntersecting);
+ if (removeNestedPaths) {
+ qCDebug(lcSGCurveIntersectionSolver) << "No Intersections found. Looking for enclosed subpaths.";
+ return removeNestedSubpaths(path);
+ } else {
+ qCDebug(lcSGCurveIntersectionSolver) << "Returning the path unchanged.";
+ return false;
+ }
+ }
+
+
+ // Store the starting and end elements of the subpaths to be able
+ // to jump quickly between them. Also keep a list of handled paths,
+ // so we know if we need to come back to a subpath or if it
+ // was already united with another subpath due to an intersection.
+ QList<int> subPathStartPoints;
+ QList<int> subPathEndPoints;
+ QList<bool> subPathHandled;
+ for (int i = 0; i < path.elementCount(); i++) {
+ if (path.elementAt(i).isSubpathStart())
+ subPathStartPoints.append(i);
+ if (path.elementAt(i).isSubpathEnd()) {
+ subPathEndPoints.append(i);
+ subPathHandled.append(false);
+ }
+ }
+
+ // A small helper to find the subPath of an element with index
+ auto subPathIndex = [&](int index) {
+ for (int i = 0; i < subPathStartPoints.size(); i++) {
+ if (index >= subPathStartPoints.at(i) && index <= subPathEndPoints.at(i))
+ return i;
+ }
+ return -1;
+ };
+
+ // Helper to ensure that index i and position t are valid:
+ auto ensureInBounds = [&](int *i, float *t, float deltaT) {
+ if (*t <= 0.f) {
+ if (path.elementAt(*i).isSubpathStart())
+ *i = subPathEndPoints.at(subPathIndex(*i));
+ else
+ *i = *i - 1;
+ *t = 1.f - deltaT;
+ } else if (*t >= 1.f) {
+ if (path.elementAt(*i).isSubpathEnd())
+ *i = subPathStartPoints.at(subPathIndex(*i));
+ else
+ *i = *i + 1;
+ *t = deltaT;
+ }
+ };
+
+ // Helper function to find a siutable starting point between start and end.
+ // A suitable starting point is where right is inside and left is outside
+ // If left is inside and right is outside it works too, just move in the
+ // other direction (forward = false).
+ auto findStart = [=](QQuadPath &path, int start, int end, int *result, bool *forward) {
+ for (int i = start; i < end; i++) {
+ int adjecent;
+ if (subPathStartPoints.contains(i))
+ adjecent = subPathEndPoints.at(subPathStartPoints.indexOf(i));
+ else
+ adjecent = i - 1;
+
+ QQuadPath::Element::FillSide fillSide = path.fillSideOf(i, 1e-4f);
+ const bool leftInside = fillSide == QQuadPath::Element::FillSideLeft;
+ const bool rightInside = fillSide == QQuadPath::Element::FillSideRight;
+ qCDebug(lcSGCurveIntersectionSolver) << "Element" << i << "/" << adjecent << "meeting point is left/right inside:" << leftInside << "/" << rightInside;
+ if (rightInside) {
+ *result = i;
+ *forward = true;
+ return true;
+ } else if (leftInside) {
+ *result = adjecent;
+ *forward = false;
+ return true;
+ }
+ }
+ return false;
+ };
+
+ // Also store handledElements (handled is when we touch the start point).
+ // This is used to identify and abort on errors.
+ QVarLengthArray<bool> handledElements(path.elementCount(), false);
+ // Only store handledElements when it is not touched due to an intersection.
+ bool regularVisit = true;
+
+ QQuadPath fixedPath;
+ fixedPath.setFillRule(path.fillRule());
+
+ int i1 = 0;
+ float t1 = 0;
+
+ int i2 = 0;
+ float t2 = 0;
+
+ float t = 0;
+ bool forward = true;
+
+ int startedAtIndex = -1;
+ float startedAtT = -1;
+
+ if (!findStart(path, 0, path.elementCount(), &i1, &forward)) {
+ qCDebug(lcSGCurveIntersectionSolver) << "No suitable start found. This should not happen. Returning the path unchanged.";
+ return false;
+ }
+
+ // Helper function to start a new subpath and update temporary variables.
+ auto startNewSubPath = [&](int i, bool forward) {
+ if (forward) {
+ fixedPath.moveTo(path.elementAt(i).startPoint());
+ t = startedAtT = 0;
+ } else {
+ fixedPath.moveTo(path.elementAt(i).endPoint());
+ t = startedAtT = 1;
+ }
+ startedAtIndex = i;
+ subPathHandled[subPathIndex(i)] = true;
+ };
+ startNewSubPath(i1, forward);
+
+ // If not all interactions where found correctly, we might end up in an infinite loop.
+ // Therefore we count the total number of iterations and bail out at some point.
+ int totalIterations = 0;
+
+ // We need to store the last intersection so we don't jump back and forward immediately.
+ int prevIntersection = -1;
+
+ do {
+ // Sanity check: Make sure that we do not process the same corner point more than once.
+ if (regularVisit && (t == 0 || t == 1)) {
+ int nextIndex = i1;
+ if (t == 1 && path.elementAt(i1).isSubpathEnd()) {
+ nextIndex = subPathStartPoints.at(subPathIndex(i1));
+ } else if (t == 1) {
+ nextIndex = nextIndex + 1;
+ }
+ if (handledElements[nextIndex]) {
+ qCDebug(lcSGCurveIntersectionSolver) << "Revisiting an element when trying to solve intersections. This should not happen. Returning the path unchanged.";
+ return false;
+ }
+ handledElements[nextIndex] = true;
+ }
+ // Sanity check: Keep an eye on total iterations
+ totalIterations++;
+
+ qCDebug(lcSGCurveIntersectionSolver) << "Checking section" << i1 << "from" << t << "going" << (forward ? "forward" : "backward");
+
+ // Find the next intersection that is as close as possible to t but in direction of processing (forward or !forward).
+ int iC = -1; //intersection candidate
+ t1 = forward? 1 : -1; //intersection candidate t-value
+ for (int j = 0; j < intersections.size(); j++) {
+ if (j == prevIntersection)
+ continue;
+ if (i1 == intersections[j].e1 &&
+ intersections[j].t1 * (forward ? 1 : -1) >= t * (forward ? 1 : -1) &&
+ intersections[j].t1 * (forward ? 1 : -1) < t1 * (forward ? 1 : -1)) {
+ iC = j;
+ t1 = intersections[j].t1;
+ i2 = intersections[j].e2;
+ t2 = intersections[j].t2;
+ }
+ if (i1 == intersections[j].e2 &&
+ intersections[j].t2 * (forward ? 1 : -1) >= t * (forward ? 1 : -1) &&
+ intersections[j].t2 * (forward ? 1 : -1) < t1 * (forward ? 1 : -1)) {
+ iC = j;
+ t1 = intersections[j].t2;
+ i2 = intersections[j].e1;
+ t2 = intersections[j].t1;
+ }
+ }
+ prevIntersection = iC;
+
+ if (iC < 0) {
+ qCDebug(lcSGCurveIntersectionSolver) << " No intersection found on my way. Adding the rest of the segment " << i1;
+ regularVisit = true;
+ // If no intersection with the current element was found, just add the rest of the element
+ // to the fixed path and go on.
+ // If we reached the end (going forward) or start (going backward) of a subpath, we have
+ // to wrap aroud. Abort condition for the loop comes separately later.
+ if (forward) {
+ if (path.elementAt(i1).isLine()) {
+ fixedPath.lineTo(path.elementAt(i1).endPoint());
+ } else {
+ const QQuadPath::Element rest = path.elementAt(i1).segmentFromTo(t, 1);
+ fixedPath.quadTo(rest.controlPoint(), rest.endPoint());
+ }
+ if (path.elementAt(i1).isSubpathEnd()) {
+ int index = subPathEndPoints.indexOf(i1);
+ qCDebug(lcSGCurveIntersectionSolver) << " Going back to the start of subPath" << index;
+ i1 = subPathStartPoints.at(index);
+ } else {
+ i1++;
+ }
+ t = 0;
+ } else {
+ if (path.elementAt(i1).isLine()) {
+ fixedPath.lineTo(path.elementAt(i1).startPoint());
+ } else {
+ const QQuadPath::Element rest = path.elementAt(i1).segmentFromTo(0, t).reversed();
+ fixedPath.quadTo(rest.controlPoint(), rest.endPoint());
+ }
+ if (path.elementAt(i1).isSubpathStart()) {
+ int index = subPathStartPoints.indexOf(i1);
+ qCDebug(lcSGCurveIntersectionSolver) << " Going back to the end of subPath" << index;
+ i1 = subPathEndPoints.at(index);
+ } else {
+ i1--;
+ }
+ t = 1;
+ }
+ } else { // Here comes the part where we actually handle intersections.
+ qCDebug(lcSGCurveIntersectionSolver) << " Found an intersection at" << t1 << "with" << i2 << "at" << t2;
+
+ // Mark the subpath we intersected with as visisted. We do not have to come here explicitly again.
+ subPathHandled[subPathIndex(i2)] = true;
+
+ // Mark the path that lead us to this intersection as handled on the intersection level.
+ // Note the ! in front of forward. This is required because we move towards the intersection.
+ markIntersectionAsHandled(&intersections[iC], i1, !forward);
+
+ // Split the path from the last point to the newly found intersection.
+ // Add the part of the current segment to the fixedPath.
+ const QQuadPath::Element &elem1 = path.elementAt(i1);
+ if (elem1.isLine()) {
+ fixedPath.lineTo(elem1.pointAtFraction(t1));
+ } else {
+ QQuadPath::Element partUntilIntersection;
+ if (forward) {
+ partUntilIntersection = elem1.segmentFromTo(t, t1);
+ } else {
+ partUntilIntersection = elem1.segmentFromTo(t1, t).reversed();
+ }
+ fixedPath.quadTo(partUntilIntersection.controlPoint(), partUntilIntersection.endPoint());
+ }
+
+ // If only one unhandled path is left the decision how to proceed is trivial
+ if (intersections[iC].in1 && intersections[iC].in2 && intersections[iC].out1 && !intersections[iC].out2) {
+ i1 = intersections[iC].e2;
+ t = intersections[iC].t2;
+ forward = true;
+ } else if (intersections[iC].in1 && intersections[iC].in2 && !intersections[iC].out1 && intersections[iC].out2) {
+ i1 = intersections[iC].e1;
+ t = intersections[iC].t1;
+ forward = true;
+ } else if (intersections[iC].in1 && !intersections[iC].in2 && intersections[iC].out1 && intersections[iC].out2) {
+ i1 = intersections[iC].e2;
+ t = intersections[iC].t2;
+ forward = false;
+ } else if (!intersections[iC].in1 && intersections[iC].in2 && intersections[iC].out1 && intersections[iC].out2) {
+ i1 = intersections[iC].e1;
+ t = intersections[iC].t1;
+ forward = false;
+ } else {
+ // If no trivial path is left, calculate the intersection angle to decide which path to move forward.
+ // For winding fill we take the left most path forward, so the inside stays on the right side
+ // For odd even fill we take the right most path forward so we cut of the smallest area.
+ // We come back at the intersection and add the missing pieces as subpaths later on.
+ if (t1 !=0 && t1 != 1 && t2 != 0 && t2 != 1) {
+ QVector2D tangent1 = elem1.tangentAtFraction(t1);
+ if (!forward)
+ tangent1 = -tangent1;
+ const QQuadPath::Element &elem2 = path.elementAt(i2);
+ const QVector2D tangent2 = elem2.tangentAtFraction(t2);
+ const float angle = angleBetween(-tangent1, tangent2);
+ qCDebug(lcSGCurveIntersectionSolver) << " Angle at intersection is" << angle;
+ // A small angle. Everything smaller is interpreted as tangent
+ constexpr float deltaAngle = 1e-3f;
+ if ((angle > deltaAngle && path.fillRule() == Qt::WindingFill) || (angle < -deltaAngle && path.fillRule() == Qt::OddEvenFill)) {
+ forward = true;
+ i1 = i2;
+ t = t2;
+ qCDebug(lcSGCurveIntersectionSolver) << " Next going forward from" << t << "on" << i1;
+ } else if ((angle < -deltaAngle && path.fillRule() == Qt::WindingFill) || (angle > deltaAngle && path.fillRule() == Qt::OddEvenFill)) {
+ forward = false;
+ i1 = i2;
+ t = t2;
+ qCDebug(lcSGCurveIntersectionSolver) << " Next going backward from" << t << "on" << i1;
+ } else { // this is basically a tangential touch and and no crossing. So stay on the current path, keep direction
+ qCDebug(lcSGCurveIntersectionSolver) << " Found tangent. Staying on element";
+ }
+ } else {
+ // If we are intersecting exactly at a corner, the trick with the angle does not help.
+ // Therefore we have to rely on finding the next path by looking forward and see if the
+ // path there is valid. This is more expensive than the method above and is therefore
+ // just used as a fallback for corner cases.
+ constexpr float deltaT = 1e-4f;
+ int i2after = i2;
+ float t2after = t2 + deltaT;
+ ensureInBounds(&i2after, &t2after, deltaT);
+ QQuadPath::Element::FillSide fillSideForwardNew = path.fillSideOf(i2after, t2after);
+ if (fillSideForwardNew == QQuadPath::Element::FillSideRight) {
+ forward = true;
+ i1 = i2;
+ t = t2;
+ qCDebug(lcSGCurveIntersectionSolver) << " Next going forward from" << t << "on" << i1;
+ } else {
+ int i2before = i2;
+ float t2before = t2 - deltaT;
+ ensureInBounds(&i2before, &t2before, deltaT);
+ QQuadPath::Element::FillSide fillSideBackwardNew = path.fillSideOf(i2before, t2before);
+ if (fillSideBackwardNew == QQuadPath::Element::FillSideLeft) {
+ forward = false;
+ i1 = i2;
+ t = t2;
+ qCDebug(lcSGCurveIntersectionSolver) << " Next going backward from" << t << "on" << i1;
+ } else {
+ qCDebug(lcSGCurveIntersectionSolver) << " Staying on element.";
+ }
+ }
+ }
+ }
+
+ // Mark the path that takes us away from this intersection as handled on the intersection level.
+ if (!(i1 == startedAtIndex && t == startedAtT))
+ markIntersectionAsHandled(&intersections[iC], i1, forward);
+
+ // If we took all paths from an intersection it can be deleted.
+ if (intersections[iC].in1 && intersections[iC].in2 && intersections[iC].out1 && intersections[iC].out2) {
+ qCDebug(lcSGCurveIntersectionSolver) << " This intersection was processed completely and will be removed";
+ intersections.removeAt(iC);
+ prevIntersection = -1;
+ }
+ regularVisit = false;
+ }
+
+ if (i1 == startedAtIndex && t == startedAtT) {
+ // We reached the point on the subpath where we started. This subpath is done.
+ // We have to find an unhandled subpath or a new subpath that was generated by cuts/intersections.
+ qCDebug(lcSGCurveIntersectionSolver) << "Reached my starting point and try to find a new subpath.";
+
+ // Search for the next subpath to handle.
+ int nextUnhandled = -1;
+ for (int i = 0; i < subPathHandled.size(); i++) {
+ if (!subPathHandled.at(i)) {
+
+ // Not nesesarrily handled (if findStart return false) but if we find no starting
+ // point, we cannot/don't need to handle it anyway. So just mark it as handled.
+ subPathHandled[i] = true;
+
+ if (findStart(path, subPathStartPoints.at(i), subPathEndPoints.at(i), &i1, &forward)) {
+ nextUnhandled = i;
+ qCDebug(lcSGCurveIntersectionSolver) << "Found a new subpath" << i << "to be processed.";
+ startNewSubPath(i1, forward);
+ regularVisit = true;
+ break;
+ }
+ }
+ }
+
+ // If no valid subpath is left, we have to go back to the unhandled intersections.
+ while (nextUnhandled < 0) {
+ qCDebug(lcSGCurveIntersectionSolver) << "All subpaths handled. Looking for unhandled intersections.";
+ if (intersections.isEmpty()) {
+ qCDebug(lcSGCurveIntersectionSolver) << "All intersections handled. I am done.";
+ fixedPath.setHint(QQuadPath::PathNonIntersecting);
+ path = fixedPath;
+ return true;
+ }
+
+ IntersectionData &unhandledIntersec = intersections[0];
+ prevIntersection = 0;
+ regularVisit = false;
+ qCDebug(lcSGCurveIntersectionSolver) << "Revisiting intersection of" << unhandledIntersec.e1 << "with" << unhandledIntersec.e2;
+ qCDebug(lcSGCurveIntersectionSolver) << "Handled are" << unhandledIntersec.e1 << "in:" << unhandledIntersec.in1 << "out:" << unhandledIntersec.out1
+ << "/" << unhandledIntersec.e2 << "in:" << unhandledIntersec.in2 << "out:" << unhandledIntersec.out2;
+
+ // Searching for the correct direction to go forward.
+ // That requires that the intersection + small delta (here 1e-4)
+ // is a valid starting point (filling only on one side)
+ auto lookForwardOnIntersection = [&](bool *handledPath, int nextE, float nextT, bool nextForward) {
+ if (*handledPath)
+ return false;
+ constexpr float deltaT = 1e-4f;
+ int eForward = nextE;
+ float tForward = nextT + (nextForward ? deltaT : -deltaT);
+ ensureInBounds(&eForward, &tForward, deltaT);
+ QQuadPath::Element::FillSide fillSide = path.fillSideOf(eForward, tForward);
+ if ((nextForward && fillSide == QQuadPath::Element::FillSideRight) ||
+ (!nextForward && fillSide == QQuadPath::Element::FillSideLeft)) {
+ fixedPath.moveTo(path.elementAt(nextE).pointAtFraction(nextT));
+ i1 = startedAtIndex = nextE;
+ t = startedAtT = nextT;
+ forward = nextForward;
+ *handledPath = true;
+ return true;
+ }
+ return false;
+ };
+
+ if (lookForwardOnIntersection(&unhandledIntersec.in1, unhandledIntersec.e1, unhandledIntersec.t1, false))
+ break;
+ if (lookForwardOnIntersection(&unhandledIntersec.in2, unhandledIntersec.e2, unhandledIntersec.t2, false))
+ break;
+ if (lookForwardOnIntersection(&unhandledIntersec.out1, unhandledIntersec.e1, unhandledIntersec.t1, true))
+ break;
+ if (lookForwardOnIntersection(&unhandledIntersec.out2, unhandledIntersec.e2, unhandledIntersec.t2, true))
+ break;
+
+ intersections.removeFirst();
+ qCDebug(lcSGCurveIntersectionSolver) << "Found no way to move forward at this intersection and removed it.";
+ }
+ }
+
+ } while (totalIterations < path.elementCount() * 50);
+ // Check the totalIterations as a sanity check. Should never be triggered.
+
+ qCDebug(lcSGCurveIntersectionSolver) << "Could not solve intersections of path. This should not happen. Returning the path unchanged.";
+
+ return false;
+}
+
+
+void QSGCurveProcessor::processStroke(const QQuadPath &strokePath,
+ float miterLimit,
+ float penWidth,
+ Qt::PenJoinStyle joinStyle,
+ Qt::PenCapStyle capStyle,
+ addStrokeTriangleCallback addTriangle,
+ int subdivisions)
+{
+ auto thePath = subdivide(strokePath, subdivisions).flattened(); // TODO: don't flatten, but handle it in the triangulator
+ auto triangles = customTriangulator2(thePath, penWidth, joinStyle, capStyle, miterLimit);
+
+ auto addCurveTriangle = [&](const QQuadPath::Element &element, const TriangleData &t) {
+ addTriangle(t.points,
+ { element.startPoint(), element.controlPoint(), element.endPoint() },
+ t.normals,
+ element.isLine());
+ };
+
+ auto addBevelTriangle = [&](const TrianglePoints &p)
+ {
+ QVector2D fp1 = p[0];
+ QVector2D fp2 = p[2];
+
+ // That describes a path that passes through those points. We want the stroke
+ // edge, so we need to shift everything down by the stroke offset
+
+ QVector2D nn = calcNormalVector(p[0], p[2]);
+ if (determinant(p) < 0)
+ nn = -nn;
+ float delta = penWidth / 2;
+
+ QVector2D offset = nn.normalized() * delta;
+ fp1 += offset;
+ fp2 += offset;
+
+ TrianglePoints n;
+ // p1 is inside, so n[1] is {0,0}
+ n[0] = (p[0] - p[1]).normalized();
+ n[2] = (p[2] - p[1]).normalized();
+
+ addTriangle(p, { fp1, QVector2D(0.0f, 0.0f), fp2 }, n, true);
+ };
+
+ for (const auto &triangle : triangles) {
+ if (triangle.pathElementIndex < 0) {
+ addBevelTriangle(triangle.points);
+ continue;
+ }
+ const auto &element = thePath.elementAt(triangle.pathElementIndex);
+ addCurveTriangle(element, triangle);
+ }
+}
+
+// 2x variant of qHash(float)
+inline size_t qHash(QVector2D key, size_t seed = 0) noexcept
+{
+ Q_STATIC_ASSERT(sizeof(QVector2D) == sizeof(quint64));
+ // ensure -0 gets mapped to 0
+ key[0] += 0.0f;
+ key[1] += 0.0f;
+ quint64 k;
+ memcpy(&k, &key, sizeof(QVector2D));
+ return QHashPrivate::hash(k, seed);
+}
+
+void QSGCurveProcessor::processFill(const QQuadPath &fillPath,
+ Qt::FillRule fillRule,
+ addTriangleCallback addTriangle)
+{
+ QPainterPath internalHull;
+ internalHull.setFillRule(fillRule);
+
+ QMultiHash<QVector2D, int> pointHash;
+
+ auto roundVec2D = [](const QVector2D &p) -> QVector2D {
+ return { qRound64(p.x() * 32.0f) / 32.0f, qRound64(p.y() * 32.0f) / 32.0f };
+ };
+
+ auto addCurveTriangle = [&](const QQuadPath::Element &element,
+ const QVector2D &sp,
+ const QVector2D &ep,
+ const QVector2D &cp) {
+ addTriangle({ sp, cp, ep },
+ {},
+ [&element](QVector2D v) { return elementUvForPoint(element, v); });
+ };
+
+ auto addCurveTriangleWithNormals = [&](const QQuadPath::Element &element,
+ const std::array<QVector2D, 3> &v,
+ const std::array<QVector2D, 3> &n) {
+ addTriangle(v, n, [&element](QVector2D v) { return elementUvForPoint(element, v); });
+ };
+
+ auto outsideNormal = [](const QVector2D &startPoint,
+ const QVector2D &endPoint,
+ const QVector2D &insidePoint) {
+
+ QVector2D baseLine = endPoint - startPoint;
+ QVector2D insideVector = insidePoint - startPoint;
+ QVector2D normal = normalVector(baseLine);
+
+ bool swap = QVector2D::dotProduct(insideVector, normal) < 0;
+
+ return swap ? normal : -normal;
+ };
+
+ auto addTriangleForLine = [&](const QQuadPath::Element &element,
+ const QVector2D &sp,
+ const QVector2D &ep,
+ const QVector2D &cp) {
+ addCurveTriangle(element, sp, ep, cp);
+
+ // Add triangles on the outer side to make room for AA
+ const QVector2D normal = outsideNormal(sp, ep, cp);
+ constexpr QVector2D null;
+ addCurveTriangleWithNormals(element, {sp, sp, ep}, {null, normal, null});
+ addCurveTriangleWithNormals(element, {sp, ep, ep}, {normal, normal, null});
+ };
+
+ auto addTriangleForConcave = [&](const QQuadPath::Element &element,
+ const QVector2D &sp,
+ const QVector2D &ep,
+ const QVector2D &cp) {
+ addTriangleForLine(element, sp, ep, cp);
+ };
+
+ auto addTriangleForConvex = [&](const QQuadPath::Element &element,
+ const QVector2D &sp,
+ const QVector2D &ep,
+ const QVector2D &cp) {
+ addCurveTriangle(element, sp, ep, cp);
+ // Add two triangles on the outer side to get some more AA
+
+ constexpr QVector2D null;
+ // First triangle on the line sp-cp, replacing ep
+ {
+ const QVector2D normal = outsideNormal(sp, cp, ep);
+ addCurveTriangleWithNormals(element, {sp, sp, cp}, {null, normal, null});
+ }
+
+ // Second triangle on the line ep-cp, replacing sp
+ {
+ const QVector2D normal = outsideNormal(ep, cp, sp);
+ addCurveTriangleWithNormals(element, {ep, ep, cp}, {null, normal, null});
+ }
+ };
+
+ auto addFillTriangle = [&](const QVector2D &p1, const QVector2D &p2, const QVector2D &p3) {
+ constexpr QVector3D uv(0.0, 1.0, -1.0);
+ addTriangle({ p1, p2, p3 },
+ {},
+ [&uv](QVector2D) { return uv; });
+ };
+
+ fillPath.iterateElements([&](const QQuadPath::Element &element, int index) {
+ QVector2D sp(element.startPoint());
+ QVector2D cp(element.controlPoint());
+ QVector2D ep(element.endPoint());
+ QVector2D rsp = roundVec2D(sp);
+
+ if (element.isSubpathStart())
+ internalHull.moveTo(sp.toPointF());
+ if (element.isLine()) {
+ internalHull.lineTo(ep.toPointF());
+ pointHash.insert(rsp, index);
+ } else {
+ QVector2D rep = roundVec2D(ep);
+ QVector2D rcp = roundVec2D(cp);
+ if (element.isConvex()) {
+ internalHull.lineTo(ep.toPointF());
+ addTriangleForConvex(element, rsp, rep, rcp);
+ pointHash.insert(rsp, index);
+ } else {
+ internalHull.lineTo(cp.toPointF());
+ internalHull.lineTo(ep.toPointF());
+ addTriangleForConcave(element, rsp, rep, rcp);
+ pointHash.insert(rcp, index);
+ }
+ }
+ });
+
+ // Points in p are already rounded do 1/32
+ // Returns false if the triangle needs to be split. Adds the triangle to the graphics buffers and returns true otherwise.
+ // (Does not handle ambiguous vertices that are on multiple unrelated lines/curves)
+ auto onSameSideOfLine = [](const QVector2D &p1,
+ const QVector2D &p2,
+ const QVector2D &linePoint,
+ const QVector2D &lineNormal) {
+ float side1 = testSideOfLineByNormal(linePoint, lineNormal, p1);
+ float side2 = testSideOfLineByNormal(linePoint, lineNormal, p2);
+ return side1 * side2 >= 0;
+ };
+
+ auto pointInSafeSpace = [&](const QVector2D &p, const QQuadPath::Element &element) -> bool {
+ const QVector2D a = element.startPoint();
+ const QVector2D b = element.endPoint();
+ const QVector2D c = element.controlPoint();
+ // There are "safe" areas of the curve also across the baseline: the curve can never cross:
+ // line1: the line through A and B'
+ // line2: the line through B and A'
+ // Where A' = A "mirrored" through C and B' = B "mirrored" through C
+ const QVector2D n1 = calcNormalVector(a, c + (c - b));
+ const QVector2D n2 = calcNormalVector(b, c + (c - a));
+ bool safeSideOf1 = onSameSideOfLine(p, c, a, n1);
+ bool safeSideOf2 = onSameSideOfLine(p, c, b, n2);
+ return safeSideOf1 && safeSideOf2;
+ };
+
+ // Returns false if the triangle belongs to multiple elements and need to be split.
+ // Otherwise adds the triangle, optionally splitting it to avoid "unsafe space"
+ auto handleTriangle = [&](const QVector2D (&p)[3]) -> bool {
+ bool isLine = false;
+ bool isConcave = false;
+ bool isConvex = false;
+ int elementIndex = -1;
+
+ bool foundElement = false;
+ int si = -1;
+ int ei = -1;
+
+ for (int i = 0; i < 3; ++i) {
+ auto pointFoundRange = std::as_const(pointHash).equal_range(roundVec2D(p[i]));
+
+ if (pointFoundRange.first == pointHash.constEnd())
+ continue;
+
+ // This point is on some element, now find the element
+ int testIndex = *pointFoundRange.first;
+ bool ambiguous = std::next(pointFoundRange.first) != pointFoundRange.second;
+ if (ambiguous) {
+ // The triangle should be on the inside of exactly one of the elements
+ // We're doing the test for each of the points, which maybe duplicates some effort,
+ // but optimize for simplicity for now.
+ for (auto it = pointFoundRange.first; it != pointFoundRange.second; ++it) {
+ auto &el = fillPath.elementAt(*it);
+ bool fillOnLeft = !el.isFillOnRight();
+ auto sp = roundVec2D(el.startPoint());
+ auto ep = roundVec2D(el.endPoint());
+ // Check if the triangle is on the inside of el; i.e. each point is either sp, ep, or on the inside.
+ auto pointInside = [&](const QVector2D &p) {
+ return p == sp || p == ep
+ || QQuadPath::isPointOnLeft(p, el.startPoint(), el.endPoint()) == fillOnLeft;
+ };
+ if (pointInside(p[0]) && pointInside(p[1]) && pointInside(p[2])) {
+ testIndex = *it;
+ break;
+ }
+ }
+ }
+
+ const auto &element = fillPath.elementAt(testIndex);
+ // Now we check if p[i] -> p[j] is on the element for some j
+ // For a line, the relevant line is sp-ep
+ // For concave it's cp-sp/ep
+ // For convex it's sp-ep again
+ bool onElement = false;
+ for (int j = 0; j < 3; ++j) {
+ if (i == j)
+ continue;
+ if (element.isConvex() || element.isLine())
+ onElement = roundVec2D(element.endPoint()) == p[j];
+ else // concave
+ onElement = roundVec2D(element.startPoint()) == p[j] || roundVec2D(element.endPoint()) == p[j];
+ if (onElement) {
+ if (foundElement)
+ return false; // Triangle already on some other element: must split
+ si = i;
+ ei = j;
+ foundElement = true;
+ elementIndex = testIndex;
+ isConvex = element.isConvex();
+ isLine = element.isLine();
+ isConcave = !isLine && !isConvex;
+ break;
+ }
+ }
+ }
+
+ if (isLine) {
+ int ci = (6 - si - ei) % 3; // 1+2+3 is 6, so missing number is 6-n1-n2
+ addTriangleForLine(fillPath.elementAt(elementIndex), p[si], p[ei], p[ci]);
+ } else if (isConcave) {
+ addCurveTriangle(fillPath.elementAt(elementIndex), p[0], p[1], p[2]);
+ } else if (isConvex) {
+ int oi = (6 - si - ei) % 3;
+ const auto &otherPoint = p[oi];
+ const auto &element = fillPath.elementAt(elementIndex);
+ // We have to test whether the triangle can cross the line
+ // TODO: use the toplevel element's safe space
+ bool safeSpace = pointInSafeSpace(otherPoint, element);
+ if (safeSpace) {
+ addCurveTriangle(element, p[0], p[1], p[2]);
+ } else {
+ // Find a point inside the triangle that's also in the safe space
+ QVector2D newPoint = (p[0] + p[1] + p[2]) / 3;
+ // We should calculate the point directly, but just do a lazy implementation for now:
+ for (int i = 0; i < 7; ++i) {
+ safeSpace = pointInSafeSpace(newPoint, element);
+ if (safeSpace)
+ break;
+ newPoint = (p[si] + p[ei] + newPoint) / 3;
+ }
+ if (safeSpace) {
+ // Split triangle. We know the original triangle is only on one path element, so the other triangles are both fill.
+ // Curve triangle is (sp, ep, np)
+ addCurveTriangle(element, p[si], p[ei], newPoint);
+ // The other two are (sp, op, np) and (ep, op, np)
+ addFillTriangle(p[si], p[oi], newPoint);
+ addFillTriangle(p[ei], p[oi], newPoint);
+ } else {
+ // fallback to fill if we can't find a point in safe space
+ addFillTriangle(p[0], p[1], p[2]);
+ }
+ }
+
+ } else {
+ addFillTriangle(p[0], p[1], p[2]);
+ }
+ return true;
+ };
+
+ QTriangleSet triangles = qTriangulate(internalHull);
+ // Workaround issue in qTriangulate() for single-triangle path
+ if (triangles.indices.size() == 3)
+ triangles.indices.setDataUint({ 0, 1, 2 });
+
+ const quint32 *idxTable = static_cast<const quint32 *>(triangles.indices.data());
+ for (int triangle = 0; triangle < triangles.indices.size() / 3; ++triangle) {
+ const quint32 *idx = &idxTable[triangle * 3];
+
+ QVector2D p[3];
+ for (int i = 0; i < 3; ++i) {
+ p[i] = roundVec2D(QVector2D(float(triangles.vertices.at(idx[i] * 2)),
+ float(triangles.vertices.at(idx[i] * 2 + 1))));
+ }
+ if (qFuzzyIsNull(determinant(p[0], p[1], p[2])))
+ continue; // Skip degenerate triangles
+ bool needsSplit = !handleTriangle(p);
+ if (needsSplit) {
+ QVector2D c = (p[0] + p[1] + p[2]) / 3;
+ for (int i = 0; i < 3; ++i) {
+ qSwap(c, p[i]);
+ handleTriangle(p);
+ qSwap(c, p[i]);
+ }
+ }
+ }
+}
+
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgcurveprocessor_p.h b/src/quick/scenegraph/qsgcurveprocessor_p.h
new file mode 100644
index 0000000000..69f3a3a661
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurveprocessor_p.h
@@ -0,0 +1,57 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGCURVEPROCESSOR_P_H
+#define QSGCURVEPROCESSOR_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtQuick/qtquickexports.h>
+#include "util/qquadpath_p.h"
+
+#include <QtCore/qloggingcategory.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(lcSGCurveProcessor);
+
+class Q_QUICK_EXPORT QSGCurveProcessor
+{
+public:
+ typedef std::function<QVector3D(QVector2D)> uvForPointCallback;
+ typedef std::function<void(const std::array<QVector2D, 3> &,
+ const std::array<QVector2D, 3> &,
+ uvForPointCallback)> addTriangleCallback;
+ typedef std::function<void(const std::array<QVector2D, 3> &,
+ const std::array<QVector2D, 3> &,
+ const std::array<QVector2D, 3> &,
+ bool)> addStrokeTriangleCallback;
+
+ static void processFill(const QQuadPath &path,
+ Qt::FillRule fillRule,
+ addTriangleCallback addTriangle);
+ static void processStroke(const QQuadPath &strokePath,
+ float miterLimit,
+ float penWidth,
+ Qt::PenJoinStyle joinStyle,
+ Qt::PenCapStyle capStyle,
+ addStrokeTriangleCallback addTriangle,
+ int subdivisions = 3);
+ static bool solveOverlaps(QQuadPath &path);
+ static QList<QPair<int, int>> findOverlappingCandidates(const QQuadPath &path);
+ static bool removeNestedSubpaths(QQuadPath &path);
+ static bool solveIntersections(QQuadPath &path, bool removeNestedPaths = true);
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGCURVEPROCESSOR_P_H
diff --git a/src/quick/scenegraph/qsgcurvestrokenode.cpp b/src/quick/scenegraph/qsgcurvestrokenode.cpp
new file mode 100644
index 0000000000..2bfaff1b4e
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurvestrokenode.cpp
@@ -0,0 +1,112 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsgcurvestrokenode_p.h"
+#include "qsgcurvestrokenode_p_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QSGCurveStrokeNode::QSGCurveStrokeNode()
+{
+ setFlag(OwnsGeometry, true);
+ setGeometry(new QSGGeometry(attributes(), 0, 0));
+ updateMaterial();
+}
+
+void QSGCurveStrokeNode::QSGCurveStrokeNode::updateMaterial()
+{
+ m_material.reset(new QSGCurveStrokeMaterial(this));
+ setMaterial(m_material.data());
+}
+
+// Take the start, control and end point of a curve and return the points A, B, C
+// representing the curve as Q(s) = A*s*s + B*s + C
+std::array<QVector2D, 3> QSGCurveStrokeNode::curveABC(const std::array<QVector2D, 3> &p)
+{
+ QVector2D a = p[0] - 2*p[1] + p[2];
+ QVector2D b = 2*p[1] - 2*p[0];
+ QVector2D c = p[0];
+
+ return {a, b, c};
+}
+
+// Curve from p[0] to p[2] with control point p[1]
+void QSGCurveStrokeNode::appendTriangle(const std::array<QVector2D, 3> &v,
+ const std::array<QVector2D, 3> &p,
+ const std::array<QVector2D, 3> &n)
+{
+ auto abc = curveABC(p);
+
+ int currentVertex = m_uncookedVertexes.count();
+
+ for (int i = 0; i < 3; ++i) {
+ m_uncookedVertexes.append( { v[i].x(), v[i].y(),
+ abc[0].x(), abc[0].y(), abc[1].x(), abc[1].y(), abc[2].x(), abc[2].y(),
+ n[i].x(), n[i].y() } );
+ }
+ m_uncookedIndexes << currentVertex << currentVertex + 1 << currentVertex + 2;
+}
+
+// Straight line from p0 to p1
+void QSGCurveStrokeNode::appendTriangle(const std::array<QVector2D, 3> &v,
+ const std::array<QVector2D, 2> &p,
+ const std::array<QVector2D, 3> &n)
+{
+ // We could reduce this to a linear equation by setting A to (0,0).
+ // However, then we cannot use the cubic solution and need an additional
+ // code path in the shader. The following formulation looks more complicated
+ // but allows to always use the cubic solution.
+ auto A = p[1] - p[0];
+ auto B = QVector2D(0., 0.);
+ auto C = p[0];
+
+ int currentVertex = m_uncookedVertexes.count();
+
+// for (auto v : QList<QPair<QVector2D, QVector2D>>({{v0, n0}, {v1, n1}, {v2, n2}})) {
+ for (int i = 0; i < 3; ++i) {
+ m_uncookedVertexes.append( { v[i].x(), v[i].y(),
+ A.x(), A.y(), B.x(), B.y(), C.x(), C.y(),
+ n[i].x(), n[i].y() } );
+ }
+ m_uncookedIndexes << currentVertex << currentVertex + 1 << currentVertex + 2;
+}
+
+void QSGCurveStrokeNode::cookGeometry()
+{
+ QSGGeometry *g = geometry();
+ if (g->indexType() != QSGGeometry::UnsignedIntType) {
+ g = new QSGGeometry(attributes(),
+ m_uncookedVertexes.size(),
+ m_uncookedIndexes.size(),
+ QSGGeometry::UnsignedIntType);
+ setGeometry(g);
+ } else {
+ g->allocate(m_uncookedVertexes.size(), m_uncookedIndexes.size());
+ }
+
+ g->setDrawingMode(QSGGeometry::DrawTriangles);
+ memcpy(g->vertexData(),
+ m_uncookedVertexes.constData(),
+ g->vertexCount() * g->sizeOfVertex());
+ memcpy(g->indexData(),
+ m_uncookedIndexes.constData(),
+ g->indexCount() * g->sizeOfIndex());
+
+ m_uncookedIndexes.clear();
+ m_uncookedVertexes.clear();
+}
+
+const QSGGeometry::AttributeSet &QSGCurveStrokeNode::attributes()
+{
+ static QSGGeometry::Attribute data[] = {
+ QSGGeometry::Attribute::createWithAttributeType(0, 2, QSGGeometry::FloatType, QSGGeometry::PositionAttribute), //vertexCoord
+ QSGGeometry::Attribute::createWithAttributeType(1, 2, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), //A
+ QSGGeometry::Attribute::createWithAttributeType(2, 2, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), //B
+ QSGGeometry::Attribute::createWithAttributeType(3, 2, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), //C
+ QSGGeometry::Attribute::createWithAttributeType(4, 2, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), //normalVector
+ };
+ static QSGGeometry::AttributeSet attrs = { 5, sizeof(StrokeVertex), data };
+ return attrs;
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgcurvestrokenode_p.cpp b/src/quick/scenegraph/qsgcurvestrokenode_p.cpp
new file mode 100644
index 0000000000..b86493b169
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurvestrokenode_p.cpp
@@ -0,0 +1,90 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsgcurvestrokenode_p_p.h"
+#include "qsgcurvestrokenode_p.h"
+
+QT_BEGIN_NAMESPACE
+
+bool QSGCurveStrokeMaterialShader::updateUniformData(RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect)
+{
+ bool changed = false;
+ QByteArray *buf = state.uniformData();
+ Q_ASSERT(buf->size() >= 64);
+ const int matrixCount = qMin(state.projectionMatrixCount(), newEffect->viewCount());
+
+ auto *newMaterial = static_cast<QSGCurveStrokeMaterial *>(newEffect);
+ auto *oldMaterial = static_cast<QSGCurveStrokeMaterial *>(oldEffect);
+
+ auto *newNode = newMaterial != nullptr ? newMaterial->node() : nullptr;
+ auto *oldNode = oldMaterial != nullptr ? oldMaterial->node() : nullptr;
+
+ if (state.isMatrixDirty()) {
+ float localScale = newNode != nullptr ? newNode->localScale() : 1.0f;
+ for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
+ QMatrix4x4 m = state.combinedMatrix(viewIndex);
+ m.scale(localScale);
+ memcpy(buf->data() + viewIndex * 64, m.constData(), 64);
+ }
+ float matrixScale = qSqrt(qAbs(state.determinant())) * state.devicePixelRatio() * localScale;
+ memcpy(buf->data() + matrixCount * 64, &matrixScale, 4);
+ changed = true;
+ }
+
+ if (state.isOpacityDirty()) {
+ const float opacity = state.opacity();
+ memcpy(buf->data() + matrixCount * 64 + 4, &opacity, 4);
+ changed = true;
+ }
+
+ int offset = matrixCount * 64 + 16;
+ if (newNode == nullptr)
+ return changed;
+
+ QVector4D newStrokeColor(newNode->color().redF(),
+ newNode->color().greenF(),
+ newNode->color().blueF(),
+ newNode->color().alphaF());
+ QVector4D oldStrokeColor = oldNode != nullptr
+ ? QVector4D(oldNode->color().redF(),
+ oldNode->color().greenF(),
+ oldNode->color().blueF(),
+ oldNode->color().alphaF())
+ : QVector4D{};
+
+ if (oldNode == nullptr || oldStrokeColor != newStrokeColor) {
+ memcpy(buf->data() + offset, &newStrokeColor, 16);
+ changed = true;
+ }
+ offset += 16;
+
+ if (oldNode == nullptr || newNode->strokeWidth() != oldNode->strokeWidth()) {
+ float w = newNode->strokeWidth();
+ memcpy(buf->data() + offset, &w, 4);
+ changed = true;
+ }
+ offset += 4;
+ if (oldNode == nullptr || newNode->debug() != oldNode->debug()) {
+ float w = newNode->debug();
+ memcpy(buf->data() + offset, &w, 4);
+ changed = true;
+ }
+// offset += 4;
+
+ return changed;
+}
+
+int QSGCurveStrokeMaterial::compare(const QSGMaterial *other) const
+{
+ int typeDif = type() - other->type();
+ if (!typeDif) {
+ auto *othernode = static_cast<const QSGCurveStrokeMaterial*>(other)->node();
+ if (node()->color() != othernode->color())
+ return node()->color().rgb() < othernode->color().rgb() ? -1 : 1;
+ if (node()->strokeWidth() != othernode->strokeWidth())
+ return node()->strokeWidth() < othernode->strokeWidth() ? -1 : 1;
+ }
+ return typeDif;
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgcurvestrokenode_p.h b/src/quick/scenegraph/qsgcurvestrokenode_p.h
new file mode 100644
index 0000000000..079414bc16
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurvestrokenode_p.h
@@ -0,0 +1,116 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGCURVESTROKENODE_P_H
+#define QSGCURVESTROKENODE_P_H
+
+#include <QtQuick/qtquickexports.h>
+#include <QtQuick/qsgnode.h>
+
+#include "qsgcurveabstractnode_p.h"
+#include "qsgcurvestrokenode_p_p.h"
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+QT_BEGIN_NAMESPACE
+
+class Q_QUICK_EXPORT QSGCurveStrokeNode : public QSGCurveAbstractNode
+{
+public:
+ QSGCurveStrokeNode();
+
+ void setColor(QColor col) override
+ {
+ m_color = col;
+ }
+
+ QColor color() const
+ {
+ return m_color;
+ }
+
+ void setStrokeWidth(float width)
+ {
+ m_strokeWidth = width;
+ }
+
+ float strokeWidth() const
+ {
+ return m_strokeWidth;
+ }
+
+ void appendTriangle(const std::array<QVector2D, 3> &v, // triangle vertices
+ const std::array<QVector2D, 3> &p, // curve points
+ const std::array<QVector2D, 3> &n); // vertex normals
+ void appendTriangle(const std::array<QVector2D, 3> &v, // triangle vertices
+ const std::array<QVector2D, 2> &p, // line points
+ const std::array<QVector2D, 3> &n); // vertex normals
+
+ void cookGeometry() override;
+
+ static const QSGGeometry::AttributeSet &attributes();
+
+ QVector<quint32> uncookedIndexes() const
+ {
+ return m_uncookedIndexes;
+ }
+
+ float debug() const
+ {
+ return m_debug;
+ }
+
+ void setDebug(float newDebug)
+ {
+ m_debug = newDebug;
+ }
+
+ void setLocalScale(float scale)
+ {
+ m_localScale = scale;
+ }
+
+ float localScale() const
+ {
+ return m_localScale;
+ }
+
+private:
+
+ struct StrokeVertex
+ {
+ float x, y;
+ float ax, ay;
+ float bx, by;
+ float cx, cy;
+ float nx, ny; //normal vector: direction to move vertext to account for AA
+ };
+
+ void updateMaterial();
+
+ static std::array<QVector2D, 3> curveABC(const std::array<QVector2D, 3> &p);
+
+ QColor m_color;
+ float m_strokeWidth = 0.0f;
+ float m_debug = 0.0f;
+ float m_localScale = 1.0f;
+
+protected:
+ QScopedPointer<QSGCurveStrokeMaterial> m_material;
+
+ QVector<StrokeVertex> m_uncookedVertexes;
+ QVector<quint32> m_uncookedIndexes;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGCURVESTROKENODE_P_H
diff --git a/src/quick/scenegraph/qsgcurvestrokenode_p_p.h b/src/quick/scenegraph/qsgcurvestrokenode_p_p.h
new file mode 100644
index 0000000000..847098f8cc
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurvestrokenode_p_p.h
@@ -0,0 +1,75 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGCURVESTROKENODE_P_P_H
+#define QSGCURVESTROKENODE_P_P_H
+
+#include <QtQuick/qtquickexports.h>
+#include <QtQuick/qsgmaterial.h>
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+QT_BEGIN_NAMESPACE
+
+class QSGCurveStrokeNode;
+class QSGCurveStrokeMaterial;
+
+class Q_QUICK_EXPORT QSGCurveStrokeMaterialShader : public QSGMaterialShader
+{
+public:
+ QSGCurveStrokeMaterialShader(int viewCount)
+ {
+ setShaderFileName(VertexStage,
+ QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shapestroke.vert.qsb"),
+ viewCount);
+ setShaderFileName(FragmentStage,
+ QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shapestroke.frag.qsb"),
+ viewCount);
+ }
+
+ bool updateUniformData(RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) override;
+};
+
+
+class Q_QUICK_EXPORT QSGCurveStrokeMaterial : public QSGMaterial
+{
+public:
+ QSGCurveStrokeMaterial(QSGCurveStrokeNode *node)
+ : m_node(node)
+ {
+ setFlag(Blending, true);
+ }
+
+ int compare(const QSGMaterial *other) const override;
+
+ QSGCurveStrokeNode *node() const
+ {
+ return m_node;
+ }
+
+protected:
+ QSGMaterialType *type() const override
+ {
+ static QSGMaterialType t;
+ return &t;
+ }
+ QSGMaterialShader *createShader(QSGRendererInterface::RenderMode) const override
+ {
+ return new QSGCurveStrokeMaterialShader(viewCount());
+ }
+
+ QSGCurveStrokeNode *m_node;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGCURVESTROKENODE_P_P_H
diff --git a/src/quick/scenegraph/qsgdefaultcontext.cpp b/src/quick/scenegraph/qsgdefaultcontext.cpp
index f3f85445e5..7d1daa1716 100644
--- a/src/quick/scenegraph/qsgdefaultcontext.cpp
+++ b/src/quick/scenegraph/qsgdefaultcontext.cpp
@@ -7,6 +7,7 @@
#include <QtQuick/private/qsgdefaultinternalimagenode_p.h>
#include <QtQuick/private/qsgdefaultpainternode_p.h>
#include <QtQuick/private/qsgdefaultglyphnode_p.h>
+#include <QtQuick/private/qsgcurveglyphnode_p.h>
#include <QtQuick/private/qsgdistancefieldglyphnode_p.h>
#include <QtQuick/private/qsgdistancefieldglyphnode_p_p.h>
#include <QtQuick/private/qsgrhisupport_p.h>
@@ -19,6 +20,8 @@
#include <QtQuick/private/qsgdefaultspritenode_p.h>
#endif
#include <QtQuick/private/qsgrhishadereffectnode_p.h>
+#include <QtQuick/private/qsginternaltextnode_p.h>
+#include <QtQuick/private/qsgrhiinternaltextnode_p.h>
#include <QOpenGLContext>
@@ -29,10 +32,7 @@
#include <algorithm>
-#include <QtGui/private/qrhi_p.h>
-#if QT_CONFIG(opengl)
-# include <QtGui/private/qrhigles2_p.h>
-#endif
+#include <rhi/qrhi.h>
QT_BEGIN_NAMESPACE
@@ -147,11 +147,18 @@ QSGPainterNode *QSGDefaultContext::createPainterNode(QQuickPaintedItem *item)
return new QSGDefaultPainterNode(item);
}
+QSGInternalTextNode *QSGDefaultContext::createInternalTextNode(QSGRenderContext *renderContext)
+{
+ return new QSGRhiInternalTextNode(renderContext);
+}
+
QSGGlyphNode *QSGDefaultContext::createGlyphNode(QSGRenderContext *rc,
- bool preferNativeGlyphNode,
+ QSGTextNode::RenderType renderType,
int renderTypeQuality)
{
- if (m_distanceFieldDisabled || preferNativeGlyphNode) {
+ if (renderType == QSGTextNode::CurveRendering) {
+ return new QSGCurveGlyphNode(rc);
+ } else if (m_distanceFieldDisabled || renderType == QSGTextNode::NativeRendering) {
return new QSGDefaultGlyphNode(rc);
} else {
QSGDistanceFieldGlyphNode *node = new QSGDistanceFieldGlyphNode(rc);
diff --git a/src/quick/scenegraph/qsgdefaultcontext_p.h b/src/quick/scenegraph/qsgdefaultcontext_p.h
index 8dd507bd9a..5f35e7b451 100644
--- a/src/quick/scenegraph/qsgdefaultcontext_p.h
+++ b/src/quick/scenegraph/qsgdefaultcontext_p.h
@@ -21,7 +21,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QSGDefaultContext : public QSGContext, public QSGRendererInterface
+class Q_QUICK_EXPORT QSGDefaultContext : public QSGContext, public QSGRendererInterface
{
public:
QSGDefaultContext(QObject *parent = nullptr);
@@ -33,7 +33,8 @@ public:
QSGInternalRectangleNode *createInternalRectangleNode() override;
QSGInternalImageNode *createInternalImageNode(QSGRenderContext *renderContext) override;
QSGPainterNode *createPainterNode(QQuickPaintedItem *item) override;
- QSGGlyphNode *createGlyphNode(QSGRenderContext *rc, bool preferNativeGlyphNode, int renderTypeQuality) override;
+ QSGGlyphNode *createGlyphNode(QSGRenderContext *rc, QSGTextNode::RenderType renderType, int renderTypeQuality) override;
+ QSGInternalTextNode *createInternalTextNode(QSGRenderContext *renderContext) override;
QSGLayer *createLayer(QSGRenderContext *renderContext) override;
QSurfaceFormat defaultSurfaceFormat() const override;
QSGRendererInterface *rendererInterface(QSGRenderContext *renderContext) override;
diff --git a/src/quick/scenegraph/qsgdefaultglyphnode.cpp b/src/quick/scenegraph/qsgdefaultglyphnode.cpp
index f9133455c8..445f338bbd 100644
--- a/src/quick/scenegraph/qsgdefaultglyphnode.cpp
+++ b/src/quick/scenegraph/qsgdefaultglyphnode.cpp
@@ -47,11 +47,14 @@ void QSGDefaultGlyphNode::update()
QRawFont font = m_glyphs.rawFont();
QMargins margins(0, 0, 0, 0);
- if (m_style == QQuickText::Normal) {
+ const auto *fontEngine = QRawFontPrivate::get(font)->fontEngine;
+ const bool isColorFont = fontEngine->glyphFormat == QFontEngine::Format_ARGB;
+
+ if (m_style == QQuickText::Normal || isColorFont) {
QFontEngine::GlyphFormat glyphFormat;
// Don't try to override glyph format of color fonts
- if (QRawFontPrivate::get(font)->fontEngine->glyphFormat == QFontEngine::Format_ARGB) {
+ if (isColorFont) {
glyphFormat = QFontEngine::Format_None;
} else {
switch (m_preferredAntialiasingMode) {
diff --git a/src/quick/scenegraph/qsgdefaultglyphnode_p.cpp b/src/quick/scenegraph/qsgdefaultglyphnode_p.cpp
index 43a7e919bc..3d6cabe6fc 100644
--- a/src/quick/scenegraph/qsgdefaultglyphnode_p.cpp
+++ b/src/quick/scenegraph/qsgdefaultglyphnode_p.cpp
@@ -17,45 +17,16 @@
QT_BEGIN_NAMESPACE
-#ifndef GL_FRAMEBUFFER_SRGB
-#define GL_FRAMEBUFFER_SRGB 0x8DB9
-#endif
-
-#ifndef GL_FRAMEBUFFER_SRGB_CAPABLE
-#define GL_FRAMEBUFFER_SRGB_CAPABLE 0x8DBA
-#endif
-
static inline QVector4D qsg_premultiply(const QVector4D &c, float globalOpacity)
{
float o = c.w() * globalOpacity;
return QVector4D(c.x() * o, c.y() * o, c.z() * o, o);
}
-#if 0
-static inline qreal qt_sRGB_to_linear_RGB(qreal f)
-{
- return f > 0.04045 ? qPow((f + 0.055) / 1.055, 2.4) : f / 12.92;
-}
-
-static inline QVector4D qt_sRGB_to_linear_RGB(const QVector4D &color)
-{
- return QVector4D(qt_sRGB_to_linear_RGB(color.x()),
- qt_sRGB_to_linear_RGB(color.y()),
- qt_sRGB_to_linear_RGB(color.z()),
- color.w());
-}
-
-static inline qreal fontSmoothingGamma()
-{
- static qreal fontSmoothingGamma = QGuiApplicationPrivate::platformIntegration()->styleHint(QPlatformIntegration::FontSmoothingGamma).toReal();
- return fontSmoothingGamma;
-}
-#endif
-
class QSGTextMaskRhiShader : public QSGMaterialShader
{
public:
- QSGTextMaskRhiShader(QFontEngine::GlyphFormat glyphFormat);
+ QSGTextMaskRhiShader(QFontEngine::GlyphFormat glyphFormat, int viewCount);
bool updateUniformData(RenderState &state,
QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
@@ -64,28 +35,25 @@ public:
protected:
QFontEngine::GlyphFormat m_glyphFormat;
+ quint32 m_currentUbufOffset;
};
-QSGTextMaskRhiShader::QSGTextMaskRhiShader(QFontEngine::GlyphFormat glyphFormat)
+QSGTextMaskRhiShader::QSGTextMaskRhiShader(QFontEngine::GlyphFormat glyphFormat, int viewCount)
: m_glyphFormat(glyphFormat)
{
- setShaderFileName(VertexStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/textmask.vert.qsb"));
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/textmask.frag.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/textmask.vert.qsb"), viewCount);
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/textmask.frag.qsb"), viewCount);
}
-enum UbufOffset {
- ModelViewMatrixOffset = 0,
- ProjectionMatrixOffset = ModelViewMatrixOffset + 64,
- ColorOffset = ProjectionMatrixOffset + 64,
- TextureScaleOffset = ColorOffset + 16,
- DprOffset = TextureScaleOffset + 8,
-
- // + 1 float padding (vec4 must be aligned to 16)
- StyleColorOffset = DprOffset + 4 + 4,
- ShiftOffset = StyleColorOffset + 16
-};
+// uniform block layout:
+// mat4 modelViewMatrix
+// mat4 projectionMatrix or mat4 projectionMatrix[QSHADER_VIEW_COUNT]
+// vec2 textureScale
+// float dpr
+// vec4 color
+// [styled/outline only]
+// vec4 styleColor
+// vec2 shift
bool QSGTextMaskRhiShader::updateUniformData(RenderState &state,
QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
@@ -102,34 +70,46 @@ bool QSGTextMaskRhiShader::updateUniformData(RenderState &state,
bool changed = false;
QByteArray *buf = state.uniformData();
- Q_ASSERT(buf->size() >= DprOffset + 4);
+ const int projectionMatrixCount = qMin(state.projectionMatrixCount(), newMaterial->viewCount());
+ quint32 offset = 0; // ModelViewMatrix
if (state.isMatrixDirty()) {
const QMatrix4x4 mv = state.modelViewMatrix();
- memcpy(buf->data() + ModelViewMatrixOffset, mv.constData(), 64);
- const QMatrix4x4 p = state.projectionMatrix();
- memcpy(buf->data() + ProjectionMatrixOffset, p.constData(), 64);
-
+ memcpy(buf->data() + offset, mv.constData(), 64);
changed = true;
}
+ offset += 64; // now at ProjectionMatrix or ProjectionMatrix[0]
+
+ for (int viewIndex = 0; viewIndex < projectionMatrixCount; ++viewIndex) {
+ if (state.isMatrixDirty()) {
+ const QMatrix4x4 p = state.projectionMatrix(viewIndex);
+ memcpy(buf->data() + offset, p.constData(), 64);
+ changed = true;
+ }
+ offset += 64;
+ }
+ // offset is now at TextureScale
QRhiTexture *oldRtex = oldMat ? oldMat->texture()->rhiTexture() : nullptr;
QRhiTexture *newRtex = mat->texture()->rhiTexture();
if (updated || !oldMat || oldRtex != newRtex) {
const QVector2D textureScale = QVector2D(1.0f / mat->rhiGlyphCache()->width(),
1.0f / mat->rhiGlyphCache()->height());
- memcpy(buf->data() + TextureScaleOffset, &textureScale, 8);
+ memcpy(buf->data() + offset, &textureScale, 8);
changed = true;
}
+ offset += 8; // now at Dpr
if (!oldMat) {
float dpr = state.devicePixelRatio();
- memcpy(buf->data() + DprOffset, &dpr, 4);
+ memcpy(buf->data() + offset, &dpr, 4);
}
+ offset += 4 + 4; // now at Color (with padding to ensure vec4 alignment)
// move texture uploads/copies onto the renderer's soon-to-be-committed list
mat->rhiGlyphCache()->commitResourceUpdates(state.resourceUpdateBatch());
+ m_currentUbufOffset = offset;
return changed;
}
@@ -149,15 +129,13 @@ void QSGTextMaskRhiShader::updateSampledImage(RenderState &state, int binding, Q
class QSG8BitTextMaskRhiShader : public QSGTextMaskRhiShader
{
public:
- QSG8BitTextMaskRhiShader(QFontEngine::GlyphFormat glyphFormat, bool alphaTexture)
- : QSGTextMaskRhiShader(glyphFormat)
+ QSG8BitTextMaskRhiShader(QFontEngine::GlyphFormat glyphFormat, int viewCount, bool alphaTexture)
+ : QSGTextMaskRhiShader(glyphFormat, viewCount)
{
if (alphaTexture)
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/8bittextmask_a.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/8bittextmask_a.frag.qsb"), viewCount);
else
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/8bittextmask.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/8bittextmask.frag.qsb"), viewCount);
}
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
@@ -172,13 +150,13 @@ bool QSG8BitTextMaskRhiShader::updateUniformData(RenderState &state,
QSGTextMaskMaterial *oldMat = static_cast<QSGTextMaskMaterial *>(oldMaterial);
QByteArray *buf = state.uniformData();
- Q_ASSERT(buf->size() >= ColorOffset + 16);
if (oldMat == nullptr || mat->color() != oldMat->color() || state.isOpacityDirty()) {
const QVector4D color = qsg_premultiply(mat->color(), state.opacity());
- memcpy(buf->data() + ColorOffset, &color, 16);
+ memcpy(buf->data() + m_currentUbufOffset, &color, 16);
changed = true;
}
+ m_currentUbufOffset += 16; // now at StyleColor
return changed;
}
@@ -186,12 +164,11 @@ bool QSG8BitTextMaskRhiShader::updateUniformData(RenderState &state,
class QSG24BitTextMaskRhiShader : public QSGTextMaskRhiShader
{
public:
- QSG24BitTextMaskRhiShader(QFontEngine::GlyphFormat glyphFormat)
- : QSGTextMaskRhiShader(glyphFormat)
+ QSG24BitTextMaskRhiShader(QFontEngine::GlyphFormat glyphFormat, int viewCount)
+ : QSGTextMaskRhiShader(glyphFormat, viewCount)
{
setFlag(UpdatesGraphicsPipelineState, true);
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/24bittextmask.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/24bittextmask.frag.qsb"), viewCount);
}
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
@@ -199,13 +176,6 @@ public:
QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
};
-// ### gamma correction (sRGB) Unsurprisingly, the GL approach is not portable
-// to anything else - it just does not work that way, there is no opt-in/out
-// switch and magic winsys-provided maybe-sRGB buffers. When requesting an sRGB
-// QRhiSwapChain (which we do not do), it is full sRGB, with the sRGB
-// framebuffer update and blending always on... Could we do gamma correction in
-// the shader for text? (but that's bad for blending?)
-
bool QSG24BitTextMaskRhiShader::updateUniformData(RenderState &state,
QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
{
@@ -215,14 +185,14 @@ bool QSG24BitTextMaskRhiShader::updateUniformData(RenderState &state,
QSGTextMaskMaterial *oldMat = static_cast<QSGTextMaskMaterial *>(oldMaterial);
QByteArray *buf = state.uniformData();
- Q_ASSERT(buf->size() >= ColorOffset + 16);
if (oldMat == nullptr || mat->color() != oldMat->color() || state.isOpacityDirty()) {
// shader takes vec4 but uses alpha only; coloring happens via the blend constant
const QVector4D color = qsg_premultiply(mat->color(), state.opacity());
- memcpy(buf->data() + ColorOffset, &color, 16);
+ memcpy(buf->data() + m_currentUbufOffset, &color, 16);
changed = true;
}
+ m_currentUbufOffset += 16; // now at StyleColor
return changed;
}
@@ -240,11 +210,8 @@ bool QSG24BitTextMaskRhiShader::updateGraphicsPipelineState(RenderState &state,
QVector4D color = mat->color();
- // if (useSRGB())
- // color = qt_sRGB_to_linear_RGB(color);
-
// this is dynamic state but it's - magic! - taken care of by the renderer
- ps->blendConstant = QColor::fromRgbF(color.x(), color.y(), color.z(), color.w());
+ ps->blendConstant = QColor::fromRgbF(color.x(), color.y(), color.z());
return true;
}
@@ -252,11 +219,10 @@ bool QSG24BitTextMaskRhiShader::updateGraphicsPipelineState(RenderState &state,
class QSG32BitColorTextRhiShader : public QSGTextMaskRhiShader
{
public:
- QSG32BitColorTextRhiShader(QFontEngine::GlyphFormat glyphFormat)
- : QSGTextMaskRhiShader(glyphFormat)
+ QSG32BitColorTextRhiShader(QFontEngine::GlyphFormat glyphFormat, int viewCount)
+ : QSGTextMaskRhiShader(glyphFormat, viewCount)
{
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/32bitcolortext.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/32bitcolortext.frag.qsb"), viewCount);
}
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
@@ -271,14 +237,14 @@ bool QSG32BitColorTextRhiShader::updateUniformData(RenderState &state,
QSGTextMaskMaterial *oldMat = static_cast<QSGTextMaskMaterial *>(oldMaterial);
QByteArray *buf = state.uniformData();
- Q_ASSERT(buf->size() >= ColorOffset + 16);
if (oldMat == nullptr || mat->color() != oldMat->color() || state.isOpacityDirty()) {
// shader takes vec4 but uses alpha only
const QVector4D color(0, 0, 0, mat->color().w() * state.opacity());
- memcpy(buf->data() + ColorOffset, &color, 16);
+ memcpy(buf->data() + m_currentUbufOffset, &color, 16);
changed = true;
}
+ m_currentUbufOffset += 16; // now at StyleColor
return changed;
}
@@ -286,17 +252,14 @@ bool QSG32BitColorTextRhiShader::updateUniformData(RenderState &state,
class QSGStyledTextRhiShader : public QSG8BitTextMaskRhiShader
{
public:
- QSGStyledTextRhiShader(QFontEngine::GlyphFormat glyphFormat, bool alphaTexture)
- : QSG8BitTextMaskRhiShader(glyphFormat, alphaTexture)
+ QSGStyledTextRhiShader(QFontEngine::GlyphFormat glyphFormat, int viewCount, bool alphaTexture)
+ : QSG8BitTextMaskRhiShader(glyphFormat, viewCount, alphaTexture)
{
- setShaderFileName(VertexStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/styledtext.vert.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/styledtext.vert.qsb"), viewCount);
if (alphaTexture)
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/styledtext_a.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/styledtext_a.frag.qsb"), viewCount);
else
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/styledtext.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/styledtext.frag.qsb"), viewCount);
}
bool updateUniformData(RenderState &state,
@@ -312,17 +275,17 @@ bool QSGStyledTextRhiShader::updateUniformData(RenderState &state,
QSGStyledTextMaterial *oldMat = static_cast<QSGStyledTextMaterial *>(oldMaterial);
QByteArray *buf = state.uniformData();
- Q_ASSERT(buf->size() >= ShiftOffset + 8);
if (oldMat == nullptr || mat->styleColor() != oldMat->styleColor() || state.isOpacityDirty()) {
const QVector4D styleColor = qsg_premultiply(mat->styleColor(), state.opacity());
- memcpy(buf->data() + StyleColorOffset, &styleColor, 16);
+ memcpy(buf->data() + m_currentUbufOffset, &styleColor, 16);
changed = true;
}
+ m_currentUbufOffset += 16; // now at StyleShift
if (oldMat == nullptr || oldMat->styleShift() != mat->styleShift()) {
const QVector2D v = mat->styleShift();
- memcpy(buf->data() + ShiftOffset, &v, 8);
+ memcpy(buf->data() + m_currentUbufOffset, &v, 8);
changed = true;
}
@@ -332,17 +295,14 @@ bool QSGStyledTextRhiShader::updateUniformData(RenderState &state,
class QSGOutlinedTextRhiShader : public QSGStyledTextRhiShader
{
public:
- QSGOutlinedTextRhiShader(QFontEngine::GlyphFormat glyphFormat, bool alphaTexture)
- : QSGStyledTextRhiShader(glyphFormat, alphaTexture)
+ QSGOutlinedTextRhiShader(QFontEngine::GlyphFormat glyphFormat, int viewCount, bool alphaTexture)
+ : QSGStyledTextRhiShader(glyphFormat, viewCount, alphaTexture)
{
- setShaderFileName(VertexStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/outlinedtext.vert.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/outlinedtext.vert.qsb"), viewCount);
if (alphaTexture)
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/outlinedtext_a.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/outlinedtext_a.frag.qsb"), viewCount);
else
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/outlinedtext.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/outlinedtext.frag.qsb"), viewCount);
}
};
@@ -361,6 +321,8 @@ QSGTextMaskMaterial::QSGTextMaskMaterial(QSGRenderContext *rc, const QVector4D &
QSGTextMaskMaterial::~QSGTextMaskMaterial()
{
+ if (m_retainedFontEngine != nullptr)
+ m_rc->unregisterFontengineForCleanup(m_retainedFontEngine);
delete m_texture;
}
@@ -408,7 +370,8 @@ void QSGTextMaskMaterial::updateCache(QFontEngine::GlyphFormat glyphFormat)
qreal devicePixelRatio;
void *cacheKey;
Q_ASSERT(m_rhi);
- cacheKey = m_rhi;
+ Q_ASSERT(m_rc);
+ cacheKey = m_rc;
// Get the dpr the modern way. This value retrieved via the
// rendercontext matches what RenderState::devicePixelRatio()
// exposes to the material shaders later on.
@@ -423,6 +386,12 @@ void QSGTextMaskMaterial::updateCache(QFontEngine::GlyphFormat glyphFormat)
if (!m_glyphCache || int(m_glyphCache->glyphFormat()) != glyphFormat) {
m_glyphCache = new QSGRhiTextureGlyphCache(m_rc, glyphFormat, glyphCacheTransform, color);
fontEngine->setGlyphCache(cacheKey, m_glyphCache.data());
+ if (m_retainedFontEngine != nullptr)
+ m_rc->unregisterFontengineForCleanup(m_retainedFontEngine);
+
+ // Note: This is reference counted by the render context, so it will stay alive until
+ // we release that reference
+ m_retainedFontEngine = fontEngine;
m_rc->registerFontengineForCleanup(fontEngine);
}
}
@@ -557,12 +526,12 @@ QSGMaterialShader *QSGTextMaskMaterial::createShader(QSGRendererInterface::Rende
const QFontEngine::GlyphFormat glyphFormat = gc->glyphFormat();
switch (glyphFormat) {
case QFontEngine::Format_ARGB:
- return new QSG32BitColorTextRhiShader(glyphFormat);
+ return new QSG32BitColorTextRhiShader(glyphFormat, viewCount());
case QFontEngine::Format_A32:
- return new QSG24BitTextMaskRhiShader(glyphFormat);
+ return new QSG24BitTextMaskRhiShader(glyphFormat, viewCount());
case QFontEngine::Format_A8:
default:
- return new QSG8BitTextMaskRhiShader(glyphFormat, gc->eightBitFormatIsAlphaSwizzled());
+ return new QSG8BitTextMaskRhiShader(glyphFormat, viewCount(), gc->eightBitFormatIsAlphaSwizzled());
}
}
@@ -621,7 +590,7 @@ QSGMaterialShader *QSGStyledTextMaterial::createShader(QSGRendererInterface::Ren
{
Q_UNUSED(renderMode);
QSGRhiTextureGlyphCache *gc = rhiGlyphCache();
- return new QSGStyledTextRhiShader(gc->glyphFormat(), gc->eightBitFormatIsAlphaSwizzled());
+ return new QSGStyledTextRhiShader(gc->glyphFormat(), viewCount(), gc->eightBitFormatIsAlphaSwizzled());
}
int QSGStyledTextMaterial::compare(const QSGMaterial *o) const
@@ -653,7 +622,7 @@ QSGMaterialShader *QSGOutlinedTextMaterial::createShader(QSGRendererInterface::R
{
Q_UNUSED(renderMode);
QSGRhiTextureGlyphCache *gc = rhiGlyphCache();
- return new QSGOutlinedTextRhiShader(gc->glyphFormat(), gc->eightBitFormatIsAlphaSwizzled());
+ return new QSGOutlinedTextRhiShader(gc->glyphFormat(), viewCount(), gc->eightBitFormatIsAlphaSwizzled());
}
QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgdefaultglyphnode_p_p.h b/src/quick/scenegraph/qsgdefaultglyphnode_p_p.h
index e77e2715cd..d3b919c275 100644
--- a/src/quick/scenegraph/qsgdefaultglyphnode_p_p.h
+++ b/src/quick/scenegraph/qsgdefaultglyphnode_p_p.h
@@ -69,6 +69,7 @@ private:
QSGPlainTexture *m_texture;
QExplicitlySharedDataPointer<QFontEngineGlyphCache> m_glyphCache;
QRawFont m_font;
+ QFontEngine *m_retainedFontEngine = nullptr;
QRhi *m_rhi;
QVector4D m_color;
QSize m_size;
diff --git a/src/quick/scenegraph/qsgdefaultinternalimagenode.cpp b/src/quick/scenegraph/qsgdefaultinternalimagenode.cpp
index 9179382f89..d99f19bbe5 100644
--- a/src/quick/scenegraph/qsgdefaultinternalimagenode.cpp
+++ b/src/quick/scenegraph/qsgdefaultinternalimagenode.cpp
@@ -7,14 +7,14 @@
#include <private/qsgtexturematerial_p.h>
#include <qopenglfunctions.h>
#include <QtCore/qmath.h>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
QT_BEGIN_NAMESPACE
class SmoothTextureMaterialRhiShader : public QSGTextureMaterialRhiShader
{
public:
- SmoothTextureMaterialRhiShader();
+ SmoothTextureMaterialRhiShader(int viewCount);
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
};
@@ -40,25 +40,28 @@ QSGMaterialType *QSGSmoothTextureMaterial::type() const
QSGMaterialShader *QSGSmoothTextureMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
Q_UNUSED(renderMode);
- return new SmoothTextureMaterialRhiShader;
+ return new SmoothTextureMaterialRhiShader(viewCount());
}
-SmoothTextureMaterialRhiShader::SmoothTextureMaterialRhiShader()
+SmoothTextureMaterialRhiShader::SmoothTextureMaterialRhiShader(int viewCount)
+ : QSGTextureMaterialRhiShader(viewCount)
{
- setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/smoothtexture.vert.qsb"));
- setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/smoothtexture.frag.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/smoothtexture.vert.qsb"), viewCount);
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/smoothtexture.frag.qsb"), viewCount);
}
bool SmoothTextureMaterialRhiShader::updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
{
bool changed = false;
QByteArray *buf = state.uniformData();
+ const int shaderMatrixCount = newMaterial->viewCount();
if (!oldMaterial) {
// The viewport is constant, so set the pixel size uniform only once (per batches with the same material).
const QRect r = state.viewportRect();
const QVector2D v(2.0f / r.width(), 2.0f / r.height());
- memcpy(buf->data() + 64 + 8, &v, 8);
+ // mat4 matrix, float opacity, vec2 pixelSize, and the vec2 must be 2*4 aligned
+ memcpy(buf->data() + 64 * shaderMatrixCount + 8, &v, 8);
changed = true;
}
diff --git a/src/quick/scenegraph/qsgdefaultinternalimagenode_p.h b/src/quick/scenegraph/qsgdefaultinternalimagenode_p.h
index 2fc75367a3..1a64bdaace 100644
--- a/src/quick/scenegraph/qsgdefaultinternalimagenode_p.h
+++ b/src/quick/scenegraph/qsgdefaultinternalimagenode_p.h
@@ -24,7 +24,7 @@ QT_BEGIN_NAMESPACE
class QSGDefaultRenderContext;
-class Q_QUICK_PRIVATE_EXPORT QSGSmoothTextureMaterial : public QSGTextureMaterial
+class Q_QUICK_EXPORT QSGSmoothTextureMaterial : public QSGTextureMaterial
{
public:
QSGSmoothTextureMaterial();
@@ -36,7 +36,7 @@ protected:
QSGMaterialShader *createShader(QSGRendererInterface::RenderMode renderMode) const override;
};
-class Q_QUICK_PRIVATE_EXPORT QSGDefaultInternalImageNode : public QSGBasicInternalImageNode
+class Q_QUICK_EXPORT QSGDefaultInternalImageNode : public QSGBasicInternalImageNode
{
public:
QSGDefaultInternalImageNode(QSGDefaultRenderContext *rc);
diff --git a/src/quick/scenegraph/qsgdefaultinternalrectanglenode.cpp b/src/quick/scenegraph/qsgdefaultinternalrectanglenode.cpp
index 19ee18c0de..57fe15d6bf 100644
--- a/src/quick/scenegraph/qsgdefaultinternalrectanglenode.cpp
+++ b/src/quick/scenegraph/qsgdefaultinternalrectanglenode.cpp
@@ -16,26 +16,30 @@ QT_BEGIN_NAMESPACE
class SmoothColorMaterialRhiShader : public QSGMaterialShader
{
public:
- SmoothColorMaterialRhiShader();
+ SmoothColorMaterialRhiShader(int viewCount);
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
};
-SmoothColorMaterialRhiShader::SmoothColorMaterialRhiShader()
+SmoothColorMaterialRhiShader::SmoothColorMaterialRhiShader(int viewCount)
{
- setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/smoothcolor.vert.qsb"));
- setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/smoothcolor.frag.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/smoothcolor.vert.qsb"), viewCount);
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/smoothcolor.frag.qsb"), viewCount);
}
-bool SmoothColorMaterialRhiShader::updateUniformData(RenderState &state, QSGMaterial *, QSGMaterial *oldMaterial)
+bool SmoothColorMaterialRhiShader::updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
{
bool changed = false;
QByteArray *buf = state.uniformData();
-
- if (state.isMatrixDirty()) {
- const QMatrix4x4 m = state.combinedMatrix();
- memcpy(buf->data(), m.constData(), 64);
- changed = true;
+ const int shaderMatrixCount = newMaterial->viewCount();
+ const int matrixCount = qMin(state.projectionMatrixCount(), shaderMatrixCount);
+
+ for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
+ if (state.isMatrixDirty()) {
+ const QMatrix4x4 m = state.combinedMatrix(viewIndex);
+ memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
+ changed = true;
+ }
}
if (oldMaterial == nullptr) {
@@ -43,13 +47,13 @@ bool SmoothColorMaterialRhiShader::updateUniformData(RenderState &state, QSGMate
const QRect r = state.viewportRect();
const QVector2D v(2.0f / r.width(), 2.0f / r.height());
Q_ASSERT(sizeof(v) == 8);
- memcpy(buf->data() + 64, &v, 8);
+ memcpy(buf->data() + 64 * shaderMatrixCount, &v, 8);
changed = true;
}
if (state.isOpacityDirty()) {
const float opacity = state.opacity();
- memcpy(buf->data() + 72, &opacity, 4);
+ memcpy(buf->data() + 64 * shaderMatrixCount + 8, &opacity, 4);
changed = true;
}
@@ -78,7 +82,7 @@ QSGMaterialType *QSGSmoothColorMaterial::type() const
QSGMaterialShader *QSGSmoothColorMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
Q_UNUSED(renderMode);
- return new SmoothColorMaterialRhiShader;
+ return new SmoothColorMaterialRhiShader(viewCount());
}
QSGDefaultInternalRectangleNode::QSGDefaultInternalRectangleNode()
diff --git a/src/quick/scenegraph/qsgdefaultinternalrectanglenode_p.h b/src/quick/scenegraph/qsgdefaultinternalrectanglenode_p.h
index 48236d9022..e4ded20888 100644
--- a/src/quick/scenegraph/qsgdefaultinternalrectanglenode_p.h
+++ b/src/quick/scenegraph/qsgdefaultinternalrectanglenode_p.h
@@ -24,7 +24,7 @@ QT_BEGIN_NAMESPACE
class QSGContext;
-class Q_QUICK_PRIVATE_EXPORT QSGSmoothColorMaterial : public QSGMaterial
+class Q_QUICK_EXPORT QSGSmoothColorMaterial : public QSGMaterial
{
public:
QSGSmoothColorMaterial();
@@ -36,7 +36,7 @@ protected:
QSGMaterialShader *createShader(QSGRendererInterface::RenderMode renderMode) const override;
};
-class Q_QUICK_PRIVATE_EXPORT QSGDefaultInternalRectangleNode : public QSGBasicInternalRectangleNode
+class Q_QUICK_EXPORT QSGDefaultInternalRectangleNode : public QSGBasicInternalRectangleNode
{
public:
QSGDefaultInternalRectangleNode();
diff --git a/src/quick/scenegraph/qsgdefaultrendercontext.cpp b/src/quick/scenegraph/qsgdefaultrendercontext.cpp
index 3fee9d9faf..1b0753e9ae 100644
--- a/src/quick/scenegraph/qsgdefaultrendercontext.cpp
+++ b/src/quick/scenegraph/qsgdefaultrendercontext.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qsgdefaultrendercontext_p.h"
+#include "qsgcurveglyphatlas_p.h"
#include <QtGui/QGuiApplication>
@@ -59,13 +60,32 @@ void QSGDefaultRenderContext::initialize(const QSGRenderContext::InitParams *par
void QSGDefaultRenderContext::invalidateGlyphCaches()
{
- auto it = m_glyphCaches.begin();
- while (it != m_glyphCaches.end()) {
- if (!(*it)->isActive()) {
- delete *it;
- it = m_glyphCaches.erase(it);
- } else {
- ++it;
+ {
+ auto it = m_glyphCaches.begin();
+ while (it != m_glyphCaches.end()) {
+ if (!(*it)->isActive()) {
+ delete *it;
+ it = m_glyphCaches.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ }
+
+ qDeleteAll(m_curveGlyphAtlases);
+ m_curveGlyphAtlases.clear();
+
+ {
+ auto it = m_fontEnginesToClean.begin();
+ while (it != m_fontEnginesToClean.end()) {
+ if (it.value() == 0) {
+ it.key()->clearGlyphCache(this);
+ if (!it.key()->ref.deref())
+ delete it.key();
+ it = m_fontEnginesToClean.erase(it);
+ } else {
+ ++it;
+ }
}
}
}
@@ -108,18 +128,20 @@ void QSGDefaultRenderContext::invalidate()
// code is only called from QQuickWindow's shutdown which is called
// only when the GUI is blocked, and multiple threads will call it in
// sequence. (see qsgdefaultglyphnode_p.cpp's init())
- for (QSet<QFontEngine *>::const_iterator it = m_fontEnginesToClean.constBegin(),
- end = m_fontEnginesToClean.constEnd(); it != end; ++it) {
- (*it)->clearGlyphCache(m_rhi);
- if (!(*it)->ref.deref())
- delete *it;
+ for (auto it = m_fontEnginesToClean.constBegin(); it != m_fontEnginesToClean.constEnd(); ++it) {
+ it.key()->clearGlyphCache(this);
+ if (!it.key()->ref.deref())
+ delete it.key();
}
m_fontEnginesToClean.clear();
+ qDeleteAll(m_curveGlyphAtlases);
+ m_curveGlyphAtlases.clear();
+
qDeleteAll(m_glyphCaches);
m_glyphCaches.clear();
- releaseGlyphCacheResourceUpdates();
+ resetGlyphCacheResources();
m_rhi = nullptr;
@@ -205,27 +227,6 @@ QSGTexture *QSGDefaultRenderContext::compressedTextureForFactory(const QSGCompre
return nullptr;
}
-QString QSGDefaultRenderContext::fontKey(const QRawFont &font, int renderTypeQuality)
-{
- QFontEngine *fe = QRawFontPrivate::get(font)->fontEngine;
- if (!fe->faceId().filename.isEmpty()) {
- QByteArray keyName =
- fe->faceId().filename + ' ' + QByteArray::number(fe->faceId().index)
- + (font.style() != QFont::StyleNormal ? QByteArray(" I") : QByteArray())
- + (font.weight() != QFont::Normal ? ' ' + QByteArray::number(font.weight()) : QByteArray())
- + ' ' + QByteArray::number(renderTypeQuality)
- + QByteArray(" DF");
- return QString::fromUtf8(keyName);
- } else {
- return QString::fromLatin1("%1_%2_%3_%4_%5")
- .arg(font.familyName())
- .arg(font.styleName())
- .arg(font.weight())
- .arg(font.style())
- .arg(renderTypeQuality);
- }
-}
-
void QSGDefaultRenderContext::initializeRhiShader(QSGMaterialShader *shader, QShader::Variant shaderVariant)
{
QSGMaterialShaderPrivate::get(shader)->prepare(shaderVariant);
@@ -239,11 +240,23 @@ void QSGDefaultRenderContext::preprocess()
}
}
+QSGCurveGlyphAtlas *QSGDefaultRenderContext::curveGlyphAtlas(const QRawFont &font)
+{
+ FontKey key = FontKey(font, 0);
+ QSGCurveGlyphAtlas *atlas = m_curveGlyphAtlases.value(key, nullptr);
+ if (atlas == nullptr) {
+ atlas = new QSGCurveGlyphAtlas(font);
+ m_curveGlyphAtlases.insert(key, atlas);
+ }
+
+ return atlas;
+}
+
QSGDistanceFieldGlyphCache *QSGDefaultRenderContext::distanceFieldGlyphCache(const QRawFont &font, int renderTypeQuality)
{
- QString key = fontKey(font, renderTypeQuality);
+ FontKey key(font, renderTypeQuality);
QSGDistanceFieldGlyphCache *cache = m_glyphCaches.value(key, 0);
- if (!cache) {
+ if (!cache && font.isValid()) {
cache = new QSGRhiDistanceFieldGlyphCache(this, font, renderTypeQuality);
m_glyphCaches.insert(key, cache);
}
@@ -264,12 +277,23 @@ QRhiResourceUpdateBatch *QSGDefaultRenderContext::glyphCacheResourceUpdates()
return m_glyphCacheResourceUpdates;
}
-void QSGDefaultRenderContext::releaseGlyphCacheResourceUpdates()
+void QSGDefaultRenderContext::deferredReleaseGlyphCacheTexture(QRhiTexture *texture)
+{
+ if (texture)
+ m_pendingGlyphCacheTextures.insert(texture);
+}
+
+void QSGDefaultRenderContext::resetGlyphCacheResources()
{
if (m_glyphCacheResourceUpdates) {
m_glyphCacheResourceUpdates->release();
m_glyphCacheResourceUpdates = nullptr;
}
+
+ for (QRhiTexture *t : std::as_const(m_pendingGlyphCacheTextures))
+ t->deleteLater(); // the QRhiTexture object stays valid for the current frame
+
+ m_pendingGlyphCacheTextures.clear();
}
QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgdefaultrendercontext_p.h b/src/quick/scenegraph/qsgdefaultrendercontext_p.h
index 448255a594..c3352aa89f 100644
--- a/src/quick/scenegraph/qsgdefaultrendercontext_p.h
+++ b/src/quick/scenegraph/qsgdefaultrendercontext_p.h
@@ -16,7 +16,7 @@
//
#include <QtQuick/private/qsgcontext_p.h>
-#include <QtGui/private/qshader_p.h>
+#include <rhi/qshader.h>
QT_BEGIN_NAMESPACE
@@ -24,6 +24,7 @@ class QRhi;
class QRhiCommandBuffer;
class QRhiRenderPassDescriptor;
class QRhiResourceUpdateBatch;
+class QRhiTexture;
class QSGMaterialShader;
class QSurface;
@@ -31,7 +32,7 @@ namespace QSGRhiAtlasTexture {
class Manager;
}
-class Q_QUICK_PRIVATE_EXPORT QSGDefaultRenderContext : public QSGRenderContext
+class Q_QUICK_EXPORT QSGDefaultRenderContext : public QSGRenderContext
{
Q_OBJECT
public:
@@ -69,6 +70,7 @@ public:
void preprocess() override;
void invalidateGlyphCaches() override;
QSGDistanceFieldGlyphCache *distanceFieldGlyphCache(const QRawFont &font, int renderTypeQuality) override;
+ QSGCurveGlyphAtlas *curveGlyphAtlas(const QRawFont &font) override;
QSGTexture *createTexture(const QImage &image, uint flags) const override;
QSGRenderer *createRenderer(QSGRendererInterface::RenderMode renderMode = QSGRendererInterface::RenderMode2D) override;
@@ -102,11 +104,10 @@ public:
QRhiResourceUpdateBatch *maybeGlyphCacheResourceUpdates();
QRhiResourceUpdateBatch *glyphCacheResourceUpdates();
- void releaseGlyphCacheResourceUpdates();
+ void deferredReleaseGlyphCacheTexture(QRhiTexture *texture);
+ void resetGlyphCacheResources();
protected:
- static QString fontKey(const QRawFont &font, int renderTypeQuality);
-
InitParams m_initParams;
QRhi *m_rhi;
int m_maxTextureSize;
@@ -116,6 +117,8 @@ protected:
qreal m_currentDevicePixelRatio;
bool m_useDepthBufferFor2D;
QRhiResourceUpdateBatch *m_glyphCacheResourceUpdates;
+ QSet<QRhiTexture *> m_pendingGlyphCacheTextures;
+ QHash<FontKey, QSGCurveGlyphAtlas *> m_curveGlyphAtlases;
};
QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgdefaultspritenode.cpp b/src/quick/scenegraph/qsgdefaultspritenode.cpp
index 233d8e17ac..242c844e9c 100644
--- a/src/quick/scenegraph/qsgdefaultspritenode.cpp
+++ b/src/quick/scenegraph/qsgdefaultspritenode.cpp
@@ -53,7 +53,7 @@ QQuickSpriteMaterial::~QQuickSpriteMaterial()
class SpriteMaterialRhiShader : public QSGMaterialShader
{
public:
- SpriteMaterialRhiShader();
+ SpriteMaterialRhiShader(int viewCount);
bool updateUniformData(RenderState &state,
QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
@@ -61,10 +61,10 @@ public:
QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
};
-SpriteMaterialRhiShader::SpriteMaterialRhiShader()
+SpriteMaterialRhiShader::SpriteMaterialRhiShader(int viewCount)
{
- setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/sprite.vert.qsb"));
- setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/sprite.frag.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/sprite.vert.qsb"), viewCount);
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/sprite.frag.qsb"), viewCount);
}
bool SpriteMaterialRhiShader::updateUniformData(RenderState &state,
@@ -80,20 +80,24 @@ bool SpriteMaterialRhiShader::updateUniformData(RenderState &state,
QByteArray *buf = state.uniformData();
Q_ASSERT(buf->size() >= 96);
- if (state.isMatrixDirty()) {
- const QMatrix4x4 m = state.combinedMatrix();
- memcpy(buf->data(), m.constData(), 64);
- changed = true;
+ const int shaderMatrixCount = newMaterial->viewCount();
+ const int matrixCount = qMin(state.projectionMatrixCount(), shaderMatrixCount);
+ for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
+ if (state.isMatrixDirty()) {
+ const QMatrix4x4 m = state.combinedMatrix(viewIndex);
+ memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
+ changed = true;
+ }
}
float animPosAndData[7] = { mat->animX1, mat->animY1, mat->animX2, mat->animY2,
mat->animW, mat->animH, mat->animT };
- memcpy(buf->data() + 64, animPosAndData, 28);
+ memcpy(buf->data() + 64 * shaderMatrixCount, animPosAndData, 28);
changed = true;
if (state.isOpacityDirty()) {
const float opacity = state.opacity();
- memcpy(buf->data() + 92, &opacity, 4);
+ memcpy(buf->data() + 64 * shaderMatrixCount + 16 + 12, &opacity, 4);
changed = true;
}
@@ -120,7 +124,7 @@ void SpriteMaterialRhiShader::updateSampledImage(RenderState &state, int binding
QSGMaterialShader *QQuickSpriteMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
Q_UNUSED(renderMode);
- return new SpriteMaterialRhiShader;
+ return new SpriteMaterialRhiShader(viewCount());
}
static QSGGeometry::Attribute Sprite_Attributes[] = {
diff --git a/src/quick/scenegraph/qsgdistancefieldglyphnode.cpp b/src/quick/scenegraph/qsgdistancefieldglyphnode.cpp
index 64f862f948..d5adb52a9f 100644
--- a/src/quick/scenegraph/qsgdistancefieldglyphnode.cpp
+++ b/src/quick/scenegraph/qsgdistancefieldglyphnode.cpp
@@ -41,7 +41,6 @@ QSGDistanceFieldGlyphNode::~QSGDistanceFieldGlyphNode()
if (m_glyph_cache) {
m_glyph_cache->release(m_glyphs.glyphIndexes());
m_glyph_cache->unregisterGlyphNode(this);
- m_glyph_cache->unregisterOwnerElement(ownerElement());
}
}
@@ -90,15 +89,13 @@ void QSGDistanceFieldGlyphNode::setGlyphs(const QPointF &position, const QGlyphR
return;
if (m_glyph_cache != oldCache) {
- Q_ASSERT(ownerElement() != nullptr);
if (oldCache) {
oldCache->unregisterGlyphNode(this);
- oldCache->unregisterOwnerElement(ownerElement());
}
m_glyph_cache->registerGlyphNode(this);
- m_glyph_cache->registerOwnerElement(ownerElement());
}
- m_glyph_cache->populate(glyphs.glyphIndexes());
+ if (m_glyph_cache)
+ m_glyph_cache->populate(glyphs.glyphIndexes());
const QVector<quint32> glyphIndexes = m_glyphs.glyphIndexes();
for (int i = 0; i < glyphIndexes.size(); ++i)
@@ -158,7 +155,8 @@ void QSGDistanceFieldGlyphNode::invalidateGlyphs(const QVector<quint32> &glyphs)
void QSGDistanceFieldGlyphNode::updateGeometry()
{
- Q_ASSERT(m_glyph_cache);
+ if (!m_glyph_cache)
+ return;
// Remove previously created sub glyph nodes
// We assume all the children are sub glyph nodes
diff --git a/src/quick/scenegraph/qsgdistancefieldglyphnode_p.cpp b/src/quick/scenegraph/qsgdistancefieldglyphnode_p.cpp
index b3da72dc81..afb41f32e4 100644
--- a/src/quick/scenegraph/qsgdistancefieldglyphnode_p.cpp
+++ b/src/quick/scenegraph/qsgdistancefieldglyphnode_p.cpp
@@ -36,7 +36,7 @@ static float spreadFunc(float glyphScale)
class QSGDistanceFieldTextMaterialRhiShader : public QSGMaterialShader
{
public:
- QSGDistanceFieldTextMaterialRhiShader(bool alphaTexture);
+ QSGDistanceFieldTextMaterialRhiShader(bool alphaTexture, int viewCount);
bool updateUniformData(RenderState &state,
QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
@@ -47,19 +47,16 @@ public:
protected:
float m_fontScale = 1.0;
float m_matrixScale = 1.0;
+ quint32 m_currentUbufOffset;
};
-QSGDistanceFieldTextMaterialRhiShader::QSGDistanceFieldTextMaterialRhiShader(bool alphaTexture)
+QSGDistanceFieldTextMaterialRhiShader::QSGDistanceFieldTextMaterialRhiShader(bool alphaTexture, int viewCount)
{
- setShaderFileName(VertexStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldtext.vert.qsb"));
-
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldtext.vert.qsb"), viewCount);
if (alphaTexture)
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldtext_a.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldtext_a.frag.qsb"), viewCount);
else
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldtext.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldtext.frag.qsb"), viewCount);
}
bool QSGDistanceFieldTextMaterialRhiShader::updateUniformData(RenderState &state,
@@ -85,36 +82,47 @@ bool QSGDistanceFieldTextMaterialRhiShader::updateUniformData(RenderState &state
updateRange = true;
}
if (state.isMatrixDirty()) {
- const QMatrix4x4 m = state.combinedMatrix();
- memcpy(buf->data(), m.constData(), 64);
- changed = true;
m_matrixScale = qSqrt(qAbs(state.determinant())) * state.devicePixelRatio();
updateRange = true;
}
+ quint32 offset = 0;
+ const int matrixCount = qMin(state.projectionMatrixCount(), newMaterial->viewCount());
+ for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
+ if (state.isMatrixDirty()) {
+ const QMatrix4x4 m = state.combinedMatrix(viewIndex);
+ memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
+ changed = true;
+ }
+ offset += 64;
+ }
if (textureUpdated || !oldMat || oldMat->texture()->texture != mat->texture()->texture) {
const QVector2D ts(1.0f / mat->textureSize().width(), 1.0f / mat->textureSize().height());
Q_ASSERT(sizeof(ts) == 8);
- memcpy(buf->data() + 64, &ts, 8);
+ memcpy(buf->data() + offset, &ts, 8);
changed = true;
}
+ offset += 8 + 8; // 8 is padding for vec4 alignment
if (!oldMat || mat->color() != oldMat->color() || state.isOpacityDirty()) {
const QVector4D color = mat->color() * state.opacity();
Q_ASSERT(sizeof(color) == 16);
- memcpy(buf->data() + 80, &color, 16);
+ memcpy(buf->data() + offset, &color, 16);
changed = true;
}
+ offset += 16;
if (updateRange) { // deferred because depends on m_fontScale and m_matrixScale
const float combinedScale = m_fontScale * m_matrixScale;
const float base = thresholdFunc(combinedScale);
const float range = spreadFunc(combinedScale);
const QVector2D alphaMinMax(qMax(0.0f, base - range), qMin(base + range, 1.0f));
- memcpy(buf->data() + 96, &alphaMinMax, 8);
+ memcpy(buf->data() + offset, &alphaMinMax, 8);
changed = true;
}
+ offset += 8; // not adding any padding here since we are not sure what comes afterwards in the subclasses' shaders
// move texture uploads/copies onto the renderer's soon-to-be-committed list
static_cast<QSGRhiDistanceFieldGlyphCache *>(mat->glyphCache())->commitResourceUpdates(state.resourceUpdateBatch());
+ m_currentUbufOffset = offset;
return changed;
}
@@ -134,20 +142,17 @@ void QSGDistanceFieldTextMaterialRhiShader::updateSampledImage(RenderState &stat
class DistanceFieldAnisotropicTextMaterialRhiShader : public QSGDistanceFieldTextMaterialRhiShader
{
public:
- DistanceFieldAnisotropicTextMaterialRhiShader(bool alphaTexture);
+ DistanceFieldAnisotropicTextMaterialRhiShader(bool alphaTexture, int viewCount);
};
-DistanceFieldAnisotropicTextMaterialRhiShader::DistanceFieldAnisotropicTextMaterialRhiShader(bool alphaTexture)
- : QSGDistanceFieldTextMaterialRhiShader(alphaTexture)
+DistanceFieldAnisotropicTextMaterialRhiShader::DistanceFieldAnisotropicTextMaterialRhiShader(bool alphaTexture, int viewCount)
+ : QSGDistanceFieldTextMaterialRhiShader(alphaTexture, viewCount)
{
- setShaderFileName(VertexStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldtext.vert.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldtext.vert.qsb"), viewCount);
if (alphaTexture)
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldtext_a_fwidth.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldtext_a_fwidth.frag.qsb"), viewCount);
else
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldtext_fwidth.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldtext_fwidth.frag.qsb"), viewCount);
}
QSGDistanceFieldTextMaterial::QSGDistanceFieldTextMaterial()
@@ -180,9 +185,9 @@ void QSGDistanceFieldTextMaterial::setColor(const QColor &color)
QSGMaterialShader *QSGDistanceFieldTextMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
if (renderMode == QSGRendererInterface::RenderMode3D && m_glyph_cache->screenSpaceDerivativesSupported())
- return new DistanceFieldAnisotropicTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled());
+ return new DistanceFieldAnisotropicTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled(), viewCount());
else
- return new QSGDistanceFieldTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled());
+ return new QSGDistanceFieldTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled(), viewCount());
}
bool QSGDistanceFieldTextMaterial::updateTextureSize()
@@ -234,13 +239,13 @@ int QSGDistanceFieldTextMaterial::compare(const QSGMaterial *o) const
class DistanceFieldStyledTextMaterialRhiShader : public QSGDistanceFieldTextMaterialRhiShader
{
public:
- DistanceFieldStyledTextMaterialRhiShader(bool alphaTexture);
+ DistanceFieldStyledTextMaterialRhiShader(bool alphaTexture, int viewCount);
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
};
-DistanceFieldStyledTextMaterialRhiShader::DistanceFieldStyledTextMaterialRhiShader(bool alphaTexture)
- : QSGDistanceFieldTextMaterialRhiShader(alphaTexture)
+DistanceFieldStyledTextMaterialRhiShader::DistanceFieldStyledTextMaterialRhiShader(bool alphaTexture, int viewCount)
+ : QSGDistanceFieldTextMaterialRhiShader(alphaTexture, viewCount)
{
}
@@ -254,12 +259,17 @@ bool DistanceFieldStyledTextMaterialRhiShader::updateUniformData(RenderState &st
QByteArray *buf = state.uniformData();
Q_ASSERT(buf->size() >= 128);
+ // must add 8 bytes padding for vec4 alignment, the base class did not do this
+ m_currentUbufOffset += 8; // now at StyleColor
+
if (!oldMat || mat->styleColor() != oldMat->styleColor() || state.isOpacityDirty()) {
QVector4D styleColor = mat->styleColor();
styleColor *= state.opacity();
- memcpy(buf->data() + 112, &styleColor, 16);
+
+ memcpy(buf->data() + m_currentUbufOffset, &styleColor, 16);
changed = true;
}
+ m_currentUbufOffset += 16;
return changed;
}
@@ -292,42 +302,35 @@ int QSGDistanceFieldStyledTextMaterial::compare(const QSGMaterial *o) const
class DistanceFieldOutlineTextMaterialRhiShader : public DistanceFieldStyledTextMaterialRhiShader
{
public:
- DistanceFieldOutlineTextMaterialRhiShader(bool alphaTexture);
+ DistanceFieldOutlineTextMaterialRhiShader(bool alphaTexture, int viewCount);
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
};
-DistanceFieldOutlineTextMaterialRhiShader::DistanceFieldOutlineTextMaterialRhiShader(bool alphaTexture)
- : DistanceFieldStyledTextMaterialRhiShader(alphaTexture)
+DistanceFieldOutlineTextMaterialRhiShader::DistanceFieldOutlineTextMaterialRhiShader(bool alphaTexture, int viewCount)
+ : DistanceFieldStyledTextMaterialRhiShader(alphaTexture, viewCount)
{
- setShaderFileName(VertexStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldoutlinetext.vert.qsb"));
-
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldoutlinetext.vert.qsb"), viewCount);
if (alphaTexture)
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldoutlinetext_a.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldoutlinetext_a.frag.qsb"), viewCount);
else
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldoutlinetext.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldoutlinetext.frag.qsb"), viewCount);
}
class DistanceFieldAnisotropicOutlineTextMaterialRhiShader : public DistanceFieldOutlineTextMaterialRhiShader
{
public:
- DistanceFieldAnisotropicOutlineTextMaterialRhiShader(bool alphaTexture);
+ DistanceFieldAnisotropicOutlineTextMaterialRhiShader(bool alphaTexture, int viewCount);
};
-DistanceFieldAnisotropicOutlineTextMaterialRhiShader::DistanceFieldAnisotropicOutlineTextMaterialRhiShader(bool alphaTexture)
- : DistanceFieldOutlineTextMaterialRhiShader(alphaTexture)
+DistanceFieldAnisotropicOutlineTextMaterialRhiShader::DistanceFieldAnisotropicOutlineTextMaterialRhiShader(bool alphaTexture, int viewCount)
+ : DistanceFieldOutlineTextMaterialRhiShader(alphaTexture, viewCount)
{
- setShaderFileName(VertexStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldoutlinetext.vert.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldoutlinetext.vert.qsb"), viewCount);
if (alphaTexture)
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldoutlinetext_a_fwidth.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldoutlinetext_a_fwidth.frag.qsb"), viewCount);
else
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldoutlinetext_fwidth.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldoutlinetext_fwidth.frag.qsb"), viewCount);
}
bool DistanceFieldOutlineTextMaterialRhiShader::updateUniformData(RenderState &state,
@@ -349,8 +352,8 @@ bool DistanceFieldOutlineTextMaterialRhiShader::updateUniformData(RenderState &s
float alphaMin = qMax(0.0f, base - range);
float styleAlphaMin0 = qMax(0.0f, outlineLimit - range);
float styleAlphaMin1 = qMin(outlineLimit + range, alphaMin);
- memcpy(buf->data() + 128, &styleAlphaMin0, 4);
- memcpy(buf->data() + 132, &styleAlphaMin1, 4);
+ memcpy(buf->data() + m_currentUbufOffset, &styleAlphaMin0, 4);
+ memcpy(buf->data() + m_currentUbufOffset + 4, &styleAlphaMin1, 4);
changed = true;
}
@@ -375,30 +378,27 @@ QSGMaterialType *QSGDistanceFieldOutlineTextMaterial::type() const
QSGMaterialShader *QSGDistanceFieldOutlineTextMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
if (renderMode == QSGRendererInterface::RenderMode3D && m_glyph_cache->screenSpaceDerivativesSupported())
- return new DistanceFieldAnisotropicOutlineTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled());
+ return new DistanceFieldAnisotropicOutlineTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled(), viewCount());
else
- return new DistanceFieldOutlineTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled());
+ return new DistanceFieldOutlineTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled(), viewCount());
}
class DistanceFieldShiftedStyleTextMaterialRhiShader : public DistanceFieldStyledTextMaterialRhiShader
{
public:
- DistanceFieldShiftedStyleTextMaterialRhiShader(bool alphaTexture);
+ DistanceFieldShiftedStyleTextMaterialRhiShader(bool alphaTexture, int viewCount);
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
};
-DistanceFieldShiftedStyleTextMaterialRhiShader::DistanceFieldShiftedStyleTextMaterialRhiShader(bool alphaTexture)
- : DistanceFieldStyledTextMaterialRhiShader(alphaTexture)
+DistanceFieldShiftedStyleTextMaterialRhiShader::DistanceFieldShiftedStyleTextMaterialRhiShader(bool alphaTexture, int viewCount)
+ : DistanceFieldStyledTextMaterialRhiShader(alphaTexture, viewCount)
{
- setShaderFileName(VertexStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldshiftedtext.vert.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldshiftedtext.vert.qsb"), viewCount);
if (alphaTexture)
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldshiftedtext_a.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldshiftedtext_a.frag.qsb"), viewCount);
else
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldshiftedtext.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldshiftedtext.frag.qsb"), viewCount);
}
bool DistanceFieldShiftedStyleTextMaterialRhiShader::updateUniformData(RenderState &state,
@@ -416,7 +416,7 @@ bool DistanceFieldShiftedStyleTextMaterialRhiShader::updateUniformData(RenderSta
{
QVector2D shift(1.0 / mat->fontScale() * mat->shift().x(),
1.0 / mat->fontScale() * mat->shift().y());
- memcpy(buf->data() + 128, &shift, 8);
+ memcpy(buf->data() + m_currentUbufOffset, &shift, 8);
changed = true;
}
@@ -426,20 +426,17 @@ bool DistanceFieldShiftedStyleTextMaterialRhiShader::updateUniformData(RenderSta
class DistanceFieldAnisotropicShiftedTextMaterialRhiShader : public DistanceFieldShiftedStyleTextMaterialRhiShader
{
public:
- DistanceFieldAnisotropicShiftedTextMaterialRhiShader(bool alphaTexture);
+ DistanceFieldAnisotropicShiftedTextMaterialRhiShader(bool alphaTexture, int viewCount);
};
-DistanceFieldAnisotropicShiftedTextMaterialRhiShader::DistanceFieldAnisotropicShiftedTextMaterialRhiShader(bool alphaTexture)
- : DistanceFieldShiftedStyleTextMaterialRhiShader(alphaTexture)
+DistanceFieldAnisotropicShiftedTextMaterialRhiShader::DistanceFieldAnisotropicShiftedTextMaterialRhiShader(bool alphaTexture, int viewCount)
+ : DistanceFieldShiftedStyleTextMaterialRhiShader(alphaTexture, viewCount)
{
- setShaderFileName(VertexStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldshiftedtext.vert.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldshiftedtext.vert.qsb"), viewCount);
if (alphaTexture)
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldshiftedtext_a_fwidth.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldshiftedtext_a_fwidth.frag.qsb"), viewCount);
else
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldshiftedtext_fwidth.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldshiftedtext_fwidth.frag.qsb"), viewCount);
}
QSGDistanceFieldShiftedStyleTextMaterial::QSGDistanceFieldShiftedStyleTextMaterial()
@@ -460,9 +457,9 @@ QSGMaterialType *QSGDistanceFieldShiftedStyleTextMaterial::type() const
QSGMaterialShader *QSGDistanceFieldShiftedStyleTextMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
if (renderMode == QSGRendererInterface::RenderMode3D && m_glyph_cache->screenSpaceDerivativesSupported())
- return new DistanceFieldAnisotropicShiftedTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled());
+ return new DistanceFieldAnisotropicShiftedTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled(), viewCount());
else
- return new DistanceFieldShiftedStyleTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled());
+ return new DistanceFieldShiftedStyleTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled(), viewCount());
}
int QSGDistanceFieldShiftedStyleTextMaterial::compare(const QSGMaterial *o) const
@@ -476,26 +473,22 @@ int QSGDistanceFieldShiftedStyleTextMaterial::compare(const QSGMaterial *o) cons
class QSGHiQSubPixelDistanceFieldTextMaterialRhiShader : public QSGDistanceFieldTextMaterialRhiShader
{
public:
- QSGHiQSubPixelDistanceFieldTextMaterialRhiShader(bool alphaTexture);
+ QSGHiQSubPixelDistanceFieldTextMaterialRhiShader(bool alphaTexture, int viewCount);
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
bool updateGraphicsPipelineState(RenderState &state, GraphicsPipelineState *ps,
QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
};
-QSGHiQSubPixelDistanceFieldTextMaterialRhiShader::QSGHiQSubPixelDistanceFieldTextMaterialRhiShader(bool alphaTexture)
- : QSGDistanceFieldTextMaterialRhiShader(alphaTexture)
+QSGHiQSubPixelDistanceFieldTextMaterialRhiShader::QSGHiQSubPixelDistanceFieldTextMaterialRhiShader(bool alphaTexture, int viewCount)
+ : QSGDistanceFieldTextMaterialRhiShader(alphaTexture, viewCount)
{
setFlag(UpdatesGraphicsPipelineState, true);
-
- setShaderFileName(VertexStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.vert.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.vert.qsb"), viewCount);
if (alphaTexture)
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext_a.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext_a.frag.qsb"), viewCount);
else
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.frag.qsb"), viewCount);
}
bool QSGHiQSubPixelDistanceFieldTextMaterialRhiShader::updateUniformData(RenderState &state,
@@ -510,15 +503,16 @@ bool QSGHiQSubPixelDistanceFieldTextMaterialRhiShader::updateUniformData(RenderS
if (!oldMat || mat->fontScale() != oldMat->fontScale()) {
float fontScale = mat->fontScale();
- memcpy(buf->data() + 104, &fontScale, 4);
+ memcpy(buf->data() + m_currentUbufOffset, &fontScale, 4);
changed = true;
}
+ m_currentUbufOffset += 4 + 4; // 4 for padding for vec2 alignment
if (!oldMat || state.isMatrixDirty()) {
int viewportWidth = state.viewportRect().width();
QMatrix4x4 mat = state.combinedMatrix().inverted();
QVector4D vecDelta = mat.column(0) * (qreal(2) / viewportWidth);
- memcpy(buf->data() + 112, &vecDelta, 16);
+ memcpy(buf->data() + m_currentUbufOffset, &vecDelta, 16);
}
return changed;
@@ -551,28 +545,25 @@ QSGMaterialType *QSGHiQSubPixelDistanceFieldTextMaterial::type() const
QSGMaterialShader *QSGHiQSubPixelDistanceFieldTextMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
if (renderMode == QSGRendererInterface::RenderMode3D && m_glyph_cache->screenSpaceDerivativesSupported())
- return new DistanceFieldAnisotropicTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled());
+ return new DistanceFieldAnisotropicTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled(), viewCount());
else
- return new QSGHiQSubPixelDistanceFieldTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled());
+ return new QSGHiQSubPixelDistanceFieldTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled(), viewCount());
}
class QSGLoQSubPixelDistanceFieldTextMaterialRhiShader : public QSGHiQSubPixelDistanceFieldTextMaterialRhiShader
{
public:
- QSGLoQSubPixelDistanceFieldTextMaterialRhiShader(bool alphaTexture);
+ QSGLoQSubPixelDistanceFieldTextMaterialRhiShader(bool alphaTexture, int viewCount);
};
-QSGLoQSubPixelDistanceFieldTextMaterialRhiShader::QSGLoQSubPixelDistanceFieldTextMaterialRhiShader(bool alphaTexture)
- : QSGHiQSubPixelDistanceFieldTextMaterialRhiShader(alphaTexture)
+QSGLoQSubPixelDistanceFieldTextMaterialRhiShader::QSGLoQSubPixelDistanceFieldTextMaterialRhiShader(bool alphaTexture, int viewCount)
+ : QSGHiQSubPixelDistanceFieldTextMaterialRhiShader(alphaTexture, viewCount)
{
- setShaderFileName(VertexStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.vert.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.vert.qsb"), viewCount);
if (alphaTexture)
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/loqsubpixeldistancefieldtext_a.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/loqsubpixeldistancefieldtext_a.frag.qsb"), viewCount);
else
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.frag.qsb"), viewCount);
}
QSGMaterialType *QSGLoQSubPixelDistanceFieldTextMaterial::type() const
@@ -584,9 +575,9 @@ QSGMaterialType *QSGLoQSubPixelDistanceFieldTextMaterial::type() const
QSGMaterialShader *QSGLoQSubPixelDistanceFieldTextMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
if (renderMode == QSGRendererInterface::RenderMode3D && m_glyph_cache->screenSpaceDerivativesSupported())
- return new DistanceFieldAnisotropicTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled());
+ return new DistanceFieldAnisotropicTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled(), viewCount());
else
- return new QSGLoQSubPixelDistanceFieldTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled());
+ return new QSGLoQSubPixelDistanceFieldTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled(), viewCount());
}
QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgdistancefieldglyphnode_p.h b/src/quick/scenegraph/qsgdistancefieldglyphnode_p.h
index d92bf0a836..4c75c2239f 100644
--- a/src/quick/scenegraph/qsgdistancefieldglyphnode_p.h
+++ b/src/quick/scenegraph/qsgdistancefieldglyphnode_p.h
@@ -22,6 +22,8 @@
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcSgText)
+
class QSGRenderContext;
class QSGDistanceFieldTextMaterial;
diff --git a/src/quick/scenegraph/qsgdistancefieldglyphnode_p_p.h b/src/quick/scenegraph/qsgdistancefieldglyphnode_p_p.h
index 06d05a928b..207facf213 100644
--- a/src/quick/scenegraph/qsgdistancefieldglyphnode_p_p.h
+++ b/src/quick/scenegraph/qsgdistancefieldglyphnode_p_p.h
@@ -24,7 +24,7 @@ QT_BEGIN_NAMESPACE
class QSGPlainTexture;
-class Q_QUICK_PRIVATE_EXPORT QSGDistanceFieldTextMaterial: public QSGMaterial
+class Q_QUICK_EXPORT QSGDistanceFieldTextMaterial: public QSGMaterial
{
public:
QSGDistanceFieldTextMaterial();
@@ -61,7 +61,7 @@ protected:
QSGPlainTexture *m_sgTexture;
};
-class Q_QUICK_PRIVATE_EXPORT QSGDistanceFieldStyledTextMaterial : public QSGDistanceFieldTextMaterial
+class Q_QUICK_EXPORT QSGDistanceFieldStyledTextMaterial : public QSGDistanceFieldTextMaterial
{
public:
QSGDistanceFieldStyledTextMaterial();
@@ -78,7 +78,7 @@ protected:
QVector4D m_styleColor;
};
-class Q_QUICK_PRIVATE_EXPORT QSGDistanceFieldOutlineTextMaterial : public QSGDistanceFieldStyledTextMaterial
+class Q_QUICK_EXPORT QSGDistanceFieldOutlineTextMaterial : public QSGDistanceFieldStyledTextMaterial
{
public:
QSGDistanceFieldOutlineTextMaterial();
@@ -88,7 +88,7 @@ public:
QSGMaterialShader *createShader(QSGRendererInterface::RenderMode renderMode) const override;
};
-class Q_QUICK_PRIVATE_EXPORT QSGDistanceFieldShiftedStyleTextMaterial : public QSGDistanceFieldStyledTextMaterial
+class Q_QUICK_EXPORT QSGDistanceFieldShiftedStyleTextMaterial : public QSGDistanceFieldStyledTextMaterial
{
public:
QSGDistanceFieldShiftedStyleTextMaterial();
@@ -105,7 +105,7 @@ protected:
QPointF m_shift;
};
-class Q_QUICK_PRIVATE_EXPORT QSGHiQSubPixelDistanceFieldTextMaterial : public QSGDistanceFieldTextMaterial
+class Q_QUICK_EXPORT QSGHiQSubPixelDistanceFieldTextMaterial : public QSGDistanceFieldTextMaterial
{
public:
QSGMaterialType *type() const override;
@@ -116,7 +116,7 @@ public:
}
};
-class Q_QUICK_PRIVATE_EXPORT QSGLoQSubPixelDistanceFieldTextMaterial : public QSGDistanceFieldTextMaterial
+class Q_QUICK_EXPORT QSGLoQSubPixelDistanceFieldTextMaterial : public QSGDistanceFieldTextMaterial
{
public:
QSGMaterialType *type() const override;
diff --git a/src/quick/scenegraph/qsgrenderloop.cpp b/src/quick/scenegraph/qsgrenderloop.cpp
index e620a22fb7..3c8da0fc6a 100644
--- a/src/quick/scenegraph/qsgrenderloop.cpp
+++ b/src/quick/scenegraph/qsgrenderloop.cpp
@@ -40,6 +40,18 @@ extern bool qsg_useConsistentTiming();
#define ENABLE_DEFAULT_BACKEND
+Q_TRACE_POINT(qtquick, QSG_renderWindow_entry)
+Q_TRACE_POINT(qtquick, QSG_renderWindow_exit)
+Q_TRACE_POINT(qtquick, QSG_polishItems_entry)
+Q_TRACE_POINT(qtquick, QSG_polishItems_exit)
+Q_TRACE_POINT(qtquick, QSG_sync_entry)
+Q_TRACE_POINT(qtquick, QSG_sync_exit)
+Q_TRACE_POINT(qtquick, QSG_render_entry)
+Q_TRACE_POINT(qtquick, QSG_render_exit)
+Q_TRACE_POINT(qtquick, QSG_swap_entry)
+Q_TRACE_POINT(qtquick, QSG_swap_exit)
+
+
/*
- Uses one QRhi per window. (and so each window has its own QOpenGLContext or ID3D11Device(Context) etc.)
- Animations are advanced using the standard timer (no custom animation
@@ -126,6 +138,7 @@ public:
void releaseSwapchain(QQuickWindow *window);
void handleDeviceLoss();
+ void teardownGraphics();
bool eventFilter(QObject *watched, QEvent *event) override;
@@ -154,6 +167,8 @@ public:
mutable QSet<QSGRenderContext *> pendingRenderContexts;
bool m_inPolish = false;
+
+ bool swRastFallbackDueToSwapchainFailure = false;
};
#endif
@@ -246,6 +261,15 @@ void QSGRenderLoop::setInstance(QSGRenderLoop *instance)
void QSGRenderLoop::handleContextCreationFailure(QQuickWindow *window)
{
+ // Must always be called on the gui thread.
+
+ // Guard for recursion; relevant due to the MessageBox() on Windows.
+ static QSet<QQuickWindow *> recurseGuard;
+ if (recurseGuard.contains(window))
+ return;
+
+ recurseGuard.insert(window);
+
QString translatedMessage;
QString untranslatedMessage;
QQuickWindowPrivate::rhiCreationFailureMessage(QSGRhiSupport::instance()->rhiBackendName(),
@@ -266,6 +290,8 @@ void QSGRenderLoop::handleContextCreationFailure(QQuickWindow *window)
#endif // Q_OS_WIN
if (!signalEmitted)
qFatal("%s", qPrintable(untranslatedMessage));
+
+ recurseGuard.remove(window);
}
#ifdef ENABLE_DEFAULT_BACKEND
@@ -354,13 +380,28 @@ void QSGGuiThreadRenderLoop::windowDestroyed(QQuickWindow *window)
}
}
+void QSGGuiThreadRenderLoop::teardownGraphics()
+{
+ for (auto it = m_windows.begin(), itEnd = m_windows.end(); it != itEnd; ++it) {
+ if (it->rhi) {
+ QQuickWindowPrivate::get(it.key())->cleanupNodesOnShutdown();
+ if (it->rc)
+ it->rc->invalidate();
+ releaseSwapchain(it.key());
+ if (it->ownRhi)
+ QSGRhiSupport::instance()->destroyRhi(it->rhi, {});
+ it->rhi = nullptr;
+ }
+ }
+}
+
void QSGGuiThreadRenderLoop::handleDeviceLoss()
{
qWarning("Graphics device lost, cleaning up scenegraph and releasing RHIs");
for (auto it = m_windows.begin(), itEnd = m_windows.end(); it != itEnd; ++it) {
if (!it->rhi || !it->rhi->isDeviceLost())
- return;
+ continue;
QQuickWindowPrivate::get(it.key())->cleanupNodesOnShutdown();
@@ -424,7 +465,8 @@ bool QSGGuiThreadRenderLoop::ensureRhi(QQuickWindow *window, WindowData &data)
if (!offscreenSurface)
offscreenSurface = rhiSupport->maybeCreateOffscreenSurface(window);
- QSGRhiSupport::RhiCreateResult rhiResult = rhiSupport->createRhi(window, offscreenSurface);
+ const bool forcePreferSwRenderer = swRastFallbackDueToSwapchainFailure;
+ QSGRhiSupport::RhiCreateResult rhiResult = rhiSupport->createRhi(window, offscreenSurface, forcePreferSwRenderer);
data.rhi = rhiResult.rhi;
data.ownRhi = rhiResult.own;
@@ -496,7 +538,7 @@ bool QSGGuiThreadRenderLoop::ensureRhi(QQuickWindow *window, WindowData &data)
cd->swapchain->setDepthStencil(cd->depthStencilForSwapchain);
}
cd->swapchain->setWindow(window);
- rhiSupport->applySwapChainFormat(cd->swapchain);
+ rhiSupport->applySwapChainFormat(cd->swapchain, window);
qCDebug(QSG_LOG_INFO, "MSAA sample count for the swapchain is %d. Alpha channel requested = %s",
data.sampleCount, alpha ? "yes" : "no");
cd->swapchain->setSampleCount(data.sampleCount);
@@ -591,15 +633,24 @@ void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window)
// signals and want to do graphics stuff already there.
if (cd->swapchain) {
Q_ASSERT(!effectiveOutputSize.isEmpty());
+ QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
const QSize previousOutputSize = cd->swapchain->currentPixelSize();
if (previousOutputSize != effectiveOutputSize || cd->swapchainJustBecameRenderable) {
if (cd->swapchainJustBecameRenderable)
qCDebug(QSG_LOG_RENDERLOOP, "just became exposed");
cd->hasActiveSwapchain = cd->swapchain->createOrResize();
- if (!cd->hasActiveSwapchain && data.rhi->isDeviceLost()) {
- handleDeviceLoss();
- return;
+ if (!cd->hasActiveSwapchain) {
+ if (data.rhi->isDeviceLost()) {
+ handleDeviceLoss();
+ return;
+ } else if (previousOutputSize.isEmpty() && !swRastFallbackDueToSwapchainFailure && rhiSupport->attemptReinitWithSwRastUponFail()) {
+ qWarning("Failed to create swapchain."
+ " Retrying by requesting a software rasterizer, if applicable for the 3D API implementation.");
+ swRastFallbackDueToSwapchainFailure = true;
+ teardownGraphics();
+ return;
+ }
}
cd->swapchainJustBecameRenderable = false;
@@ -659,6 +710,7 @@ void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window)
Q_TRACE(QSG_swap_entry);
const bool needsPresent = alsoSwap && window->isVisible();
+ double lastCompletedGpuTime = 0;
if (cd->swapchain) {
QRhi::EndFrameFlags flags;
if (!needsPresent)
@@ -669,6 +721,8 @@ void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window)
handleDeviceLoss();
else if (frameResult == QRhi::FrameOpError)
qWarning("Failed to end frame");
+ } else {
+ lastCompletedGpuTime = cd->swapchain->currentFrameCommandBuffer()->lastCompletedGpuTime();
}
}
if (needsPresent)
@@ -694,6 +748,11 @@ void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window)
int((renderTime - syncTime) / 1000000),
int((swapTime - renderTime) / 1000000),
int(data.timeBetweenRenders.restart()));
+ if (!qFuzzyIsNull(lastCompletedGpuTime) && cd->graphicsConfig.timestampsEnabled()) {
+ qCDebug(QSG_LOG_TIME_RENDERLOOP, "[window %p][gui thread] syncAndRender: last retrieved GPU frame time was %.4f ms",
+ window,
+ lastCompletedGpuTime * 1000.0);
+ }
}
// Might have been set during syncSceneGraph()
diff --git a/src/quick/scenegraph/qsgrenderloop_p.h b/src/quick/scenegraph/qsgrenderloop_p.h
index 1a25154658..9157e2edd2 100644
--- a/src/quick/scenegraph/qsgrenderloop_p.h
+++ b/src/quick/scenegraph/qsgrenderloop_p.h
@@ -20,6 +20,7 @@
#include <private/qtquickglobal_p.h>
#include <QtCore/qset.h>
#include <QtCore/qobject.h>
+#include <QtCore/qcoreevent.h>
QT_BEGIN_NAMESPACE
@@ -29,7 +30,7 @@ class QSGRenderContext;
class QAnimationDriver;
class QRunnable;
-class Q_QUICK_PRIVATE_EXPORT QSGRenderLoop : public QObject
+class Q_QUICK_EXPORT QSGRenderLoop : public QObject
{
Q_OBJECT
@@ -94,6 +95,33 @@ enum QSGRenderLoopType
ThreadedRenderLoop
};
+enum QSGCustomEvents {
+
+// Passed from the RL to the RT when a window is removed obscured and
+// should be removed from the render loop.
+WM_Obscure = QEvent::User + 1,
+
+// Passed from the RL to RT when GUI has been locked, waiting for sync
+// (updatePaintNode())
+WM_RequestSync = QEvent::User + 2,
+
+// Passed by the RL to the RT to free up maybe release SG and GL contexts
+// if no windows are rendering.
+WM_TryRelease = QEvent::User + 4,
+
+// Passed by the RL to the RT when a QQuickWindow::grabWindow() is
+// called.
+WM_Grab = QEvent::User + 5,
+
+// Passed by the window when there is a render job to run
+WM_PostJob = QEvent::User + 6,
+
+// When using the QRhi this is sent upon PlatformSurfaceAboutToBeDestroyed from
+// the event filter installed on the QQuickWindow.
+WM_ReleaseSwapchain = QEvent::User + 7,
+
+};
+
QT_END_NAMESPACE
#endif // QSGRENDERLOOP_P_H
diff --git a/src/quick/scenegraph/qsgrhidistancefieldglyphcache.cpp b/src/quick/scenegraph/qsgrhidistancefieldglyphcache.cpp
index a68842577d..54cf298943 100644
--- a/src/quick/scenegraph/qsgrhidistancefieldglyphcache.cpp
+++ b/src/quick/scenegraph/qsgrhidistancefieldglyphcache.cpp
@@ -32,19 +32,10 @@ QSGRhiDistanceFieldGlyphCache::QSGRhiDistanceFieldGlyphCache(QSGDefaultRenderCon
QSGRhiDistanceFieldGlyphCache::~QSGRhiDistanceFieldGlyphCache()
{
- // A plain delete should work, but just in case commitResourceUpdates was
- // not called and something is enqueued on the update batch for a texture,
- // defer until the end of the frame.
- for (int i = 0; i < m_textures.size(); ++i) {
- if (m_textures[i].texture)
- m_textures[i].texture->deleteLater();
- }
+ for (const TextureInfo &t : std::as_const(m_textures))
+ m_rc->deferredReleaseGlyphCacheTexture(t.texture);
delete m_areaAllocator;
-
- // should be empty, but just in case
- for (QRhiTexture *t : std::as_const(m_pendingDispose))
- t->deleteLater();
}
void QSGRhiDistanceFieldGlyphCache::requestGlyphs(const QSet<glyph_t> &glyphs)
@@ -243,7 +234,7 @@ void QSGRhiDistanceFieldGlyphCache::resizeTexture(TextureInfo *texInfo, int widt
resourceUpdates->copyTexture(texInfo->texture, oldTexture);
}
- m_pendingDispose.insert(oldTexture);
+ m_rc->deferredReleaseGlyphCacheTexture(oldTexture);
}
bool QSGRhiDistanceFieldGlyphCache::useTextureResizeWorkaround() const
@@ -522,14 +513,8 @@ void QSGRhiDistanceFieldGlyphCache::commitResourceUpdates(QRhiResourceUpdateBatc
{
if (QRhiResourceUpdateBatch *resourceUpdates = m_rc->maybeGlyphCacheResourceUpdates()) {
mergeInto->merge(resourceUpdates);
- m_rc->releaseGlyphCacheResourceUpdates();
+ m_rc->resetGlyphCacheResources();
}
-
- // now let's assume the resource updates will be committed in this frame
- for (QRhiTexture *t : std::as_const(m_pendingDispose))
- t->deleteLater(); // will be deleted after the frame is submitted -> safe
-
- m_pendingDispose.clear();
}
bool QSGRhiDistanceFieldGlyphCache::eightBitFormatIsAlphaSwizzled() const
diff --git a/src/quick/scenegraph/qsgrhidistancefieldglyphcache_p.h b/src/quick/scenegraph/qsgrhidistancefieldglyphcache_p.h
index b7653881f5..4959cd2c50 100644
--- a/src/quick/scenegraph/qsgrhidistancefieldglyphcache_p.h
+++ b/src/quick/scenegraph/qsgrhidistancefieldglyphcache_p.h
@@ -17,13 +17,13 @@
#include "qsgadaptationlayer_p.h"
#include <private/qsgareaallocator_p.h>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
QT_BEGIN_NAMESPACE
class QSGDefaultRenderContext;
-class Q_QUICK_PRIVATE_EXPORT QSGRhiDistanceFieldGlyphCache : public QSGDistanceFieldGlyphCache
+class Q_QUICK_EXPORT QSGRhiDistanceFieldGlyphCache : public QSGDistanceFieldGlyphCache
{
public:
QSGRhiDistanceFieldGlyphCache(QSGDefaultRenderContext *rc, const QRawFont &font, int renderTypeQuality);
diff --git a/src/quick/scenegraph/qsgrhiinternaltextnode.cpp b/src/quick/scenegraph/qsgrhiinternaltextnode.cpp
new file mode 100644
index 0000000000..6108ff7044
--- /dev/null
+++ b/src/quick/scenegraph/qsgrhiinternaltextnode.cpp
@@ -0,0 +1,41 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsgrhiinternaltextnode_p.h"
+
+#include <private/qquadpath_p.h>
+#include <private/qsgcurvefillnode_p.h>
+#include <private/qsgcurvestrokenode_p.h>
+#include <private/qsgcurveprocessor_p.h>
+
+QT_BEGIN_NAMESPACE
+
+QSGRhiInternalTextNode::QSGRhiInternalTextNode(QSGRenderContext *renderContext)
+ : QSGInternalTextNode(renderContext)
+{
+}
+
+void QSGRhiInternalTextNode::addDecorationNode(const QRectF &rect, const QColor &color)
+{
+ QSGCurveStrokeNode *node = new QSGCurveStrokeNode;
+ node->setColor(color);
+ node->setStrokeWidth(rect.height());
+
+ QQuadPath path;
+ QPointF c = rect.center();
+ path.moveTo(QVector2D(rect.left(), c.y()));
+ path.lineTo(QVector2D(rect.right(), c.y()));
+
+ QSGCurveProcessor::processStroke(path, 2, rect.height(), Qt::MiterJoin, Qt::FlatCap,
+ [&node](const std::array<QVector2D, 3> &s,
+ const std::array<QVector2D, 3> &p,
+ const std::array<QVector2D, 3> &n,
+ bool isLine) {
+ Q_ASSERT(isLine);
+ node->appendTriangle(s, std::array<QVector2D, 2>{p.at(0), p.at(2)}, n);
+ });
+ node->cookGeometry();
+ appendChildNode(node);
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgrhiinternaltextnode_p.h b/src/quick/scenegraph/qsgrhiinternaltextnode_p.h
new file mode 100644
index 0000000000..88980773b3
--- /dev/null
+++ b/src/quick/scenegraph/qsgrhiinternaltextnode_p.h
@@ -0,0 +1,31 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGRHIINTERNALTEXTNODE_P_H
+#define QSGRHIINTERNALTEXTNODE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qsginternaltextnode_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSGRhiInternalTextNode : public QSGInternalTextNode
+{
+public:
+ QSGRhiInternalTextNode(QSGRenderContext *renderContext);
+ void addDecorationNode(const QRectF &rect, const QColor &color) override;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGRHIINTERNALTEXTNODE_P_H
diff --git a/src/quick/scenegraph/qsgrhilayer.cpp b/src/quick/scenegraph/qsgrhilayer.cpp
index 14ed4dee6d..5299cb54ce 100644
--- a/src/quick/scenegraph/qsgrhilayer.cpp
+++ b/src/quick/scenegraph/qsgrhilayer.cpp
@@ -443,10 +443,13 @@ QImage QSGRhiLayer::toImage() const
return QImage();
}
- // There is no room for negotiation here, the texture is RGBA8, and the
- // readback happens with GL_RGBA on GL, so RGBA8888 is the only option.
+ // There is little room for negotiation here, the texture is one of the formats from setFormat.
// Also, Quick is always premultiplied alpha.
- const QImage::Format imageFormat = QImage::Format_RGBA8888_Premultiplied;
+ QImage::Format imageFormat = QImage::Format_RGBA8888_Premultiplied;
+ if (m_format == QRhiTexture::RGBA16F)
+ imageFormat = QImage::Format_RGBA16FPx4_Premultiplied;
+ else if (m_format == QRhiTexture::RGBA32F)
+ imageFormat = QImage::Format_RGBA32FPx4_Premultiplied;
const uchar *p = reinterpret_cast<const uchar *>(result.data.constData());
return QImage(p, result.pixelSize.width(), result.pixelSize.height(), imageFormat).mirrored();
diff --git a/src/quick/scenegraph/qsgrhilayer_p.h b/src/quick/scenegraph/qsgrhilayer_p.h
index 502672b725..922192ec5e 100644
--- a/src/quick/scenegraph/qsgrhilayer_p.h
+++ b/src/quick/scenegraph/qsgrhilayer_p.h
@@ -17,13 +17,13 @@
#include <private/qsgadaptationlayer_p.h>
#include <private/qsgcontext_p.h>
#include <private/qsgtexture_p.h>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
QT_BEGIN_NAMESPACE
class QSGDefaultRenderContext;
-class Q_QUICK_PRIVATE_EXPORT QSGRhiLayer : public QSGLayer
+class Q_QUICK_EXPORT QSGRhiLayer : public QSGLayer
{
Q_OBJECT
diff --git a/src/quick/scenegraph/qsgrhishadereffectnode.cpp b/src/quick/scenegraph/qsgrhishadereffectnode.cpp
index 6d6e774918..c3b7b42cb9 100644
--- a/src/quick/scenegraph/qsgrhishadereffectnode.cpp
+++ b/src/quick/scenegraph/qsgrhishadereffectnode.cpp
@@ -7,7 +7,7 @@
#include <qsgmaterialshader.h>
#include <qsgtextureprovider.h>
#include <private/qsgplaintexture_p.h>
-#include <QtGui/private/qshaderdescription_p.h>
+#include <rhi/qshaderdescription.h>
#include <QQmlFile>
#include <QFile>
#include <QFileSelector>
@@ -23,7 +23,7 @@ void QSGRhiShaderLinker::reset(const QShader &vs, const QShader &fs)
m_error = false;
- m_constantBufferSize = 0;
+ //m_constantBufferSize = 0;
m_constants.clear();
m_samplers.clear();
m_samplerNameMap.clear();
@@ -39,7 +39,6 @@ void QSGRhiShaderLinker::feedConstants(const QSGShaderEffectNode::ShaderData &sh
Q_ASSERT(shader.shaderInfo.variables.size() == shader.varData.size());
static bool shaderEffectDebug = qEnvironmentVariableIntValue("QSG_RHI_SHADEREFFECT_DEBUG");
if (!dirtyIndices) {
- m_constantBufferSize = qMax(m_constantBufferSize, shader.shaderInfo.constantDataSize);
for (int i = 0; i < shader.shaderInfo.variables.size(); ++i) {
const QSGGuiThreadShaderEffectManager::ShaderInfo::Variable &var(shader.shaderInfo.variables.at(i));
if (var.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Constant) {
@@ -86,6 +85,12 @@ void QSGRhiShaderLinker::feedSamplers(const QSGShaderEffectNode::ShaderData &sha
const QSGShaderEffectNode::VariableData &vd(shader.varData.at(i));
if (var.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Sampler) {
Q_ASSERT(vd.specialType == QSGShaderEffectNode::VariableData::Source);
+
+#ifndef QT_NO_DEBUG
+ int existingBindPoint = m_samplerNameMap.value(var.name, -1);
+ Q_ASSERT(existingBindPoint < 0 || existingBindPoint == var.bindPoint);
+#endif
+
m_samplers.insert(var.bindPoint, vd.value);
m_samplerNameMap.insert(var.name, var.bindPoint);
}
@@ -94,6 +99,12 @@ void QSGRhiShaderLinker::feedSamplers(const QSGShaderEffectNode::ShaderData &sha
for (int idx : *dirtyIndices) {
const QSGGuiThreadShaderEffectManager::ShaderInfo::Variable &var(shader.shaderInfo.variables.at(idx));
const QSGShaderEffectNode::VariableData &vd(shader.varData.at(idx));
+
+#ifndef QT_NO_DEBUG
+ int existingBindPoint = m_samplerNameMap.value(var.name, -1);
+ Q_ASSERT(existingBindPoint < 0 || existingBindPoint == var.bindPoint);
+#endif
+
m_samplers.insert(var.bindPoint, vd.value);
m_samplerNameMap.insert(var.name, var.bindPoint);
}
@@ -125,7 +136,7 @@ void QSGRhiShaderLinker::dump()
qDebug() << "Failed to generate program data";
return;
}
- qDebug() << "Combined shader data" << m_vs << m_fs << "cbuffer size" << m_constantBufferSize;
+ qDebug() << "Combined shader data" << m_vs << m_fs;
qDebug() << " - constants" << m_constants;
qDebug() << " - samplers" << m_samplers;
}
@@ -176,7 +187,7 @@ struct QSGRhiShaderMaterialTypeCache
QSGMaterialType *type;
};
QHash<Key, MaterialType> m_types;
- QVector<QSGMaterialType *> m_graveyard;
+ QHash<Key, QSGMaterialType *> m_graveyard;
};
size_t qHash(const QSGRhiShaderMaterialTypeCache::Key &key, size_t seed = 0)
@@ -196,6 +207,14 @@ QSGMaterialType *QSGRhiShaderMaterialTypeCache::ref(const QShader &vs, const QSh
return it->type;
}
+ auto reuseIt = m_graveyard.constFind(k);
+ if (reuseIt != m_graveyard.cend()) {
+ QSGMaterialType *t = reuseIt.value();
+ m_types.insert(k, { 1, t });
+ m_graveyard.erase(reuseIt);
+ return t;
+ }
+
QSGMaterialType *t = new QSGMaterialType;
m_types.insert(k, { 1, t });
return t;
@@ -208,7 +227,7 @@ void QSGRhiShaderMaterialTypeCache::unref(const QShader &vs, const QShader &fs)
auto it = m_types.find(k);
if (it != m_types.end()) {
if (!--it->ref) {
- m_graveyard.append(it->type);
+ m_graveyard.insert(k, it->type);
m_types.erase(it);
}
}
@@ -286,8 +305,27 @@ bool QSGRhiShaderEffectMaterialShader::updateUniformData(RenderState &state, QSG
}
} else if (c.specialType == QSGShaderEffectNode::VariableData::Matrix) {
if (state.isMatrixDirty()) {
- const QMatrix4x4 m = state.combinedMatrix();
- fillUniformBlockMember<float>(dst, m.constData(), 16, c.size);
+ Q_ASSERT(state.projectionMatrixCount() == mat->viewCount());
+ const int rendererViewCount = state.projectionMatrixCount();
+ const int shaderMatrixCount = c.size / 64;
+ if (shaderMatrixCount < mat->viewCount() && mat->viewCount() >= 2) {
+ qWarning("qt_Matrix uniform block member size is wrong: expected at least view_count * 64 bytes, "
+ "where view_count is %d, meaning %d bytes in total, but got only %d bytes. "
+ "This may be due to the ShaderEffect and its shaders not being multiview-capable, "
+ "or they are used with an unexpected render target. "
+ "Check if the shaders declare qt_Matrix as appropriate, "
+ "and if gl_ViewIndex is used correctly in the vertex shader.",
+ mat->viewCount(), mat->viewCount() * 64, c.size);
+ }
+ const int matrixCount = qMin(rendererViewCount, shaderMatrixCount);
+ size_t offset = 0;
+ for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
+ const QMatrix4x4 m = state.combinedMatrix(viewIndex);
+ fillUniformBlockMember<float>(dst + offset, m.constData(), 16, 64);
+ offset += 64;
+ }
+ if (offset < c.size)
+ memset(dst + offset, 0, c.size - offset);
changed = true;
}
} else if (c.specialType == QSGShaderEffectNode::VariableData::SubRect) {
@@ -651,10 +689,42 @@ static QShader loadShaderFromFile(const QString &filename)
return QShader::fromSerialized(f.readAll());
}
+struct QSGRhiShaderEffectDefaultShader
+{
+ QShader shader;
+ quint32 matrixArrayByteSize;
+ quint32 opacityOffset;
+ qint8 viewCount;
+ static QSGRhiShaderEffectDefaultShader create(const QString &filename, int viewCount);
+};
+
+QSGRhiShaderEffectDefaultShader QSGRhiShaderEffectDefaultShader::create(const QString &filename, int viewCount)
+{
+ QSGRhiShaderEffectDefaultShader s;
+ s.shader = loadShaderFromFile(filename);
+ const QList<QShaderDescription::BlockVariable> uboMembers = s.shader.description().uniformBlocks().constFirst().members;
+ for (const auto &member: uboMembers) {
+ if (member.name == QByteArrayLiteral("qt_Matrix"))
+ s.matrixArrayByteSize = member.size;
+ else if (member.name == QByteArrayLiteral("qt_Opacity"))
+ s.opacityOffset = member.offset;
+ }
+ s.viewCount = viewCount;
+ return s;
+}
+
void QSGRhiShaderEffectNode::syncMaterial(SyncData *syncData)
{
- static QShader defaultVertexShader;
- static QShader defaultFragmentShader;
+ static const int defaultVertexShaderCount = 2;
+ static QSGRhiShaderEffectDefaultShader defaultVertexShaders[defaultVertexShaderCount] = {
+ QSGRhiShaderEffectDefaultShader::create(QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shadereffect.vert.qsb"), 1),
+ QSGRhiShaderEffectDefaultShader::create(QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shadereffect.vert.qsb.mv2qsb"), 2)
+ };
+ static const int defaultFragmentShaderCount = 2;
+ static QSGRhiShaderEffectDefaultShader defaultFragmentShaders[defaultFragmentShaderCount] = {
+ QSGRhiShaderEffectDefaultShader::create(QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shadereffect.frag.qsb"), 1),
+ QSGRhiShaderEffectDefaultShader::create(QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shadereffect.frag.qsb.mv2qsb"), 2)
+ };
if (bool(m_material.flags() & QSGMaterial::Blending) != syncData->blending) {
m_material.setFlag(QSGMaterial::Blending, syncData->blending);
@@ -673,21 +743,45 @@ void QSGRhiShaderEffectNode::syncMaterial(SyncData *syncData)
}
m_material.m_hasCustomVertexShader = syncData->vertex.shader->hasShaderCode;
+ quint32 defaultMatrixArrayByteSize = 0;
if (m_material.m_hasCustomVertexShader) {
m_material.m_vertexShader = syncData->vertex.shader->shaderInfo.rhiShader;
} else {
- if (!defaultVertexShader.isValid())
- defaultVertexShader = loadShaderFromFile(QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shadereffect.vert.qsb"));
- m_material.m_vertexShader = defaultVertexShader;
+ bool found = false;
+ for (int i = 0; i < defaultVertexShaderCount; ++i) {
+ if (defaultVertexShaders[i].viewCount == syncData->viewCount) {
+ m_material.m_vertexShader = defaultVertexShaders[i].shader;
+ defaultMatrixArrayByteSize = defaultVertexShaders[i].matrixArrayByteSize;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ qWarning("No default vertex shader found for view count %d", syncData->viewCount);
+ m_material.m_vertexShader = defaultVertexShaders[0].shader;
+ defaultMatrixArrayByteSize = 64;
+ }
}
m_material.m_hasCustomFragmentShader = syncData->fragment.shader->hasShaderCode;
+ quint32 defaultOpacityOffset = 0;
if (m_material.m_hasCustomFragmentShader) {
m_material.m_fragmentShader = syncData->fragment.shader->shaderInfo.rhiShader;
} else {
- if (!defaultFragmentShader.isValid())
- defaultFragmentShader = loadShaderFromFile(QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shadereffect.frag.qsb"));
- m_material.m_fragmentShader = defaultFragmentShader;
+ bool found = false;
+ for (int i = 0; i < defaultFragmentShaderCount; ++i) {
+ if (defaultFragmentShaders[i].viewCount == syncData->viewCount) {
+ m_material.m_fragmentShader = defaultFragmentShaders[i].shader;
+ defaultOpacityOffset = defaultFragmentShaders[i].opacityOffset;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ qWarning("No default fragment shader found for view count %d", syncData->viewCount);
+ m_material.m_fragmentShader = defaultFragmentShaders[0].shader;
+ defaultOpacityOffset = 64;
+ }
}
m_material.m_materialType = shaderMaterialTypeCache[syncData->materialTypeCacheKey].ref(m_material.m_vertexShader,
@@ -705,16 +799,15 @@ void QSGRhiShaderEffectNode::syncMaterial(SyncData *syncData)
defaultSD.shaderInfo.rhiShader = m_material.m_vertexShader;
defaultSD.shaderInfo.type = QSGGuiThreadShaderEffectManager::ShaderInfo::TypeVertex;
- // { mat4 qt_Matrix; float qt_Opacity; } where only the matrix is used
+ // { mat4 qt_Matrix[VIEW_COUNT]; float qt_Opacity; } where only the matrix is used
QSGGuiThreadShaderEffectManager::ShaderInfo::Variable v;
v.name = QByteArrayLiteral("qt_Matrix");
v.offset = 0;
- v.size = 16 * sizeof(float);
+ v.size = defaultMatrixArrayByteSize;
defaultSD.shaderInfo.variables.append(v);
QSGShaderEffectNode::VariableData vd;
vd.specialType = QSGShaderEffectNode::VariableData::Matrix;
defaultSD.varData.append(vd);
- defaultSD.shaderInfo.constantDataSize = (16 + 1) * sizeof(float);
m_material.m_linker.feedConstants(defaultSD);
}
@@ -727,10 +820,10 @@ void QSGRhiShaderEffectNode::syncMaterial(SyncData *syncData)
defaultSD.shaderInfo.rhiShader = m_material.m_fragmentShader;
defaultSD.shaderInfo.type = QSGGuiThreadShaderEffectManager::ShaderInfo::TypeFragment;
- // { mat4 qt_Matrix; float qt_Opacity; } where only the opacity is used
+ // { mat4 qt_Matrix[VIEW_COUNT]; float qt_Opacity; } where only the opacity is used
QSGGuiThreadShaderEffectManager::ShaderInfo::Variable v;
v.name = QByteArrayLiteral("qt_Opacity");
- v.offset = 16 * sizeof(float);
+ v.offset = defaultOpacityOffset;
v.size = sizeof(float);
defaultSD.shaderInfo.variables.append(v);
QSGShaderEffectNode::VariableData vd;
@@ -750,8 +843,6 @@ void QSGRhiShaderEffectNode::syncMaterial(SyncData *syncData)
vd.specialType = QSGShaderEffectNode::VariableData::Source;
defaultSD.varData.append(vd);
- defaultSD.shaderInfo.constantDataSize = (16 + 1) * sizeof(float);
-
m_material.m_linker.feedConstants(defaultSD);
m_material.m_linker.feedSamplers(defaultSD);
}
@@ -875,7 +966,6 @@ bool QSGRhiGuiThreadShaderEffectManager::reflect(ShaderInfo *result)
}
const QShaderDescription desc = result->rhiShader.description();
- result->constantDataSize = 0;
int ubufBinding = -1;
const QVector<QShaderDescription::UniformBlock> ubufs = desc.uniformBlocks();
@@ -884,7 +974,6 @@ bool QSGRhiGuiThreadShaderEffectManager::reflect(ShaderInfo *result)
const QShaderDescription::UniformBlock &ubuf(ubufs[i]);
if (ubufBinding == -1 && ubuf.binding >= 0) {
ubufBinding = ubuf.binding;
- result->constantDataSize = ubuf.size;
for (const QShaderDescription::BlockVariable &member : ubuf.members) {
ShaderInfo::Variable v;
v.type = ShaderInfo::Constant;
diff --git a/src/quick/scenegraph/qsgrhishadereffectnode_p.h b/src/quick/scenegraph/qsgrhishadereffectnode_p.h
index ed3bf1f1e9..376c7d38ab 100644
--- a/src/quick/scenegraph/qsgrhishadereffectnode_p.h
+++ b/src/quick/scenegraph/qsgrhishadereffectnode_p.h
@@ -50,7 +50,6 @@ public:
bool m_error;
QShader m_vs;
QShader m_fs;
- uint m_constantBufferSize;
QHash<uint, Constant> m_constants; // offset -> Constant
QHash<int, QVariant> m_samplers; // binding -> value (source ref)
QHash<QByteArray, int> m_samplerNameMap; // name -> binding
diff --git a/src/quick/scenegraph/qsgrhisupport.cpp b/src/quick/scenegraph/qsgrhisupport.cpp
index f18d6a7d00..45c183a5f8 100644
--- a/src/quick/scenegraph/qsgrhisupport.cpp
+++ b/src/quick/scenegraph/qsgrhisupport.cpp
@@ -50,19 +50,22 @@ void QSGRhiSupport::applySettings()
if (m_requested.valid) {
// explicit rhi backend request from C++ (e.g. via QQuickWindow)
switch (m_requested.api) {
- case QSGRendererInterface::OpenGLRhi:
+ case QSGRendererInterface::OpenGL:
m_rhiBackend = QRhi::OpenGLES2;
break;
- case QSGRendererInterface::Direct3D11Rhi:
+ case QSGRendererInterface::Direct3D11:
m_rhiBackend = QRhi::D3D11;
break;
- case QSGRendererInterface::VulkanRhi:
+ case QSGRendererInterface::Direct3D12:
+ m_rhiBackend = QRhi::D3D12;
+ break;
+ case QSGRendererInterface::Vulkan:
m_rhiBackend = QRhi::Vulkan;
break;
- case QSGRendererInterface::MetalRhi:
+ case QSGRendererInterface::Metal:
m_rhiBackend = QRhi::Metal;
break;
- case QSGRendererInterface::NullRhi:
+ case QSGRendererInterface::Null:
m_rhiBackend = QRhi::Null;
break;
default:
@@ -79,6 +82,8 @@ void QSGRhiSupport::applySettings()
m_rhiBackend = QRhi::OpenGLES2;
} else if (rhiBackend == QByteArrayLiteral("d3d11") || rhiBackend == QByteArrayLiteral("d3d")) {
m_rhiBackend = QRhi::D3D11;
+ } else if (rhiBackend == QByteArrayLiteral("d3d12")) {
+ m_rhiBackend = QRhi::D3D12;
} else if (rhiBackend == QByteArrayLiteral("vulkan")) {
m_rhiBackend = QRhi::Vulkan;
} else if (rhiBackend == QByteArrayLiteral("metal")) {
@@ -92,7 +97,7 @@ void QSGRhiSupport::applySettings()
}
#if defined(Q_OS_WIN)
m_rhiBackend = QRhi::D3D11;
-#elif defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+#elif QT_CONFIG(metal)
m_rhiBackend = QRhi::Metal;
#elif QT_CONFIG(opengl)
m_rhiBackend = QRhi::OpenGLES2;
@@ -111,26 +116,11 @@ void QSGRhiSupport::applySettings()
// (QQuickWindow) may depend on the graphics API as well (surfaceType
// f.ex.), and all that is based on what we report from here. So further
// adjustments are not possible (or, at minimum, not safe and portable).
-
- m_killDeviceFrameCount = qEnvironmentVariableIntValue("QSG_RHI_SIMULATE_DEVICE_LOSS");
- if (m_killDeviceFrameCount > 0 && m_rhiBackend == QRhi::D3D11)
- qDebug("Graphics device will be reset every %d frames", m_killDeviceFrameCount);
-
- QByteArray hdrRequest = qgetenv("QSG_RHI_HDR");
- if (!hdrRequest.isEmpty()) {
- hdrRequest = hdrRequest.toLower();
- if (hdrRequest == QByteArrayLiteral("scrgb") || hdrRequest == QByteArrayLiteral("extendedsrgblinear"))
- m_swapChainFormat = QRhiSwapChain::HDRExtendedSrgbLinear;
- else if (hdrRequest == QByteArrayLiteral("hdr10"))
- m_swapChainFormat = QRhiSwapChain::HDR10;
- else
- qWarning("Unknown HDR mode '%s'", hdrRequest.constData());
- }
}
void QSGRhiSupport::adjustToPlatformQuirks()
{
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+#if QT_CONFIG(metal)
// A macOS VM may not have Metal support at all. We have to decide at this
// point, it will be too late afterwards, and the only way is to see if
// MTLCreateSystemDefaultDevice succeeds.
@@ -225,12 +215,24 @@ void QSGRhiSupport::checkEnvQSgInfo()
#define GL_RGB10_A2 0x8059
#endif
-QRhiTexture::Format QSGRhiSupport::toRhiTextureFormatFromGL(uint format)
+#ifndef GL_SRGB_ALPHA
+#define GL_SRGB_ALPHA 0x8C42
+#endif
+
+#ifndef GL_SRGB8_ALPHA8
+#define GL_SRGB8_ALPHA8 0x8C43
+#endif
+
+QRhiTexture::Format QSGRhiSupport::toRhiTextureFormatFromGL(uint format, QRhiTexture::Flags *flags)
{
+ bool sRGB = false;
auto rhiFormat = QRhiTexture::UnknownFormat;
switch (format) {
- case GL_RGBA:
+ case GL_SRGB_ALPHA:
+ case GL_SRGB8_ALPHA8:
+ sRGB = true;
Q_FALLTHROUGH();
+ case GL_RGBA:
case GL_RGBA8:
case 0:
rhiFormat = QRhiTexture::RGBA8;
@@ -292,6 +294,8 @@ QRhiTexture::Format QSGRhiSupport::toRhiTextureFormatFromGL(uint format)
qWarning("GL format %d is not supported", format);
break;
}
+ if (sRGB)
+ (*flags) |=(QRhiTexture::sRGB);
return rhiFormat;
}
#endif
@@ -508,7 +512,7 @@ QRhiTexture::Format QSGRhiSupport::toRhiTextureFormatFromVulkan(uint format, QRh
#endif
#ifdef Q_OS_WIN
-QRhiTexture::Format QSGRhiSupport::toRhiTextureFormatFromD3D11(uint format, QRhiTexture::Flags *flags)
+QRhiTexture::Format QSGRhiSupport::toRhiTextureFormatFromDXGI(uint format, QRhiTexture::Flags *flags)
{
auto rhiFormat = QRhiTexture::UnknownFormat;
bool sRGB = false;
@@ -608,7 +612,7 @@ QRhiTexture::Format QSGRhiSupport::toRhiTextureFormatFromD3D11(uint format, QRhi
}
#endif
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+#if QT_CONFIG(metal)
namespace QSGRhiSupportMac {
QRhiTexture::Format toRhiTextureFormatFromMetal(uint format, QRhiTexture::Flags *flags);
}
@@ -655,15 +659,17 @@ QSGRendererInterface::GraphicsApi QSGRhiSupport::graphicsApi() const
{
switch (m_rhiBackend) {
case QRhi::Null:
- return QSGRendererInterface::NullRhi;
+ return QSGRendererInterface::Null;
case QRhi::Vulkan:
- return QSGRendererInterface::VulkanRhi;
+ return QSGRendererInterface::Vulkan;
case QRhi::OpenGLES2:
- return QSGRendererInterface::OpenGLRhi;
+ return QSGRendererInterface::OpenGL;
case QRhi::D3D11:
- return QSGRendererInterface::Direct3D11Rhi;
+ return QSGRendererInterface::Direct3D11;
+ case QRhi::D3D12:
+ return QSGRendererInterface::Direct3D12;
case QRhi::Metal:
- return QSGRendererInterface::MetalRhi;
+ return QSGRendererInterface::Metal;
default:
return QSGRendererInterface::Unknown;
}
@@ -677,6 +683,7 @@ QSurface::SurfaceType QSGRhiSupport::windowSurfaceType() const
case QRhi::OpenGLES2:
return QSurface::OpenGLSurface;
case QRhi::D3D11:
+ case QRhi::D3D12:
return QSurface::Direct3DSurface;
case QRhi::Metal:
return QSurface::MetalSurface;
@@ -714,6 +721,10 @@ static const void *qsgrhi_vk_rifResource(QSGRendererInterface::Resource res,
return &maybeVkRpNat->renderPass;
else
return nullptr;
+ case QSGRendererInterface::GraphicsQueueFamilyIndexResource:
+ return &vknat->gfxQueueFamilyIdx;
+ case QSGRendererInterface::GraphicsQueueIndexResource:
+ return &vknat->gfxQueueIdx;
default:
return nullptr;
}
@@ -746,9 +757,22 @@ static const void *qsgrhi_d3d11_rifResource(QSGRendererInterface::Resource res,
return nullptr;
}
}
+
+static const void *qsgrhi_d3d12_rifResource(QSGRendererInterface::Resource res, const QRhiNativeHandles *nat)
+{
+ const QRhiD3D12NativeHandles *d3dnat = static_cast<const QRhiD3D12NativeHandles *>(nat);
+ switch (res) {
+ case QSGRendererInterface::DeviceResource:
+ return d3dnat->dev;
+ case QSGRendererInterface::CommandQueueResource:
+ return d3dnat->commandQueue;
+ default:
+ return nullptr;
+ }
+}
#endif
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+#if QT_CONFIG(metal)
static const void *qsgrhi_mtl_rifResource(QSGRendererInterface::Resource res, const QRhiNativeHandles *nat,
const QRhiNativeHandles *cbNat)
{
@@ -795,7 +819,7 @@ const void *QSGRhiSupport::rifResource(QSGRendererInterface::Resource res,
case QSGRendererInterface::RhiRedirectCommandBuffer:
return QQuickWindowPrivate::get(w)->redirect.commandBuffer;
case QSGRendererInterface::RhiRedirectRenderTarget:
- return QQuickWindowPrivate::get(w)->redirect.rt.renderTarget;
+ return QQuickWindowPrivate::get(w)->redirect.rt.rt.renderTarget;
default:
break;
}
@@ -822,8 +846,10 @@ const void *QSGRhiSupport::rifResource(QSGRendererInterface::Resource res,
#ifdef Q_OS_WIN
case QRhi::D3D11:
return qsgrhi_d3d11_rifResource(res, nat);
+ case QRhi::D3D12:
+ return qsgrhi_d3d12_rifResource(res, nat);
#endif
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+#if QT_CONFIG(metal)
case QRhi::Metal:
{
QRhiCommandBuffer *cb = rc->currentFrameCommandBuffer();
@@ -1026,8 +1052,18 @@ void QSGRhiSupport::finalizePipelineCache(QRhi *rhi, const QQuickGraphicsConfigu
// If empty, do nothing. This is exactly what will happen if the rhi was
// created without QRhi::EnablePipelineCacheDataSave set.
- if (buf.isEmpty())
+ if (buf.isEmpty()) {
+ if (isAutomatic) {
+ // Attempt to remove the file. If it does not exist or this fails,
+ // that's fine. The goal is just to prevent warnings from
+ // setPipelineCacheData in future runs, e.g. if the Qt or driver
+ // version does not match _and_ we do not generate any data at run
+ // time, then not writing the file out also means the warning would
+ // appear again and again on every run. Prevent that.
+ QDir().remove(pipelineCacheSave);
+ }
return;
+ }
QLockFile lock(pipelineCacheLockFileName(pipelineCacheSave));
if (!lock.lock()) {
@@ -1068,7 +1104,7 @@ void QSGRhiSupport::finalizePipelineCache(QRhi *rhi, const QQuickGraphicsConfigu
}
// must be called on the render thread
-QSGRhiSupport::RhiCreateResult QSGRhiSupport::createRhi(QQuickWindow *window, QSurface *offscreenSurface)
+QSGRhiSupport::RhiCreateResult QSGRhiSupport::createRhi(QQuickWindow *window, QSurface *offscreenSurface, bool forcePreferSwRenderer)
{
QRhi *rhi = nullptr;
QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
@@ -1083,7 +1119,8 @@ QSGRhiSupport::RhiCreateResult QSGRhiSupport::createRhi(QQuickWindow *window, QS
const bool debugLayer = wd->graphicsConfig.isDebugLayerEnabled();
const bool debugMarkers = wd->graphicsConfig.isDebugMarkersEnabled();
- const bool preferSoftware = wd->graphicsConfig.prefersSoftwareDevice();
+ const bool timestamps = wd->graphicsConfig.timestampsEnabled();
+ const bool preferSoftware = wd->graphicsConfig.prefersSoftwareDevice() || forcePreferSwRenderer;
const bool pipelineCacheSave = !wd->graphicsConfig.pipelineCacheSaveFile().isEmpty()
|| (wd->graphicsConfig.isAutomaticPipelineCacheEnabled()
&& !isAutomaticPipelineCacheSaveSkippedForWindow(window->flags()));
@@ -1093,13 +1130,18 @@ QSGRhiSupport::RhiCreateResult QSGRhiSupport::createRhi(QQuickWindow *window, QS
"Creating QRhi with backend %s for window %p (wflags 0x%X)\n"
" Graphics API debug/validation layers: %d\n"
" Debug markers: %d\n"
- " Prefer software device: %d\n"
+ " Timestamps: %d\n"
+ " Prefer software device: %d%s\n"
" Shader/pipeline cache collection: %d",
- qPrintable(backendName), window, int(window->flags()), debugLayer, debugMarkers, preferSoftware, pipelineCacheSave);
+ qPrintable(backendName), window, int(window->flags()), debugLayer,
+ debugMarkers, timestamps, preferSoftware, forcePreferSwRenderer ? " [FORCED]" : "", pipelineCacheSave);
QRhi::Flags flags;
+ flags |= QRhi::SuppressSmokeTestWarnings;
if (debugMarkers)
flags |= QRhi::EnableDebugMarkers;
+ if (timestamps)
+ flags |= QRhi::EnableTimestamps;
if (preferSoftware)
flags |= QRhi::PreferSoftwareRenderer;
if (pipelineCacheSave)
@@ -1169,10 +1211,6 @@ QSGRhiSupport::RhiCreateResult QSGRhiSupport::createRhi(QQuickWindow *window, QS
if (backend == QRhi::D3D11) {
QRhiD3D11InitParams rhiParams;
rhiParams.enableDebugLayer = debugLayer;
- if (m_killDeviceFrameCount > 0) {
- rhiParams.framesUntilKillingDeviceViaTdr = m_killDeviceFrameCount;
- rhiParams.repeatDeviceKill = true;
- }
if (customDevD->type == QQuickGraphicsDevicePrivate::Type::DeviceAndContext) {
QRhiD3D11NativeHandles importDev;
importDev.dev = customDevD->u.deviceAndContext.device;
@@ -1190,7 +1228,32 @@ QSGRhiSupport::RhiCreateResult QSGRhiSupport::createRhi(QQuickWindow *window, QS
rhi = QRhi::create(backend, &rhiParams, flags, &importDev);
} else {
rhi = QRhi::create(backend, &rhiParams, flags);
- if (!rhi && !flags.testFlag(QRhi::PreferSoftwareRenderer)) {
+ if (!rhi && attemptReinitWithSwRastUponFail() && !flags.testFlag(QRhi::PreferSoftwareRenderer)) {
+ qCDebug(QSG_LOG_INFO, "Failed to create a D3D device with default settings; "
+ "attempting to get a software rasterizer backed device instead");
+ flags |= QRhi::PreferSoftwareRenderer;
+ rhi = QRhi::create(backend, &rhiParams, flags);
+ }
+ }
+ } else if (backend == QRhi::D3D12) {
+ QRhiD3D12InitParams rhiParams;
+ rhiParams.enableDebugLayer = debugLayer;
+ if (customDevD->type == QQuickGraphicsDevicePrivate::Type::DeviceAndContext) {
+ QRhiD3D12NativeHandles importDev;
+ importDev.dev = customDevD->u.deviceAndContext.device;
+ qCDebug(QSG_LOG_INFO, "Using existing native D3D12 device %p", importDev.dev);
+ rhi = QRhi::create(backend, &rhiParams, flags, &importDev);
+ } else if (customDevD->type == QQuickGraphicsDevicePrivate::Type::Adapter) {
+ QRhiD3D12NativeHandles importDev;
+ importDev.adapterLuidLow = customDevD->u.adapter.luidLow;
+ importDev.adapterLuidHigh = customDevD->u.adapter.luidHigh;
+ importDev.minimumFeatureLevel = customDevD->u.adapter.featureLevel;
+ qCDebug(QSG_LOG_INFO, "Using D3D12 adapter LUID %u, %d and minimum feature level %d",
+ importDev.adapterLuidLow, importDev.adapterLuidHigh, importDev.minimumFeatureLevel);
+ rhi = QRhi::create(backend, &rhiParams, flags, &importDev);
+ } else {
+ rhi = QRhi::create(backend, &rhiParams, flags);
+ if (!rhi && attemptReinitWithSwRastUponFail() && !flags.testFlag(QRhi::PreferSoftwareRenderer)) {
qCDebug(QSG_LOG_INFO, "Failed to create a D3D device with default settings; "
"attempting to get a software rasterizer backed device instead");
flags |= QRhi::PreferSoftwareRenderer;
@@ -1199,7 +1262,7 @@ QSGRhiSupport::RhiCreateResult QSGRhiSupport::createRhi(QQuickWindow *window, QS
}
}
#endif
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+#if QT_CONFIG(metal)
if (backend == QRhi::Metal) {
QRhiMetalInitParams rhiParams;
if (customDevD->type == QQuickGraphicsDevicePrivate::Type::DeviceAndCommandQueue) {
@@ -1460,10 +1523,28 @@ QImage QSGRhiSupport::grabOffscreenForProtectedContent(QQuickWindow *window)
}
#endif
-void QSGRhiSupport::applySwapChainFormat(QRhiSwapChain *scWithWindowSet)
+void QSGRhiSupport::applySwapChainFormat(QRhiSwapChain *scWithWindowSet, QQuickWindow *window)
{
+ Q_ASSERT(scWithWindowSet->window() == window);
+
+ QRhiSwapChain::Format swapChainFormat = QRhiSwapChain::SDR;
+
+ QByteArray hdrRequest = qgetenv("QSG_RHI_HDR");
+ if (hdrRequest.isEmpty())
+ hdrRequest = window->property("_qt_sg_hdr_format").toByteArray();
+
+ if (!hdrRequest.isEmpty()) {
+ hdrRequest = hdrRequest.toLower();
+ if (hdrRequest == QByteArrayLiteral("scrgb") || hdrRequest == QByteArrayLiteral("extendedsrgblinear"))
+ swapChainFormat = QRhiSwapChain::HDRExtendedSrgbLinear;
+ else if (hdrRequest == QByteArrayLiteral("hdr10"))
+ swapChainFormat = QRhiSwapChain::HDR10;
+ else if (hdrRequest == QByteArrayLiteral("p3"))
+ swapChainFormat = QRhiSwapChain::HDRExtendedDisplayP3Linear;
+ }
+
const char *fmtStr = "unknown";
- switch (m_swapChainFormat) {
+ switch (swapChainFormat) {
case QRhiSwapChain::SDR:
fmtStr = "SDR";
break;
@@ -1473,12 +1554,15 @@ void QSGRhiSupport::applySwapChainFormat(QRhiSwapChain *scWithWindowSet)
case QRhiSwapChain::HDR10:
fmtStr = "HDR10";
break;
+ case QRhiSwapChain::HDRExtendedDisplayP3Linear:
+ fmtStr = "Extended Linear Display P3";
+ break;
default:
break;
}
- if (!scWithWindowSet->isFormatSupported(m_swapChainFormat)) {
- if (m_swapChainFormat != QRhiSwapChain::SDR) {
+ if (!scWithWindowSet->isFormatSupported(swapChainFormat)) {
+ if (swapChainFormat != QRhiSwapChain::SDR) {
qCDebug(QSG_LOG_INFO, "Requested a %s swapchain but it is reported to be unsupported with the current display(s). "
"In multi-screen configurations make sure the window is located on a HDR-enabled screen. "
"Request ignored, using SDR swapchain.", fmtStr);
@@ -1486,9 +1570,9 @@ void QSGRhiSupport::applySwapChainFormat(QRhiSwapChain *scWithWindowSet)
return;
}
- scWithWindowSet->setFormat(m_swapChainFormat);
+ scWithWindowSet->setFormat(swapChainFormat);
- if (m_swapChainFormat != QRhiSwapChain::SDR) {
+ if (swapChainFormat != QRhiSwapChain::SDR) {
qCDebug(QSG_LOG_INFO, "Creating %s swapchain", fmtStr);
qCDebug(QSG_LOG_INFO) << "HDR output info:" << scWithWindowSet->hdrInfo();
}
@@ -1504,19 +1588,36 @@ QRhiTexture::Format QSGRhiSupport::toRhiTextureFormat(uint nativeFormat, QRhiTex
#if QT_CONFIG(opengl)
case QRhi::OpenGLES2:
Q_UNUSED(flags);
- return toRhiTextureFormatFromGL(nativeFormat);
+ return toRhiTextureFormatFromGL(nativeFormat, flags);
#endif
#ifdef Q_OS_WIN
case QRhi::D3D11:
- return toRhiTextureFormatFromD3D11(nativeFormat, flags);
+ case QRhi::D3D12:
+ return toRhiTextureFormatFromDXGI(nativeFormat, flags);
#endif
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+#if QT_CONFIG(metal)
case QRhi::Metal:
return toRhiTextureFormatFromMetal(nativeFormat, flags);
#endif
default:
return QRhiTexture::UnknownFormat;
}
+ Q_UNUSED(nativeFormat)
+ Q_UNUSED(flags)
+}
+
+bool QSGRhiSupport::attemptReinitWithSwRastUponFail() const
+{
+ const QRhi::Implementation backend = rhiBackend();
+
+ // On Windows it makes sense to retry using a software adapter whenever
+ // device creation or swapchain creation fails, as WARP is usually available
+ // (built in to the OS) and is good quality. This helps a lot in particular
+ // when running in a VM that cripples proper 3D graphics.
+ if (backend == QRhi::D3D11 || backend == QRhi::D3D12)
+ return true;
+
+ return false;
}
QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgrhisupport_p.h b/src/quick/scenegraph/qsgrhisupport_p.h
index 42cbecabcb..535317d48c 100644
--- a/src/quick/scenegraph/qsgrhisupport_p.h
+++ b/src/quick/scenegraph/qsgrhisupport_p.h
@@ -18,25 +18,7 @@
#include "qsgrenderloop_p.h"
#include "qsgrendererinterface.h"
-#include <QtGui/private/qrhi_p.h>
-
-#include <QtGui/private/qrhinull_p.h>
-
-#if QT_CONFIG(opengl)
-#include <QtGui/private/qrhigles2_p.h>
-#endif
-
-#if QT_CONFIG(vulkan)
-#include <QtGui/private/qrhivulkan_p.h>
-#endif
-
-#ifdef Q_OS_WIN
-#include <QtGui/private/qrhid3d11_p.h>
-#endif
-
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
-#include <QtGui/private/qrhimetal_p.h>
-#endif
+#include <rhi/qrhi.h>
QT_BEGIN_NAMESPACE
@@ -52,7 +34,7 @@ class QQuickGraphicsConfiguration;
// In addition, the class provides handy conversion and query stuff for the
// renderloop and the QSGRendererInterface implementations.
//
-class Q_QUICK_PRIVATE_EXPORT QSGRhiSupport
+class Q_QUICK_EXPORT QSGRhiSupport
{
public:
static QSGRhiSupport *instance_internal();
@@ -63,7 +45,7 @@ public:
static void checkEnvQSgInfo();
#if QT_CONFIG(opengl)
- static QRhiTexture::Format toRhiTextureFormatFromGL(uint format);
+ static QRhiTexture::Format toRhiTextureFormatFromGL(uint format, QRhiTexture::Flags *flags);
#endif
#if QT_CONFIG(vulkan)
@@ -71,10 +53,10 @@ public:
#endif
#if defined(Q_OS_WIN)
- static QRhiTexture::Format toRhiTextureFormatFromD3D11(uint format, QRhiTexture::Flags *flags);
+ static QRhiTexture::Format toRhiTextureFormatFromDXGI(uint format, QRhiTexture::Flags *flags);
#endif
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+#if QT_CONFIG(metal)
static QRhiTexture::Format toRhiTextureFormatFromMetal(uint format, QRhiTexture::Flags *flags);
#endif
@@ -95,7 +77,7 @@ public:
QRhi *rhi;
bool own;
};
- RhiCreateResult createRhi(QQuickWindow *window, QSurface *offscreenSurface);
+ RhiCreateResult createRhi(QQuickWindow *window, QSurface *offscreenSurface, bool forcePreferSwRenderer = false);
void destroyRhi(QRhi *rhi, const QQuickGraphicsConfiguration &config);
void prepareWindowForRhi(QQuickWindow *window);
@@ -104,11 +86,12 @@ public:
QImage grabOffscreenForProtectedContent(QQuickWindow *window);
#endif
- QRhiSwapChain::Format swapChainFormat() const { return m_swapChainFormat; }
- void applySwapChainFormat(QRhiSwapChain *scWithWindowSet);
+ void applySwapChainFormat(QRhiSwapChain *scWithWindowSet, QQuickWindow *window);
QRhiTexture::Format toRhiTextureFormat(uint nativeFormat, QRhiTexture::Flags *flags) const;
+ bool attemptReinitWithSwRastUponFail() const;
+
private:
QSGRhiSupport();
void applySettings();
@@ -121,8 +104,6 @@ private:
} m_requested;
bool m_settingsApplied = false;
QRhi::Implementation m_rhiBackend = QRhi::Null;
- int m_killDeviceFrameCount;
- QRhiSwapChain::Format m_swapChainFormat = QRhiSwapChain::SDR;
};
QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgrhitextureglyphcache.cpp b/src/quick/scenegraph/qsgrhitextureglyphcache.cpp
index 2324db5762..7e626be8e2 100644
--- a/src/quick/scenegraph/qsgrhitextureglyphcache.cpp
+++ b/src/quick/scenegraph/qsgrhitextureglyphcache.cpp
@@ -23,15 +23,7 @@ QSGRhiTextureGlyphCache::QSGRhiTextureGlyphCache(QSGDefaultRenderContext *rc,
QSGRhiTextureGlyphCache::~QSGRhiTextureGlyphCache()
{
- // A plain delete should work, but just in case commitResourceUpdates was
- // not called and something is enqueued on the update batch for m_texture,
- // defer until the end of the frame.
- if (m_texture)
- m_texture->deleteLater();
-
- // should be empty, but just in case
- for (QRhiTexture *t : std::as_const(m_pendingDispose))
- t->deleteLater();
+ m_rc->deferredReleaseGlyphCacheTexture(m_texture);
}
QRhiTexture *QSGRhiTextureGlyphCache::createEmptyTexture(QRhiTexture::Format format)
@@ -97,7 +89,7 @@ void QSGRhiTextureGlyphCache::resizeTextureData(int width, int height)
resourceUpdates->uploadTexture(t, QRhiTextureUploadEntry(0, 0, subresDesc));
}
- m_pendingDispose.insert(m_texture);
+ m_rc->deferredReleaseGlyphCacheTexture(m_texture);
m_texture = t;
}
}
@@ -117,36 +109,42 @@ void QSGRhiTextureGlyphCache::prepareGlyphImage(QImage *img)
m_bgra = false;
if (img->format() == QImage::Format_Mono) {
- *img = img->convertToFormat(QImage::Format_Grayscale8);
- } else if (img->depth() == 32) {
- if (img->format() == QImage::Format_RGB32 || img->format() == QImage::Format_ARGB32_Premultiplied) {
- // We need to make the alpha component equal to the average of the RGB values.
- // This is needed when drawing sub-pixel antialiased text on translucent targets.
+ *img = std::move(*img).convertToFormat(QImage::Format_Grayscale8);
+ } else if (img->format() == QImage::Format_RGB32 || img->format() == QImage::Format_ARGB32_Premultiplied) {
+ // We need to make the alpha component equal to the average of the RGB values.
+ // This is needed when drawing sub-pixel antialiased text on translucent targets.
+ if (img->format() == QImage::Format_RGB32
+#if Q_BYTE_ORDER != Q_BIG_ENDIAN
+ || !supportsBgra
+#endif
+ ) {
for (int y = 0; y < maskHeight; ++y) {
- QRgb *src = (QRgb *) img->scanLine(y);
+ QRgb *src = reinterpret_cast<QRgb *>(img->scanLine(y));
for (int x = 0; x < maskWidth; ++x) {
- int r = qRed(src[x]);
- int g = qGreen(src[x]);
- int b = qBlue(src[x]);
- int avg;
- if (img->format() == QImage::Format_RGB32)
- avg = (r + g + b + 1) / 3; // "+1" for rounding.
- else // Format_ARGB_Premultiplied
- avg = qAlpha(src[x]);
-
- src[x] = qRgba(r, g, b, avg);
+ QRgb &rgb = src[x];
+
+ if (img->format() == QImage::Format_RGB32) {
+ int r = qRed(rgb);
+ int g = qGreen(rgb);
+ int b = qBlue(rgb);
+ int avg = (r + g + b + 1) / 3; // "+1" for rounding.
+ rgb = qRgba(r, g, b, avg);
+ }
+
#if Q_BYTE_ORDER != Q_BIG_ENDIAN
- if (supportsBgra) {
- m_bgra = true;
- } else {
+ if (!supportsBgra) {
// swizzle the bits to accommodate for the RGBA upload.
- src[x] = ARGB2RGBA(src[x]);
+ rgb = ARGB2RGBA(rgb);
m_bgra = false;
}
#endif
}
}
}
+#if Q_BYTE_ORDER != Q_BIG_ENDIAN
+ if (supportsBgra)
+ m_bgra = true;
+#endif
}
}
@@ -157,9 +155,9 @@ void QSGRhiTextureGlyphCache::fillTexture(const Coord &c, glyph_t glyph, const Q
if (!m_resizeWithTextureCopy) {
QImageTextureGlyphCache::fillTexture(c, glyph, subPixelPosition);
- mask = image();
- subresDesc.setSourceTopLeft(QPoint(c.x, c.y));
- subresDesc.setSourceSize(QSize(c.w, c.h));
+ // Explicitly copy() here to avoid fillTexture detaching the *entire* image() when
+ // it is still referenced by QRhiTextureSubresourceUploadDescription.
+ mask = image().copy(QRect(c.x, c.y, c.w, c.h));
} else {
mask = textureMapForGlyph(glyph, subPixelPosition);
}
@@ -220,14 +218,8 @@ void QSGRhiTextureGlyphCache::commitResourceUpdates(QRhiResourceUpdateBatch *mer
{
if (QRhiResourceUpdateBatch *resourceUpdates = m_rc->maybeGlyphCacheResourceUpdates()) {
mergeInto->merge(resourceUpdates);
- m_rc->releaseGlyphCacheResourceUpdates();
+ m_rc->resetGlyphCacheResources();
}
-
- // now let's assume the resource updates will be committed in this frame
- for (QRhiTexture *t : std::as_const(m_pendingDispose))
- t->deleteLater(); // will be deleted after the frame is submitted -> safe
-
- m_pendingDispose.clear();
}
bool QSGRhiTextureGlyphCache::eightBitFormatIsAlphaSwizzled() const
diff --git a/src/quick/scenegraph/qsgrhitextureglyphcache_p.h b/src/quick/scenegraph/qsgrhitextureglyphcache_p.h
index 2960c91b01..fbe020c3a2 100644
--- a/src/quick/scenegraph/qsgrhitextureglyphcache_p.h
+++ b/src/quick/scenegraph/qsgrhitextureglyphcache_p.h
@@ -16,7 +16,7 @@
//
#include <QtGui/private/qtextureglyphcache_p.h>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
QT_BEGIN_NAMESPACE
@@ -60,7 +60,6 @@ private:
QSize m_size;
bool m_bgra = false;
QVarLengthArray<QRhiTextureUploadEntry, 16> m_uploads;
- QSet<QRhiTexture *> m_pendingDispose;
};
QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgthreadedrenderloop.cpp b/src/quick/scenegraph/qsgthreadedrenderloop.cpp
index 2a1ceedb8f..8b4afea707 100644
--- a/src/quick/scenegraph/qsgthreadedrenderloop.cpp
+++ b/src/quick/scenegraph/qsgthreadedrenderloop.cpp
@@ -7,7 +7,7 @@
#include <QtCore/QWaitCondition>
#include <QtCore/QAnimationDriver>
#include <QtCore/QQueue>
-#include <QtCore/QTime>
+#include <QtCore/QTimer>
#include <QtGui/QGuiApplication>
#include <QtGui/QScreen>
@@ -18,6 +18,7 @@
#include <QtQuick/QQuickWindow>
#include <private/qquickwindow_p.h>
#include <private/qquickitem_p.h>
+#include <QtGui/qpa/qplatformwindow_p.h>
#include <QtQuick/private/qsgrenderer_p.h>
@@ -85,6 +86,15 @@
QT_BEGIN_NAMESPACE
+Q_TRACE_POINT(qtquick, QSG_polishAndSync_entry)
+Q_TRACE_POINT(qtquick, QSG_polishAndSync_exit)
+Q_TRACE_POINT(qtquick, QSG_wait_entry)
+Q_TRACE_POINT(qtquick, QSG_wait_exit)
+Q_TRACE_POINT(qtquick, QSG_syncAndRender_entry)
+Q_TRACE_POINT(qtquick, QSG_syncAndRender_exit)
+Q_TRACE_POINT(qtquick, QSG_animations_entry)
+Q_TRACE_POINT(qtquick, QSG_animations_exit)
+
#define QSG_RT_PAD " (RT) %s"
extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha);
@@ -92,40 +102,16 @@ extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_
// RL: Render Loop
// RT: Render Thread
-// Passed from the RL to the RT when a window is removed obscured and
-// should be removed from the render loop.
-const QEvent::Type WM_Obscure = QEvent::Type(QEvent::User + 1);
-
-// Passed from the RL to RT when GUI has been locked, waiting for sync
-// (updatePaintNode())
-const QEvent::Type WM_RequestSync = QEvent::Type(QEvent::User + 2);
-
-// Passed by the RL to the RT to free up maybe release SG and GL contexts
-// if no windows are rendering.
-const QEvent::Type WM_TryRelease = QEvent::Type(QEvent::User + 4);
-
-// Passed by the RL to the RT when a QQuickWindow::grabWindow() is
-// called.
-const QEvent::Type WM_Grab = QEvent::Type(QEvent::User + 5);
-
-// Passed by the window when there is a render job to run
-const QEvent::Type WM_PostJob = QEvent::Type(QEvent::User + 6);
-
-// When using the QRhi this is sent upon PlatformSurfaceAboutToBeDestroyed from
-// the event filter installed on the QQuickWindow.
-const QEvent::Type WM_ReleaseSwapchain = QEvent::Type(QEvent::User + 7);
-template <typename T> T *windowFor(const QList<T> &list, QQuickWindow *window)
+QSGThreadedRenderLoop::Window *QSGThreadedRenderLoop::windowFor(QQuickWindow *window)
{
- for (int i=0; i<list.size(); ++i) {
- const T &t = list.at(i);
+ for (const auto &t : std::as_const(m_windows)) {
if (t.window == window)
- return const_cast<T *>(&t);
+ return const_cast<Window *>(&t);
}
return nullptr;
}
-
class WMWindowEvent : public QEvent
{
public:
@@ -137,7 +123,7 @@ class WMTryReleaseEvent : public WMWindowEvent
{
public:
WMTryReleaseEvent(QQuickWindow *win, bool destroy, bool needsFallbackSurface)
- : WMWindowEvent(win, WM_TryRelease)
+ : WMWindowEvent(win, QEvent::Type(WM_TryRelease))
, inDestructor(destroy)
, needsFallback(needsFallbackSurface)
{}
@@ -150,7 +136,7 @@ class WMSyncEvent : public WMWindowEvent
{
public:
WMSyncEvent(QQuickWindow *c, bool inExpose, bool force, const QRhiSwapChainProxyData &scProxyData)
- : WMWindowEvent(c, WM_RequestSync)
+ : WMWindowEvent(c, QEvent::Type(WM_RequestSync))
, size(c->size())
, dpr(float(c->effectiveDevicePixelRatio()))
, syncInExpose(inExpose)
@@ -168,7 +154,8 @@ public:
class WMGrabEvent : public WMWindowEvent
{
public:
- WMGrabEvent(QQuickWindow *c, QImage *result) : WMWindowEvent(c, WM_Grab), image(result) {}
+ WMGrabEvent(QQuickWindow *c, QImage *result) :
+ WMWindowEvent(c, QEvent::Type(WM_Grab)), image(result) {}
QImage *image;
};
@@ -176,7 +163,7 @@ class WMJobEvent : public WMWindowEvent
{
public:
WMJobEvent(QQuickWindow *c, QRunnable *postedJob)
- : WMWindowEvent(c, WM_PostJob), job(postedJob) {}
+ : WMWindowEvent(c, QEvent::Type(WM_PostJob)), job(postedJob) {}
~WMJobEvent() { delete job; }
QRunnable *job;
};
@@ -184,7 +171,8 @@ public:
class WMReleaseSwapchainEvent : public WMWindowEvent
{
public:
- WMReleaseSwapchainEvent(QQuickWindow *c) : WMWindowEvent(c, WM_ReleaseSwapchain) { }
+ WMReleaseSwapchainEvent(QQuickWindow *c) :
+ WMWindowEvent(c, QEvent::Type(WM_ReleaseSwapchain)) { }
};
class QSGRenderThreadEventQueue : public QQueue<QEvent *>
@@ -293,6 +281,7 @@ public:
};
void ensureRhi();
+ void teardownGraphics();
void handleDeviceLoss();
QSGThreadedRenderLoop *wm;
@@ -322,6 +311,7 @@ public:
bool rhiDeviceLost = false;
bool rhiDoomed = false;
bool guiNotifiedAboutRhiFailure = false;
+ bool swRastFallbackDueToSwapchainFailure = false;
// Local event queue stuff...
bool stopEventProcessing;
@@ -588,22 +578,27 @@ void QSGRenderThread::sync(bool inExpose)
}
}
-void QSGRenderThread::handleDeviceLoss()
+void QSGRenderThread::teardownGraphics()
{
- if (!rhi || !rhi->isDeviceLost())
- return;
-
- qWarning("Graphics device lost, cleaning up scenegraph and releasing RHI");
QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
wd->cleanupNodesOnShutdown();
sgrc->invalidate();
wm->releaseSwapchain(window);
- rhiDeviceLost = true;
if (ownRhi)
QSGRhiSupport::instance()->destroyRhi(rhi, {});
rhi = nullptr;
}
+void QSGRenderThread::handleDeviceLoss()
+{
+ if (!rhi || !rhi->isDeviceLost())
+ return;
+
+ qWarning("Graphics device lost, cleaning up scenegraph and releasing RHI");
+ teardownGraphics();
+ rhiDeviceLost = true;
+}
+
void QSGRenderThread::syncAndRender()
{
const bool profileFrames = QSG_LOG_TIME_RENDERLOOP().isDebugEnabled();
@@ -633,6 +628,7 @@ void QSGRenderThread::syncAndRender()
pendingUpdate = 0;
QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
+ QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
// Begin the frame before syncing -> sync is where we may invoke
// updatePaintNode() on the items and they may want to do resource updates.
// Also relevant for applications that connect to the before/afterSynchronizing
@@ -654,10 +650,29 @@ void QSGRenderThread::syncAndRender()
qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "just became exposed");
cd->hasActiveSwapchain = cd->swapchain->createOrResize();
- if (!cd->hasActiveSwapchain && rhi->isDeviceLost()) {
- handleDeviceLoss();
- QCoreApplication::postEvent(window, new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
- return;
+ if (!cd->hasActiveSwapchain) {
+ bool bailOut = false;
+ if (rhi->isDeviceLost()) {
+ handleDeviceLoss();
+ bailOut = true;
+ } else if (previousOutputSize.isEmpty() && !swRastFallbackDueToSwapchainFailure && rhiSupport->attemptReinitWithSwRastUponFail()) {
+ qWarning("Failed to create swapchain."
+ " Retrying by requesting a software rasterizer, if applicable for the 3D API implementation.");
+ swRastFallbackDueToSwapchainFailure = true;
+ teardownGraphics();
+ bailOut = true;
+ }
+ if (bailOut) {
+ QCoreApplication::postEvent(window, new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
+ if (syncRequested) {
+ // Lock like sync() would do. Note that exposeRequested always includes syncRequested.
+ qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- bailing out due to failed swapchain init, wake Gui");
+ mutex.lock();
+ waitCondition.wakeOne();
+ mutex.unlock();
+ }
+ return;
+ }
}
cd->swapchainJustBecameRenderable = false;
@@ -739,7 +754,7 @@ void QSGRenderThread::syncAndRender()
// Zero size windows do not initialize a swapchain and
// rendercontext. So no sync or render can be done then.
const bool canRender = d->renderer && hasValidSwapChain;
-
+ double lastCompletedGpuTime = 0;
if (canRender) {
if (!syncRequested) // else this was already done in sync()
rhi->makeThreadLocalNativeContextCurrent();
@@ -761,6 +776,8 @@ void QSGRenderThread::syncAndRender()
qWarning("Failed to end frame");
if (frameResult == QRhi::FrameOpDeviceLost || frameResult == QRhi::FrameOpSwapChainOutOfDate)
QCoreApplication::postEvent(window, new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
+ } else {
+ lastCompletedGpuTime = cd->swapchain->currentFrameCommandBuffer()->lastCompletedGpuTime();
}
d->fireFrameSwapped();
} else {
@@ -811,6 +828,12 @@ void QSGRenderThread::syncAndRender()
int((syncTime/1000000)),
int((renderTime - syncTime) / 1000000),
int((threadTimer.nsecsElapsed() - renderTime) / 1000000));
+ if (!qFuzzyIsNull(lastCompletedGpuTime) && cd->graphicsConfig.timestampsEnabled()) {
+ qCDebug(QSG_LOG_TIME_RENDERLOOP, "[window %p][render thread %p] syncAndRender: last retrieved GPU frame time was %.4f ms",
+ window,
+ QThread::currentThread(),
+ lastCompletedGpuTime * 1000.0);
+ }
}
Q_TRACE(QSG_swap_exit);
@@ -856,7 +879,8 @@ void QSGRenderThread::ensureRhi()
if (rhiDoomed) // no repeated attempts if the initial attempt failed
return;
QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
- QSGRhiSupport::RhiCreateResult rhiResult = rhiSupport->createRhi(window, offscreenSurface);
+ const bool forcePreferSwRenderer = swRastFallbackDueToSwapchainFailure;
+ QSGRhiSupport::RhiCreateResult rhiResult = rhiSupport->createRhi(window, offscreenSurface, forcePreferSwRenderer);
rhi = rhiResult.rhi;
ownRhi = rhiResult.own;
if (rhi) {
@@ -915,7 +939,7 @@ void QSGRenderThread::ensureRhi()
}
cd->swapchain->setWindow(window);
cd->swapchain->setProxyData(scProxyData);
- QSGRhiSupport::instance()->applySwapChainFormat(cd->swapchain);
+ QSGRhiSupport::instance()->applySwapChainFormat(cd->swapchain, window);
qCDebug(QSG_LOG_INFO, "MSAA sample count for the swapchain is %d. Alpha channel requested = %s.",
rhiSampleCount, alpha ? "yes" : "no");
cd->swapchain->setSampleCount(rhiSampleCount);
@@ -1054,6 +1078,9 @@ void QSGThreadedRenderLoop::animationStopped()
void QSGThreadedRenderLoop::startOrStopAnimationTimer()
{
+ if (!sg->isVSyncDependent(m_animation_driver))
+ return;
+
int exposedWindows = 0;
int unthrottledWindows = 0;
int badVSync = 0;
@@ -1123,7 +1150,7 @@ void QSGThreadedRenderLoop::hide(QQuickWindow *window)
qCDebug(QSG_LOG_RENDERLOOP) << "hide()" << window;
if (window->isExposed())
- handleObscurity(windowFor(m_windows, window));
+ handleObscurity(windowFor(window));
releaseResources(window);
}
@@ -1132,7 +1159,7 @@ void QSGThreadedRenderLoop::resize(QQuickWindow *window)
{
qCDebug(QSG_LOG_RENDERLOOP) << "reisze()" << window;
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (!w)
return;
@@ -1149,7 +1176,7 @@ void QSGThreadedRenderLoop::windowDestroyed(QQuickWindow *window)
{
qCDebug(QSG_LOG_RENDERLOOP) << "begin windowDestroyed()" << window;
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (!w)
return;
@@ -1220,7 +1247,7 @@ void QSGThreadedRenderLoop::exposureChanged(QQuickWindow *window)
if (!skipThisExpose)
handleExposure(window);
} else {
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (w)
handleObscurity(w);
}
@@ -1234,7 +1261,7 @@ void QSGThreadedRenderLoop::handleExposure(QQuickWindow *window)
{
qCDebug(QSG_LOG_RENDERLOOP) << "handleExposure()" << window;
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (!w) {
qCDebug(QSG_LOG_RENDERLOOP, "- adding window to list");
Window win;
@@ -1322,6 +1349,9 @@ void QSGThreadedRenderLoop::handleExposure(QQuickWindow *window)
*/
void QSGThreadedRenderLoop::handleObscurity(Window *w)
{
+ if (!w)
+ return;
+
qCDebug(QSG_LOG_RENDERLOOP) << "handleObscurity()" << w->window;
if (w->thread->isRunning()) {
if (!QQuickWindowPrivate::get(w->window)->updatesEnabled) {
@@ -1329,7 +1359,7 @@ void QSGThreadedRenderLoop::handleObscurity(Window *w)
return;
}
w->thread->mutex.lock();
- w->thread->postEvent(new WMWindowEvent(w->window, WM_Obscure));
+ w->thread->postEvent(new WMWindowEvent(w->window, QEvent::Type(WM_Obscure)));
w->thread->waitCondition.wait(&w->thread->mutex);
w->thread->mutex.unlock();
}
@@ -1344,7 +1374,7 @@ bool QSGThreadedRenderLoop::eventFilter(QObject *watched, QEvent *event)
if (static_cast<QPlatformSurfaceEvent *>(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) {
QQuickWindow *window = qobject_cast<QQuickWindow *>(watched);
if (window) {
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (w && w->thread->isRunning()) {
w->thread->mutex.lock();
w->thread->postEvent(new WMReleaseSwapchainEvent(window));
@@ -1369,14 +1399,14 @@ void QSGThreadedRenderLoop::handleUpdateRequest(QQuickWindow *window)
qCDebug(QSG_LOG_RENDERLOOP, "- updatesEnabled is false, abort");
return;
}
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (w)
polishAndSync(w);
}
void QSGThreadedRenderLoop::maybeUpdate(QQuickWindow *window)
{
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (w)
maybeUpdate(w);
}
@@ -1428,14 +1458,33 @@ void QSGThreadedRenderLoop::maybeUpdate(Window *w)
*/
void QSGThreadedRenderLoop::update(QQuickWindow *window)
{
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (!w)
return;
- if (w->thread == QThread::currentThread()) {
- qCDebug(QSG_LOG_RENDERLOOP) << "update on window - on render thread" << w->window;
- w->thread->requestRepaint();
- return;
+ const bool isRenderThread = QThread::currentThread() == w->thread;
+
+#if defined(Q_OS_MACOS)
+ using namespace QNativeInterface::Private;
+ if (auto *cocoaWindow = dynamic_cast<QCocoaWindow*>(window->handle())) {
+ // If the window is being resized we don't want to schedule unthrottled
+ // updates on the render thread, as this will starve the main thread
+ // from getting drawables for displaying the updated window size.
+ if (isRenderThread && cocoaWindow->inLiveResize()) {
+ // In most cases the window will already have update requested
+ // due to the animator triggering a sync, but just in case we
+ // schedule an update request on the main thread explicitly.
+ qCDebug(QSG_LOG_RENDERLOOP) << "window is resizing. update on window" << w->window;
+ QTimer::singleShot(0, window, [=]{ window->requestUpdate(); });
+ return;
+ }
+ }
+#endif
+
+ if (isRenderThread) {
+ qCDebug(QSG_LOG_RENDERLOOP) << "update on window - on render thread" << w->window;
+ w->thread->requestRepaint();
+ return;
}
qCDebug(QSG_LOG_RENDERLOOP) << "update on window" << w->window;
@@ -1448,7 +1497,7 @@ void QSGThreadedRenderLoop::update(QQuickWindow *window)
void QSGThreadedRenderLoop::releaseResources(QQuickWindow *window)
{
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (w)
releaseResources(w, false);
}
@@ -1506,7 +1555,7 @@ void QSGThreadedRenderLoop::polishAndSync(Window *w, bool inExpose)
// Flush pending touch events.
QQuickWindowPrivate::get(window)->deliveryAgentPrivate()->flushFrameSynchronousEvents(window);
// The delivery of the event might have caused the window to stop rendering
- w = windowFor(m_windows, window);
+ w = windowFor(window);
if (!w || !w->thread || !w->thread->window) {
qCDebug(QSG_LOG_RENDERLOOP, "- removed after event flushing, abort");
return;
@@ -1520,7 +1569,7 @@ void QSGThreadedRenderLoop::polishAndSync(Window *w, bool inExpose)
const qint64 elapsedSinceLastMs = w->timeBetweenPolishAndSyncs.restart();
- if (w->actualWindowFormat.swapInterval() != 0) {
+ if (w->actualWindowFormat.swapInterval() != 0 && sg->isVSyncDependent(m_animation_driver)) {
w->psTimeAccumulator += elapsedSinceLastMs;
w->psTimeSampleCount += 1;
// cannot be too high because we'd then delay recognition of broken vsync at start
@@ -1627,10 +1676,13 @@ void QSGThreadedRenderLoop::polishAndSync(Window *w, bool inExpose)
QQuickProfiler::SceneGraphPolishAndSyncSync);
Q_TRACE(QSG_animations_entry);
- // Now is the time to advance the regular animations (as we are throttled to
- // vsync due to the wait above), but this is only relevant when there is one
- // single window. With multiple windows m_animation_timer is active, and
- // advance() happens instead in response to a good old timer event, not here.
+ // Now is the time to advance the regular animations (as we are throttled
+ // to vsync due to the wait above), but this is only relevant when there is
+ // one single window. With multiple windows m_animation_timer is active,
+ // and advance() happens instead in response to a good old timer event, not
+ // here. (the above applies only when the QSGAnimationDriver reports
+ // isVSyncDependent() == true, if not then we always use the driver and
+ // just advance here)
if (m_animation_timer == 0 && m_animation_driver->isRunning()) {
qCDebug(QSG_LOG_RENDERLOOP, "- advancing animations");
m_animation_driver->advance();
@@ -1674,6 +1726,7 @@ bool QSGThreadedRenderLoop::event(QEvent *e)
switch ((int) e->type()) {
case QEvent::Timer: {
+ Q_ASSERT(sg->isVSyncDependent(m_animation_driver));
QTimerEvent *te = static_cast<QTimerEvent *>(e);
if (te->timerId() == m_animation_timer) {
qCDebug(QSG_LOG_RENDERLOOP, "- ticking non-render thread timer");
@@ -1681,6 +1734,7 @@ bool QSGThreadedRenderLoop::event(QEvent *e)
emit timeToIncubate();
return true;
}
+ break;
}
default:
@@ -1706,7 +1760,7 @@ QImage QSGThreadedRenderLoop::grab(QQuickWindow *window)
{
qCDebug(QSG_LOG_RENDERLOOP) << "grab()" << window;
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
Q_ASSERT(w);
if (!w->thread->isRunning())
@@ -1741,7 +1795,7 @@ QImage QSGThreadedRenderLoop::grab(QQuickWindow *window)
*/
void QSGThreadedRenderLoop::postJob(QQuickWindow *window, QRunnable *job)
{
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (w && w->thread && w->thread->window)
w->thread->postEvent(new WMJobEvent(window, job));
else
diff --git a/src/quick/scenegraph/qsgthreadedrenderloop_p.h b/src/quick/scenegraph/qsgthreadedrenderloop_p.h
index 4e0c471ab4..5d209f2f0f 100644
--- a/src/quick/scenegraph/qsgthreadedrenderloop_p.h
+++ b/src/quick/scenegraph/qsgthreadedrenderloop_p.h
@@ -76,6 +76,8 @@ private:
friend class QSGRenderThread;
+
+ Window *windowFor(QQuickWindow *window);
void releaseResources(Window *window, bool inDestructor);
bool checkAndResetForceUpdate(QQuickWindow *window);
diff --git a/src/quick/scenegraph/shaders_ng/24bittextmask.frag b/src/quick/scenegraph/shaders_ng/24bittextmask.frag
index ed8da4cd30..c6e39c80db 100644
--- a/src/quick/scenegraph/shaders_ng/24bittextmask.frag
+++ b/src/quick/scenegraph/shaders_ng/24bittextmask.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -7,14 +10,18 @@ layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
mat4 modelViewMatrix;
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 projectionMatrix[QSHADER_VIEW_COUNT];
+#else
mat4 projectionMatrix;
- vec4 color;
+#endif
vec2 textureScale;
float dpr;
-} ubuf;
+ vec4 color;
+};
void main()
{
vec4 glyph = texture(_qt_texture, sampleCoord);
- fragColor = vec4(glyph.rgb * ubuf.color.a, glyph.a);
+ fragColor = glyph * color.a;
}
diff --git a/src/quick/scenegraph/shaders_ng/32bitcolortext.frag b/src/quick/scenegraph/shaders_ng/32bitcolortext.frag
index 4198a4d339..9d67c8b302 100644
--- a/src/quick/scenegraph/shaders_ng/32bitcolortext.frag
+++ b/src/quick/scenegraph/shaders_ng/32bitcolortext.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -7,13 +10,17 @@ layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
mat4 modelViewMatrix;
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 projectionMatrix[QSHADER_VIEW_COUNT];
+#else
mat4 projectionMatrix;
- vec4 color;
+#endif
vec2 textureScale;
float dpr;
-} ubuf;
+ vec4 color;
+};
void main()
{
- fragColor = texture(_qt_texture, sampleCoord) * ubuf.color.a;
+ fragColor = texture(_qt_texture, sampleCoord) * color.a;
}
diff --git a/src/quick/scenegraph/shaders_ng/8bittextmask.frag b/src/quick/scenegraph/shaders_ng/8bittextmask.frag
index a06743876d..530ebd69ef 100644
--- a/src/quick/scenegraph/shaders_ng/8bittextmask.frag
+++ b/src/quick/scenegraph/shaders_ng/8bittextmask.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -7,13 +10,17 @@ layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
mat4 modelViewMatrix;
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 projectionMatrix[QSHADER_VIEW_COUNT];
+#else
mat4 projectionMatrix;
- vec4 color;
+#endif
vec2 textureScale;
float dpr;
-} ubuf;
+ vec4 color;
+};
void main()
{
- fragColor = ubuf.color * texture(_qt_texture, sampleCoord).r;
+ fragColor = color * texture(_qt_texture, sampleCoord).r;
}
diff --git a/src/quick/scenegraph/shaders_ng/8bittextmask_a.frag b/src/quick/scenegraph/shaders_ng/8bittextmask_a.frag
index f725cbc5e7..d6ace867a3 100644
--- a/src/quick/scenegraph/shaders_ng/8bittextmask_a.frag
+++ b/src/quick/scenegraph/shaders_ng/8bittextmask_a.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -7,13 +10,17 @@ layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
mat4 modelViewMatrix;
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 projectionMatrix[QSHADER_VIEW_COUNT];
+#else
mat4 projectionMatrix;
- vec4 color;
+#endif
vec2 textureScale;
float dpr;
-} ubuf;
+ vec4 color;
+};
void main()
{
- fragColor = ubuf.color * texture(_qt_texture, sampleCoord).a; // take .a instead of .r
+ fragColor = color * texture(_qt_texture, sampleCoord).a; // take .a instead of .r
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext.frag b/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext.frag
index b1551d8ef4..1ece7b1e51 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext.frag
+++ b/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -6,7 +9,11 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -15,12 +22,12 @@ layout(std140, binding = 0) uniform buf {
vec4 styleColor;
float outlineAlphaMax0;
float outlineAlphaMax1;
-} ubuf;
+};
void main()
{
float d = texture(_qt_texture, sampleCoord).r;
- float a = smoothstep(ubuf.alphaMin, ubuf.alphaMax, d);
- fragColor = step(1.0 - a, 1.0) * mix(ubuf.styleColor, ubuf.color, a)
- * smoothstep(ubuf.outlineAlphaMax0, ubuf.outlineAlphaMax1, d);
+ float a = smoothstep(alphaMin, alphaMax, d);
+ fragColor = step(1.0 - a, 1.0) * mix(styleColor, color, a)
+ * smoothstep(outlineAlphaMax0, outlineAlphaMax1, d);
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext.vert b/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext.vert
index 8f0d618503..107ec9dfdc 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext.vert
+++ b/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext.vert
@@ -6,7 +6,11 @@ layout(location = 1) in vec2 tCoord;
layout(location = 0) out vec2 sampleCoord;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -15,12 +19,14 @@ layout(std140, binding = 0) uniform buf {
vec4 styleColor;
float outlineAlphaMax0;
float outlineAlphaMax1;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+};
void main()
{
- sampleCoord = tCoord * ubuf.textureScale;
- gl_Position = ubuf.matrix * vCoord;
+ sampleCoord = tCoord * textureScale;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = matrix[gl_ViewIndex] * vCoord;
+#else
+ gl_Position = matrix * vCoord;
+#endif
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_a.frag b/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_a.frag
index 7c6bd9a493..ac289221f9 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_a.frag
+++ b/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_a.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -6,7 +9,11 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -15,12 +22,12 @@ layout(std140, binding = 0) uniform buf {
vec4 styleColor;
float outlineAlphaMax0;
float outlineAlphaMax1;
-} ubuf;
+};
void main()
{
float d = texture(_qt_texture, sampleCoord).a;
- float a = smoothstep(ubuf.alphaMin, ubuf.alphaMax, d);
- fragColor = step(1.0 - a, 1.0) * mix(ubuf.styleColor, ubuf.color, a)
- * smoothstep(ubuf.outlineAlphaMax0, ubuf.outlineAlphaMax1, d);
+ float a = smoothstep(alphaMin, alphaMax, d);
+ fragColor = step(1.0 - a, 1.0) * mix(styleColor, color, a)
+ * smoothstep(outlineAlphaMax0, outlineAlphaMax1, d);
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_a_fwidth.frag b/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_a_fwidth.frag
index 30ec465791..ddf8f28d1d 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_a_fwidth.frag
+++ b/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_a_fwidth.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -6,7 +9,11 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -15,7 +22,7 @@ layout(std140, binding = 0) uniform buf {
vec4 styleColor;
float outlineAlphaMax0;
float outlineAlphaMax1;
-} ubuf;
+};
void main()
{
@@ -24,8 +31,8 @@ void main()
// The outlineLimit is based on font size, but scales with the transform, so
// we can calculate it from the outline span.
- float outlineLimit = (ubuf.outlineAlphaMax1 - ubuf.outlineAlphaMax0) / 2.0 + ubuf.outlineAlphaMax0;
+ float outlineLimit = (outlineAlphaMax1 - outlineAlphaMax0) / 2.0 + outlineAlphaMax0;
float a = smoothstep(max(0.0, 0.5 - f), min(1.0, 0.5 + f), distance);
- fragColor = step(1.0 - a, 1.0) * mix(ubuf.styleColor, ubuf.color, a) * smoothstep(max(0.0, outlineLimit - f), min(outlineLimit + f, 0.5 - f), distance);
+ fragColor = step(1.0 - a, 1.0) * mix(styleColor, color, a) * smoothstep(max(0.0, outlineLimit - f), min(outlineLimit + f, 0.5 - f), distance);
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_fwidth.frag b/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_fwidth.frag
index 511bffb09a..8aa5326be0 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_fwidth.frag
+++ b/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_fwidth.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -6,7 +9,11 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -15,7 +22,7 @@ layout(std140, binding = 0) uniform buf {
vec4 styleColor;
float outlineAlphaMax0;
float outlineAlphaMax1;
-} ubuf;
+};
void main()
{
@@ -24,8 +31,8 @@ void main()
// The outlineLimit is based on font size, but scales with the transform, so
// we can calculate it from the outline span.
- float outlineLimit = (ubuf.outlineAlphaMax1 - ubuf.outlineAlphaMax0) / 2.0 + ubuf.outlineAlphaMax0;
+ float outlineLimit = (outlineAlphaMax1 - outlineAlphaMax0) / 2.0 + outlineAlphaMax0;
float a = smoothstep(max(0.0, 0.5 - f), min(1.0, 0.5 + f), distance);
- fragColor = step(1.0 - a, 1.0) * mix(ubuf.styleColor, ubuf.color, a) * smoothstep(max(0.0, outlineLimit - f), min(outlineLimit + f, 0.5 - f), distance);
+ fragColor = step(1.0 - a, 1.0) * mix(styleColor, color, a) * smoothstep(max(0.0, outlineLimit - f), min(outlineLimit + f, 0.5 - f), distance);
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext.frag b/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext.frag
index aa3390094b..6374f2d3ac 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext.frag
+++ b/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -8,7 +11,11 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -16,12 +23,12 @@ layout(std140, binding = 0) uniform buf {
// up to this point it must match distancefieldtext
vec4 styleColor;
vec2 shift;
-} ubuf;
+};
void main()
{
- float a = smoothstep(ubuf.alphaMin, ubuf.alphaMax, texture(_qt_texture, sampleCoord).r);
- vec4 shifted = ubuf.styleColor * smoothstep(ubuf.alphaMin, ubuf.alphaMax,
+ float a = smoothstep(alphaMin, alphaMax, texture(_qt_texture, sampleCoord).r);
+ vec4 shifted = styleColor * smoothstep(alphaMin, alphaMax,
texture(_qt_texture, shiftedSampleCoord).r);
- fragColor = mix(shifted, ubuf.color, a);
+ fragColor = mix(shifted, color, a);
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext.vert b/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext.vert
index f3a7671435..5eeb6d8ecb 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext.vert
+++ b/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext.vert
@@ -7,7 +7,11 @@ layout(location = 0) out vec2 sampleCoord;
layout(location = 1) out vec2 shiftedSampleCoord;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -15,13 +19,15 @@ layout(std140, binding = 0) uniform buf {
// up to this point it must match distancefieldtext
vec4 styleColor;
vec2 shift;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+};
void main()
{
- sampleCoord = tCoord * ubuf.textureScale;
- shiftedSampleCoord = (tCoord - ubuf.shift) * ubuf.textureScale;
- gl_Position = ubuf.matrix * vCoord;
+ sampleCoord = tCoord * textureScale;
+ shiftedSampleCoord = (tCoord - shift) * textureScale;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = matrix[gl_ViewIndex] * vCoord;
+#else
+ gl_Position = matrix * vCoord;
+#endif
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_a.frag b/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_a.frag
index ab3a5f63ff..cd505e35fb 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_a.frag
+++ b/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_a.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -8,7 +11,11 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -16,12 +23,12 @@ layout(std140, binding = 0) uniform buf {
// up to this point it must match distancefieldtext
vec4 styleColor;
vec2 shift;
-} ubuf;
+};
void main()
{
- float a = smoothstep(ubuf.alphaMin, ubuf.alphaMax, texture(_qt_texture, sampleCoord).a);
- vec4 shifted = ubuf.styleColor * smoothstep(ubuf.alphaMin, ubuf.alphaMax,
+ float a = smoothstep(alphaMin, alphaMax, texture(_qt_texture, sampleCoord).a);
+ vec4 shifted = styleColor * smoothstep(alphaMin, alphaMax,
texture(_qt_texture, shiftedSampleCoord).a);
- fragColor = mix(shifted, ubuf.color, a);
+ fragColor = mix(shifted, color, a);
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_a_fwidth.frag b/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_a_fwidth.frag
index 8f528fea1e..e7112929a7 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_a_fwidth.frag
+++ b/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_a_fwidth.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -8,7 +11,11 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -16,7 +23,7 @@ layout(std140, binding = 0) uniform buf {
// up to this point it must match distancefieldtext
vec4 styleColor;
vec2 shift;
-} ubuf;
+};
void main()
{
@@ -28,6 +35,6 @@ void main()
float shiftedF = fwidth(shiftedDistance);
float shiftedA = smoothstep(0.5 - shiftedF, 0.5 + shiftedF, shiftedDistance);
- vec4 shifted = ubuf.styleColor * shiftedA;
- fragColor = mix(shifted, ubuf.color, a);
+ vec4 shifted = styleColor * shiftedA;
+ fragColor = mix(shifted, color, a);
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_fwidth.frag b/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_fwidth.frag
index a71cc1d9b0..373e97ffff 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_fwidth.frag
+++ b/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_fwidth.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -8,7 +11,11 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -16,7 +23,7 @@ layout(std140, binding = 0) uniform buf {
// up to this point it must match distancefieldtext
vec4 styleColor;
vec2 shift;
-} ubuf;
+};
void main()
{
@@ -28,6 +35,6 @@ void main()
float shiftedF = fwidth(shiftedDistance);
float shiftedA = smoothstep(0.5 - shiftedF, 0.5 + shiftedF, shiftedDistance);
- vec4 shifted = ubuf.styleColor * shiftedA;
- fragColor = mix(shifted, ubuf.color, a);
+ vec4 shifted = styleColor * shiftedA;
+ fragColor = mix(shifted, color, a);
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldtext.frag b/src/quick/scenegraph/shaders_ng/distancefieldtext.frag
index d594207567..a94a1257b9 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldtext.frag
+++ b/src/quick/scenegraph/shaders_ng/distancefieldtext.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -6,15 +9,19 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
float alphaMax;
-} ubuf;
+};
void main()
{
- fragColor = ubuf.color * smoothstep(ubuf.alphaMin, ubuf.alphaMax,
- texture(_qt_texture, sampleCoord).r);
+ fragColor = color * smoothstep(alphaMin, alphaMax,
+ texture(_qt_texture, sampleCoord).r);
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldtext.vert b/src/quick/scenegraph/shaders_ng/distancefieldtext.vert
index d56ddddd24..eeb9f7bcce 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldtext.vert
+++ b/src/quick/scenegraph/shaders_ng/distancefieldtext.vert
@@ -6,17 +6,23 @@ layout(location = 1) in vec2 tCoord;
layout(location = 0) out vec2 sampleCoord;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
float alphaMax;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+};
void main()
{
- sampleCoord = tCoord * ubuf.textureScale;
- gl_Position = ubuf.matrix * vCoord;
+ sampleCoord = tCoord * textureScale;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = matrix[gl_ViewIndex] * vCoord;
+#else
+ gl_Position = matrix * vCoord;
+#endif
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldtext_a.frag b/src/quick/scenegraph/shaders_ng/distancefieldtext_a.frag
index bb807d86d8..dccca64a34 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldtext_a.frag
+++ b/src/quick/scenegraph/shaders_ng/distancefieldtext_a.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -6,15 +9,19 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
float alphaMax;
-} ubuf;
+};
void main()
{
- fragColor = ubuf.color * smoothstep(ubuf.alphaMin, ubuf.alphaMax,
- texture(_qt_texture, sampleCoord).a);
+ fragColor = color * smoothstep(alphaMin, alphaMax,
+ texture(_qt_texture, sampleCoord).a);
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldtext_a_fwidth.frag b/src/quick/scenegraph/shaders_ng/distancefieldtext_a_fwidth.frag
index 1aa1175b57..e4d2bced7a 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldtext_a_fwidth.frag
+++ b/src/quick/scenegraph/shaders_ng/distancefieldtext_a_fwidth.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -6,16 +9,20 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
float alphaMax;
-} ubuf;
+};
void main()
{
float distance = texture(_qt_texture, sampleCoord).a;
float f = fwidth(distance);
- fragColor = ubuf.color * smoothstep(max(0.0, 0.5 - f), min(1.0, 0.5 + f), distance);
+ fragColor = color * smoothstep(max(0.0, 0.5 - f), min(1.0, 0.5 + f), distance);
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldtext_fwidth.frag b/src/quick/scenegraph/shaders_ng/distancefieldtext_fwidth.frag
index a698c19550..e895071811 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldtext_fwidth.frag
+++ b/src/quick/scenegraph/shaders_ng/distancefieldtext_fwidth.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -6,16 +9,20 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
float alphaMax;
-} ubuf;
+};
void main()
{
float distance = texture(_qt_texture, sampleCoord).r;
float f = fwidth(distance);
- fragColor = ubuf.color * smoothstep(max(0.0, 0.5 - f), min(1.0, 0.5 + f), distance);
+ fragColor = color * smoothstep(max(0.0, 0.5 - f), min(1.0, 0.5 + f), distance);
}
diff --git a/src/quick/scenegraph/shaders_ng/flatcolor.frag b/src/quick/scenegraph/shaders_ng/flatcolor.frag
index 3a677b7c93..c5f9f3f2c4 100644
--- a/src/quick/scenegraph/shaders_ng/flatcolor.frag
+++ b/src/quick/scenegraph/shaders_ng/flatcolor.frag
@@ -1,13 +1,20 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec4 color;
-} ubuf;
+};
void main()
{
- fragColor = ubuf.color;
+ fragColor = color;
}
diff --git a/src/quick/scenegraph/shaders_ng/flatcolor.vert b/src/quick/scenegraph/shaders_ng/flatcolor.vert
index b5dfd32197..eec2a2d6ed 100644
--- a/src/quick/scenegraph/shaders_ng/flatcolor.vert
+++ b/src/quick/scenegraph/shaders_ng/flatcolor.vert
@@ -3,13 +3,19 @@
layout(location = 0) in vec4 vertexCoord;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec4 color;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+};
void main()
{
- gl_Position = ubuf.matrix * vertexCoord;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = matrix[gl_ViewIndex] * vertexCoord;
+#else
+ gl_Position = matrix * vertexCoord;
+#endif
}
diff --git a/src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.frag b/src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.frag
index 723227a04d..fc3d04cb60 100644
--- a/src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.frag
+++ b/src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -11,7 +14,11 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -19,7 +26,7 @@ layout(std140, binding = 0) uniform buf {
// up to this point it must match distancefieldtext
float fontScale;
vec4 vecDelta;
-} ubuf;
+};
void main()
{
@@ -31,10 +38,10 @@ void main()
n.w = textureProj(_qt_texture, sampleFarRight).r;
vec2 d = min(abs(n.yw - n.xz) * 2., 0.67);
- vec2 lo = mix(vec2(ubuf.alphaMin), vec2(0.5), d);
- vec2 hi = mix(vec2(ubuf.alphaMax), vec2(0.5), d);
+ vec2 lo = mix(vec2(alphaMin), vec2(0.5), d);
+ vec2 hi = mix(vec2(alphaMax), vec2(0.5), d);
n = smoothstep(lo.xxyy, hi.xxyy, n);
c = smoothstep(lo.x + lo.y, hi.x + hi.y, 2. * c);
- fragColor = vec4(0.333 * (n.xyz + n.yzw + c), c) * ubuf.color.w;
+ fragColor = vec4(0.333 * (n.xyz + n.yzw + c), c) * color.w;
}
diff --git a/src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.vert b/src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.vert
index 9c7281c31c..922074791b 100644
--- a/src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.vert
+++ b/src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.vert
@@ -10,7 +10,11 @@ layout(location = 3) out vec3 sampleNearRight;
layout(location = 4) out vec3 sampleFarRight;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -18,24 +22,26 @@ layout(std140, binding = 0) uniform buf {
// up to this point it must match distancefieldtext
float fontScale;
vec4 vecDelta;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+};
void main()
{
- sampleCoord = tCoord * ubuf.textureScale;
- gl_Position = ubuf.matrix * vCoord;
+ sampleCoord = tCoord * textureScale;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = matrix[gl_ViewIndex] * vCoord;
+#else
+ gl_Position = matrix * vCoord;
+#endif
// Calculate neighbor pixel position in item space.
- vec3 wDelta = gl_Position.w * ubuf.vecDelta.xyw;
+ vec3 wDelta = gl_Position.w * vecDelta.xyw;
vec3 farLeft = vCoord.xyw - 0.667 * wDelta;
vec3 nearLeft = vCoord.xyw - 0.333 * wDelta;
vec3 nearRight = vCoord.xyw + 0.333 * wDelta;
vec3 farRight = vCoord.xyw + 0.667 * wDelta;
// Calculate neighbor texture coordinate.
- vec2 scale = ubuf.textureScale / ubuf.fontScale;
+ vec2 scale = textureScale / fontScale;
vec2 base = sampleCoord - scale * vCoord.xy;
sampleFarLeft = vec3(base * farLeft.z + scale * farLeft.xy, farLeft.z);
sampleNearLeft = vec3(base * nearLeft.z + scale * nearLeft.xy, nearLeft.z);
diff --git a/src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext_a.frag b/src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext_a.frag
index a9d56f6380..3bd18f1dec 100644
--- a/src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext_a.frag
+++ b/src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext_a.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -11,7 +14,11 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -19,7 +26,7 @@ layout(std140, binding = 0) uniform buf {
// up to this point it must match distancefieldtext
float fontScale;
vec4 vecDelta;
-} ubuf;
+};
void main()
{
@@ -31,10 +38,10 @@ void main()
n.w = textureProj(_qt_texture, sampleFarRight).a;
vec2 d = min(abs(n.yw - n.xz) * 2., 0.67);
- vec2 lo = mix(vec2(ubuf.alphaMin), vec2(0.5), d);
- vec2 hi = mix(vec2(ubuf.alphaMax), vec2(0.5), d);
+ vec2 lo = mix(vec2(alphaMin), vec2(0.5), d);
+ vec2 hi = mix(vec2(alphaMax), vec2(0.5), d);
n = smoothstep(lo.xxyy, hi.xxyy, n);
c = smoothstep(lo.x + lo.y, hi.x + hi.y, 2. * c);
- fragColor = vec4(0.333 * (n.xyz + n.yzw + c), c) * ubuf.color.w;
+ fragColor = vec4(0.333 * (n.xyz + n.yzw + c), c) * color.w;
}
diff --git a/src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.frag b/src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.frag
index 08b2ce5187..5571417bc5 100644
--- a/src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.frag
+++ b/src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec3 sampleNearLeft;
@@ -8,7 +11,11 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -16,14 +23,14 @@ layout(std140, binding = 0) uniform buf {
// up to this point it must match distancefieldtext
float fontScale;
vec4 vecDelta;
-} ubuf;
+};
void main()
{
vec2 n;
n.x = textureProj(_qt_texture, sampleNearLeft).r;
n.y = textureProj(_qt_texture, sampleNearRight).r;
- n = smoothstep(ubuf.alphaMin, ubuf.alphaMax, n);
+ n = smoothstep(alphaMin, alphaMax, n);
float c = 0.5 * (n.x + n.y);
- fragColor = vec4(n.x, c, n.y, c) * ubuf.color.w;
+ fragColor = vec4(n.x, c, n.y, c) * color.w;
}
diff --git a/src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.vert b/src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.vert
index 187c384959..1b1d3b4013 100644
--- a/src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.vert
+++ b/src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.vert
@@ -7,7 +7,11 @@ layout(location = 0) out vec3 sampleNearLeft;
layout(location = 1) out vec3 sampleNearRight;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -15,22 +19,24 @@ layout(std140, binding = 0) uniform buf {
// up to this point it must match distancefieldtext
float fontScale;
vec4 vecDelta;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+};
void main()
{
- vec2 sampleCoord = tCoord * ubuf.textureScale;
- gl_Position = ubuf.matrix * vCoord;
+ vec2 sampleCoord = tCoord * textureScale;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = matrix[gl_ViewIndex] * vCoord;
+#else
+ gl_Position = matrix * vCoord;
+#endif
// Calculate neighbor pixel position in item space.
- vec3 wDelta = gl_Position.w * ubuf.vecDelta.xyw;
+ vec3 wDelta = gl_Position.w * vecDelta.xyw;
vec3 nearLeft = vCoord.xyw - 0.25 * wDelta;
vec3 nearRight = vCoord.xyw + 0.25 * wDelta;
// Calculate neighbor texture coordinate.
- vec2 scale = ubuf.textureScale / ubuf.fontScale;
+ vec2 scale = textureScale / fontScale;
vec2 base = sampleCoord - scale * vCoord.xy;
sampleNearLeft = vec3(base * nearLeft.z + scale * nearLeft.xy, nearLeft.z);
sampleNearRight = vec3(base * nearRight.z + scale * nearRight.xy, nearRight.z);
diff --git a/src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext_a.frag b/src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext_a.frag
index ef9407491b..3cd7176721 100644
--- a/src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext_a.frag
+++ b/src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext_a.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec3 sampleNearLeft;
@@ -8,7 +11,11 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -16,14 +23,14 @@ layout(std140, binding = 0) uniform buf {
// up to this point it must match distancefieldtext
float fontScale;
vec4 vecDelta;
-} ubuf;
+};
void main()
{
vec2 n;
n.x = textureProj(_qt_texture, sampleNearLeft).a;
n.y = textureProj(_qt_texture, sampleNearRight).a;
- n = smoothstep(ubuf.alphaMin, ubuf.alphaMax, n);
+ n = smoothstep(alphaMin, alphaMax, n);
float c = 0.5 * (n.x + n.y);
- fragColor = vec4(n.x, c, n.y, c) * ubuf.color.w;
+ fragColor = vec4(n.x, c, n.y, c) * color.w;
}
diff --git a/src/quick/scenegraph/shaders_ng/opaquetexture.frag b/src/quick/scenegraph/shaders_ng/opaquetexture.frag
index 2cd2175f87..18dd7e0fcb 100644
--- a/src/quick/scenegraph/shaders_ng/opaquetexture.frag
+++ b/src/quick/scenegraph/shaders_ng/opaquetexture.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 qt_TexCoord;
diff --git a/src/quick/scenegraph/shaders_ng/opaquetexture.vert b/src/quick/scenegraph/shaders_ng/opaquetexture.vert
index 5b52a59004..22df4a239a 100644
--- a/src/quick/scenegraph/shaders_ng/opaquetexture.vert
+++ b/src/quick/scenegraph/shaders_ng/opaquetexture.vert
@@ -6,13 +6,19 @@ layout(location = 1) in vec2 qt_VertexTexCoord;
layout(location = 0) out vec2 qt_TexCoord;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 qt_Matrix[QSHADER_VIEW_COUNT];
+#else
mat4 qt_Matrix;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+#endif
+};
void main()
{
qt_TexCoord = qt_VertexTexCoord;
- gl_Position = ubuf.qt_Matrix * qt_VertexPosition;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = qt_Matrix[gl_ViewIndex] * qt_VertexPosition;
+#else
+ gl_Position = qt_Matrix * qt_VertexPosition;
+#endif
}
diff --git a/src/quick/scenegraph/shaders_ng/outlinedtext.frag b/src/quick/scenegraph/shaders_ng/outlinedtext.frag
index e2f82d3845..5c81f26272 100644
--- a/src/quick/scenegraph/shaders_ng/outlinedtext.frag
+++ b/src/quick/scenegraph/shaders_ng/outlinedtext.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -12,14 +15,18 @@ layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
mat4 modelViewMatrix;
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 projectionMatrix[QSHADER_VIEW_COUNT];
+#else
mat4 projectionMatrix;
- vec4 color;
+#endif
vec2 textureScale;
float dpr;
+ vec4 color;
// the above must stay compatible with textmask/8bittextmask
vec4 styleColor;
vec2 shift;
-} ubuf;
+};
void main()
{
@@ -30,5 +37,5 @@ void main()
texture(_qt_texture, sCoordRight).r,
0.0, 1.0) - glyph,
0.0, 1.0);
- fragColor = outline * ubuf.styleColor + step(1.0 - glyph, 1.0) * glyph * ubuf.color;
+ fragColor = outline * styleColor + step(1.0 - glyph, 1.0) * glyph * color;
}
diff --git a/src/quick/scenegraph/shaders_ng/outlinedtext.vert b/src/quick/scenegraph/shaders_ng/outlinedtext.vert
index 4068e42f28..a75b15556d 100644
--- a/src/quick/scenegraph/shaders_ng/outlinedtext.vert
+++ b/src/quick/scenegraph/shaders_ng/outlinedtext.vert
@@ -11,24 +11,30 @@ layout(location = 4) out vec2 sCoordRight;
layout(std140, binding = 0) uniform buf {
mat4 modelViewMatrix;
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 projectionMatrix[QSHADER_VIEW_COUNT];
+#else
mat4 projectionMatrix;
- vec4 color;
+#endif
vec2 textureScale;
float dpr;
+ vec4 color;
// the above must stay compatible with textmask/8bittextmask
vec4 styleColor;
vec2 shift;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+};
void main()
{
- sampleCoord = tCoord * ubuf.textureScale;
- sCoordUp = (tCoord - vec2(0.0, -1.0)) * ubuf.textureScale;
- sCoordDown = (tCoord - vec2(0.0, 1.0)) * ubuf.textureScale;
- sCoordLeft = (tCoord - vec2(-1.0, 0.0)) * ubuf.textureScale;
- sCoordRight = (tCoord - vec2(1.0, 0.0)) * ubuf.textureScale;
- vec4 xformed = ubuf.modelViewMatrix * vCoord;
- gl_Position = ubuf.projectionMatrix * vec4(floor(xformed.xyz * ubuf.dpr + 0.5) / ubuf.dpr, xformed.w);
+ sampleCoord = tCoord * textureScale;
+ sCoordUp = (tCoord - vec2(0.0, -1.0)) * textureScale;
+ sCoordDown = (tCoord - vec2(0.0, 1.0)) * textureScale;
+ sCoordLeft = (tCoord - vec2(-1.0, 0.0)) * textureScale;
+ sCoordRight = (tCoord - vec2(1.0, 0.0)) * textureScale;
+ vec4 xformed = modelViewMatrix * vCoord;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = projectionMatrix[gl_ViewIndex] * vec4(floor(xformed.xyz * dpr + 0.5) / dpr, xformed.w);
+#else
+ gl_Position = projectionMatrix * vec4(floor(xformed.xyz * dpr + 0.5) / dpr, xformed.w);
+#endif
}
diff --git a/src/quick/scenegraph/shaders_ng/outlinedtext_a.frag b/src/quick/scenegraph/shaders_ng/outlinedtext_a.frag
index 274d891a3c..c4db8b54f0 100644
--- a/src/quick/scenegraph/shaders_ng/outlinedtext_a.frag
+++ b/src/quick/scenegraph/shaders_ng/outlinedtext_a.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -12,14 +15,18 @@ layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
mat4 modelViewMatrix;
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 projectionMatrix[QSHADER_VIEW_COUNT];
+#else
mat4 projectionMatrix;
- vec4 color;
+#endif
vec2 textureScale;
float dpr;
+ vec4 color;
// the above must stay compatible with textmask/8bittextmask
vec4 styleColor;
vec2 shift;
-} ubuf;
+};
void main()
{
@@ -30,5 +37,5 @@ void main()
texture(_qt_texture, sCoordRight).a,
0.0, 1.0) - glyph,
0.0, 1.0);
- fragColor = outline * ubuf.styleColor + step(1.0 - glyph, 1.0) * glyph * ubuf.color;
+ fragColor = outline * styleColor + step(1.0 - glyph, 1.0) * glyph * color;
}
diff --git a/src/quick/scenegraph/shaders_ng/shadereffect.frag b/src/quick/scenegraph/shaders_ng/shadereffect.frag
index bde493f6ce..3d1037ed05 100644
--- a/src/quick/scenegraph/shaders_ng/shadereffect.frag
+++ b/src/quick/scenegraph/shaders_ng/shadereffect.frag
@@ -1,10 +1,17 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform qt_buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 qt_Matrix[QSHADER_VIEW_COUNT];
+#else
mat4 qt_Matrix;
+#endif
float qt_Opacity;
} qt_ubuf;
diff --git a/src/quick/scenegraph/shaders_ng/shadereffect.vert b/src/quick/scenegraph/shaders_ng/shadereffect.vert
index ae65059f19..e030b5ee71 100644
--- a/src/quick/scenegraph/shaders_ng/shadereffect.vert
+++ b/src/quick/scenegraph/shaders_ng/shadereffect.vert
@@ -6,14 +6,20 @@ layout(location = 1) in vec2 qt_MultiTexCoord0;
layout(location = 0) out vec2 qt_TexCoord0;
layout(std140, binding = 0) uniform qt_buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 qt_Matrix[QSHADER_VIEW_COUNT];
+#else
mat4 qt_Matrix;
+#endif
float qt_Opacity;
} qt_ubuf; // must use a name that does not clash with custom code when no uniform blocks
-out gl_PerVertex { vec4 gl_Position; };
-
void main()
{
qt_TexCoord0 = qt_MultiTexCoord0;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = qt_ubuf.qt_Matrix[gl_ViewIndex] * qt_Vertex;
+#else
gl_Position = qt_ubuf.qt_Matrix * qt_Vertex;
+#endif
}
diff --git a/src/quick/scenegraph/shaders_ng/shapecurve.frag b/src/quick/scenegraph/shaders_ng/shapecurve.frag
new file mode 100644
index 0000000000..cc97f375ab
--- /dev/null
+++ b/src/quick/scenegraph/shaders_ng/shapecurve.frag
@@ -0,0 +1,152 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#version 440
+
+layout(location = 0) in vec4 qt_TexCoord;
+layout(location = 1) in vec4 gradient;
+
+#if defined(LINEARGRADIENT)
+layout(location = 2) in float gradTabIndex;
+#elif defined(RADIALGRADIENT) || defined(CONICALGRADIENT)
+layout(location = 2) in vec2 coord;
+#elif defined(TEXTUREFILL)
+layout(location = 2) in vec2 textureCoord;
+#endif
+
+layout(location = 0) out vec4 fragColor;
+
+layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 qt_Matrix[QSHADER_VIEW_COUNT];
+#else
+ mat4 qt_Matrix;
+#endif
+ float matrixScale;
+ float opacity;
+ float debug;
+ float reserved3;
+
+#if defined(LINEARGRADIENT) || defined(RADIALGRADIENT) || defined(CONICALGRADIENT) || defined(TEXTUREFILL)
+ mat4 gradientMatrix;
+#endif
+#if defined(LINEARGRADIENT)
+ vec2 gradientStart;
+ vec2 gradientEnd;
+#elif defined(RADIALGRADIENT)
+ vec2 translationPoint;
+ vec2 focalToCenter;
+ float centerRadius;
+ float focalRadius;
+#elif defined(CONICALGRADIENT)
+ vec2 translationPoint;
+ float angle;
+#elif defined(TEXTUREFILL)
+ vec2 boundsSize;
+#else
+ vec4 color;
+#endif
+} ubuf;
+
+#define INVERSE_2PI 0.1591549430918953358
+
+#if defined(LINEARGRADIENT) || defined(RADIALGRADIENT) || defined(CONICALGRADIENT)
+layout(binding = 1) uniform sampler2D gradTabTexture;
+#elif defined(TEXTUREFILL)
+layout(binding = 1) uniform sampler2D sourceTexture;
+#endif
+
+vec4 baseColor()
+{
+#if defined(LINEARGRADIENT)
+ return texture(gradTabTexture, vec2(gradTabIndex, 0.5));
+#elif defined(RADIALGRADIENT)
+ float rd = ubuf.centerRadius - ubuf.focalRadius;
+ float b = 2.0 * (rd * ubuf.focalRadius + dot(coord, ubuf.focalToCenter));
+ float fmp2_m_radius2 = -ubuf.focalToCenter.x * ubuf.focalToCenter.x - ubuf.focalToCenter.y * ubuf.focalToCenter.y + rd * rd;
+ float inverse_2_fmp2_m_radius2 = 1.0 / (2.0 * fmp2_m_radius2);
+ float det = b * b - 4.0 * fmp2_m_radius2 * ((ubuf.focalRadius * ubuf.focalRadius) - dot(coord, coord));
+ vec4 result = vec4(0.0);
+ if (det >= 0.0) {
+ float detSqrt = sqrt(det);
+ float w = max((-b - detSqrt) * inverse_2_fmp2_m_radius2, (-b + detSqrt) * inverse_2_fmp2_m_radius2);
+ if (ubuf.focalRadius + w * (ubuf.centerRadius - ubuf.focalRadius) >= 0.0)
+ result = texture(gradTabTexture, vec2(w, 0.5));
+ }
+
+ return result;
+#elif defined(CONICALGRADIENT)
+ float t;
+ if (abs(coord.y) == abs(coord.x))
+ t = (atan(-coord.y + 0.002, coord.x) + ubuf.angle) * INVERSE_2PI;
+ else
+ t = (atan(-coord.y, coord.x) + ubuf.angle) * INVERSE_2PI;
+ return texture(gradTabTexture, vec2(t - floor(t), 0.5));
+#elif defined(TEXTUREFILL)
+ return texture(sourceTexture, textureCoord);
+#else
+ return vec4(ubuf.color.rgb, 1.0) * ubuf.color.a;
+#endif
+}
+
+void main()
+{
+ float f = qt_TexCoord.z * (qt_TexCoord.x * qt_TexCoord.x - qt_TexCoord.y) // curve
+ + (1.0 - abs(qt_TexCoord.z)) * (qt_TexCoord.x - qt_TexCoord.y); // line
+
+#if defined(USE_DERIVATIVES)
+ float _ddx = dFdx(f);
+ float _ddy = dFdy(f);
+ float df = length(vec2(_ddx, _ddy));
+#else
+ // We calculate the partial derivatives for f'(x, y) based on knowing the partial derivatives
+ // for the texture coordinates (u, v).
+ // So for curves:
+ // f(x,y) = u(x, y) * u(x, y) - v(x, y)
+ // f(x,y) = p(u(x,y)) - v(x, y) where p(u) = u^2
+ // So f'(x, y) = p'(u(x, y)) * u'(x, y) - v'(x, y)
+ // (by chain rule and sum rule)
+ // f'(x, y) = 2 * u(x, y) * u'(x, y) - v'(x, y)
+ // And so:
+ // df/dx = 2 * u(x, y) * du/dx - dv/dx
+ // df/dy = 2 * u(x, y) * du/dy - dv/dy
+ //
+ // and similarly for straight lines:
+ // f(x, y) = u(x, y) - v(x, y)
+ // f'(x, y) = dudx - dvdx
+
+ float dudx = gradient.x;
+ float dvdx = gradient.y;
+ float dudy = gradient.z;
+ float dvdy = gradient.w;
+
+ // Test with analytic derivatives
+// dudx = dFdx(qt_TexCoord.x);
+// dvdx = dFdx(qt_TexCoord.y);
+// dudy = dFdy(qt_TexCoord.x);
+// dvdy = dFdy(qt_TexCoord.y);
+
+ float dfx_curve = 2.0f * qt_TexCoord.x * dudx - dvdx;
+ float dfy_curve = 2.0f * qt_TexCoord.x * dudy - dvdy;
+
+ float dfx_line = dudx - dvdx;
+ float dfy_line = dudy - dvdy;
+
+ float dfx = qt_TexCoord.z * dfx_curve + (1.0 - abs(qt_TexCoord.z)) * dfx_line;
+ float dfy = qt_TexCoord.z * dfy_curve + (1.0 - abs(qt_TexCoord.z)) * dfy_line;
+ float df = length(vec2(dfx, dfy));
+#endif
+
+ float isLine = 1.0 - abs(qt_TexCoord.z);
+ float isCurve = 1.0 - isLine;
+ float debugR = isCurve * min(1.0, 1.0 - qt_TexCoord.z);
+ float debugG = isLine;
+ float debugB = isCurve * min(1.0, 1.0 - qt_TexCoord.z * -1.0) + debugG;
+ vec3 debugColor = vec3(debugR, debugG, debugB);
+
+ // Special case: mask out concave curve in "negative space".
+ int specialCaseMask = 1 - int(qt_TexCoord.w != 0.0) * (int(qt_TexCoord.x < 0.0) + int(qt_TexCoord.x > 1.0));
+ float fillCoverage = clamp(0.5 + f / df, 0.0, 1.0) * float(specialCaseMask);
+
+ fragColor = mix(baseColor() * fillCoverage, vec4(debugColor, 1.0), ubuf.debug) * ubuf.opacity;
+}
diff --git a/src/quick/scenegraph/shaders_ng/shapecurve.vert b/src/quick/scenegraph/shaders_ng/shapecurve.vert
new file mode 100644
index 0000000000..3ff53978b8
--- /dev/null
+++ b/src/quick/scenegraph/shaders_ng/shapecurve.vert
@@ -0,0 +1,92 @@
+#version 440
+
+layout(location = 0) in vec4 vertexCoord;
+layout(location = 1) in vec4 vertexTexCoord;
+layout(location = 2) in vec4 vertexGradient;
+layout(location = 3) in vec2 normalVector;
+
+layout(location = 0) out vec4 qt_TexCoord;
+layout(location = 1) out vec4 gradient;
+
+#if defined(LINEARGRADIENT)
+layout(location = 2) out float gradTabIndex;
+#elif defined(RADIALGRADIENT) || defined(CONICALGRADIENT)
+layout(location = 2) out vec2 coord;
+#elif defined(TEXTUREFILL)
+layout(location = 2) out vec2 textureCoord;
+#endif
+
+layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 qt_Matrix[QSHADER_VIEW_COUNT];
+#else
+ mat4 qt_Matrix;
+#endif
+ float matrixScale;
+ float opacity;
+ float debug;
+ float reserved3;
+
+#if defined(LINEARGRADIENT) || defined(RADIALGRADIENT) || defined(CONICALGRADIENT) || defined(TEXTUREFILL)
+ mat4 gradientMatrix;
+#endif
+#if defined(LINEARGRADIENT)
+ vec2 gradientStart;
+ vec2 gradientEnd;
+#elif defined(RADIALGRADIENT)
+ vec2 translationPoint;
+ vec2 focalToCenter;
+ float centerRadius;
+ float focalRadius;
+#elif defined(CONICALGRADIENT)
+ vec2 translationPoint;
+ float angle;
+#elif defined(TEXTUREFILL)
+ vec2 boundsSize;
+#else
+ vec4 color;
+#endif
+} ubuf;
+
+#define SQRT2 1.41421356237
+
+vec4 addOffset(vec4 texCoord, vec2 offset, vec4 duvdxy)
+{
+ float dudx = duvdxy.x;
+ float dvdx = duvdxy.y;
+ float dudy = duvdxy.z;
+ float dvdy = duvdxy.w;
+ float u = offset.x * dudx + offset.y * dudy;
+ float v = offset.x * dvdx + offset.y * dvdy;
+ // special case external triangles for concave curves
+ int specialCase = int(texCoord.z > 0) * (int(offset.x != 0) + int(offset.y != 0));
+ return vec4(texCoord.x + u, texCoord.y + v, texCoord.z, float(specialCase));
+}
+
+void main()
+{
+ vec2 offset = normalVector * SQRT2/ubuf.matrixScale;
+
+ qt_TexCoord = addOffset(vertexTexCoord, offset, vertexGradient);
+
+ gradient = vertexGradient / ubuf.matrixScale;
+
+#if defined(LINEARGRADIENT) || defined(RADIALGRADIENT) || defined(CONICALGRADIENT) || defined(TEXTUREFILL)
+ vec2 gradVertexCoord = (ubuf.gradientMatrix * vertexCoord).xy;
+#endif
+#if defined(LINEARGRADIENT)
+ vec2 gradVec = ubuf.gradientEnd - ubuf.gradientStart;
+ gradTabIndex = dot(gradVec, gradVertexCoord - ubuf.gradientStart) / dot(gradVec, gradVec);
+#elif defined(RADIALGRADIENT) || defined(CONICALGRADIENT)
+ coord = gradVertexCoord - ubuf.translationPoint;
+#elif defined(TEXTUREFILL)
+ textureCoord = vec2(gradVertexCoord.x / ubuf.boundsSize.x,
+ gradVertexCoord.y / ubuf.boundsSize.y);
+#endif
+
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = ubuf.qt_Matrix[gl_ViewIndex] * (vertexCoord + vec4(offset, 0, 0));
+#else
+ gl_Position = ubuf.qt_Matrix * (vertexCoord + vec4(offset, 0, 0));
+#endif
+}
diff --git a/src/quick/scenegraph/shaders_ng/shapestroke.frag b/src/quick/scenegraph/shaders_ng/shapestroke.frag
new file mode 100644
index 0000000000..cedfa0845a
--- /dev/null
+++ b/src/quick/scenegraph/shaders_ng/shapestroke.frag
@@ -0,0 +1,134 @@
+#version 440
+
+layout(location = 0) in vec4 P;
+layout(location = 1) in vec2 A;
+layout(location = 2) in vec2 B;
+layout(location = 3) in vec2 C;
+layout(location = 4) in vec2 HG;
+layout(location = 5) in float offset;
+
+layout(location = 0) out vec4 fragColor;
+
+layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 qt_Matrix[QSHADER_VIEW_COUNT];
+#else
+ mat4 qt_Matrix;
+#endif
+
+ float matrixScale;
+ float opacity;
+ float reserved2;
+ float reserved3;
+
+ vec4 strokeColor;
+
+ float strokeWidth;
+ float debug;
+ float reserved5;
+ float reserved6;
+} ubuf;
+
+float cuberoot(float x)
+{
+ return sign(x) * pow(abs(x), 1 / 3.);
+}
+
+#define PI 3.1415926538
+
+vec3 solveDepressedCubic(float p, float q)
+{
+ float D = q * q / 4. + p * p * p / 27.;
+
+ float u1 = cuberoot(-q / 2. - sqrt(D));
+ float u2 = cuberoot(-q / 2. + sqrt(D));
+ vec3 rootsD1 = vec3(u1 - p / (3. * u1), u2 - p / (3. * u2), 0);
+
+ float v = 2.*sqrt(-p / 3.);
+ float t = acos(3. * q / p / v) / 3.;
+ float k = 2. * PI / 3.;
+ vec3 rootsD2 = vec3(v * cos(t), v * cos(t - k), v * cos(t - 2. * k));
+
+ return D > 0 ? rootsD1 : rootsD2;
+}
+
+mat2 qInverse(mat2 matrix) {
+ float a = matrix[0][0], b = matrix[0][1];
+ float c = matrix[1][0], d = matrix[1][1];
+
+ float determinant = a * d - b * c;
+ float invDet = 1.0 / determinant;
+
+ mat2 inverseMatrix;
+ inverseMatrix[0][0] = d * invDet;
+ inverseMatrix[0][1] = -b * invDet;
+ inverseMatrix[1][0] = -c * invDet;
+ inverseMatrix[1][1] = a * invDet;
+
+ return inverseMatrix;
+}
+
+void main()
+{
+ vec3 s = solveDepressedCubic(HG.x, HG.y) - vec3(offset, offset, offset);
+
+ vec2 Qmin = vec2(1e10, 1e10);
+ float dmin = 1e4;
+ for (int i = 0; i < 3; i++) {
+ float t = clamp(s[i], 0., 1.);
+ vec2 Q = A * t * t + B * t + C;
+ float d = length(Q - P.xy);
+ float foundNewMin = step(d, dmin);
+ dmin = min(d, dmin);
+ Qmin = foundNewMin * Q + (1. - foundNewMin) * Qmin;
+ }
+ vec2 n = (P.xy - Qmin) / dmin;
+ vec2 Q1 = (Qmin + ubuf.strokeWidth / 2. * n);
+ vec2 Q2 = (Qmin - ubuf.strokeWidth / 2. * n);
+
+ // Converting to screen coordinates:
+#if defined(USE_DERIVATIVES)
+ mat2 T = mat2(dFdx(P.x), dFdy(P.x), dFdx(P.y), dFdy(P.y));
+ mat2 Tinv = qInverse(T);
+ vec2 Q1_s = Tinv * Q1;
+ vec2 Q2_s = Tinv * Q2;
+ vec2 P_s = Tinv * P.xy;
+ vec2 n_s = Tinv * n;
+ n_s = n_s / length(n_s);
+#else
+ vec2 Q1_s = ubuf.matrixScale * Q1;
+ vec2 Q2_s = ubuf.matrixScale * Q2;
+ vec2 P_s = ubuf.matrixScale * P.xy;
+ vec2 n_s = n;
+#endif
+
+ // Geometric solution for anti aliasing using the known distances
+ // to the edges of the path in the screen coordinate system.
+ float dist1 = dot(P_s - Q1_s, n_s);
+ float dist2 = dot(P_s - Q2_s, n_s);
+
+ // Calculate the fill coverage if the line is crossing the square cell
+ // normally (vertically or horizontally).
+ // float fillCoverageLin = clamp(0.5-dist1, 0., 1.) - clamp(0.5-dist2, 0., 1.);
+
+ // Calculate the fill coverage if the line is crossing the square cell
+ // diagonally.
+ float fillCoverageDia = clamp(step(0., -dist1) + sign(dist1) * pow(max(0., sqrt(2.) / 2. - abs(dist1)), 2.), 0., 1.) -
+ clamp(step(0., -dist2) + sign(dist2) * pow(max(0., sqrt(2.) / 2. - abs(dist2)), 2.), 0., 1.);
+
+ // Merge the normal and the diagonal solution. The merge factor is periodic
+ // in 90 degrees and 0/1 at 0 and 45 degree. The simple equation was
+ // estimated after numerical experiments.
+ // float mergeFactor = 2 * abs(n_s.x) * abs(n_s.y);
+ // float fillCoverage = mergeFactor * fillCoverageDia + (1-mergeFactor) * fillCoverageLin;
+
+ // It seems to be sufficient to use the equation for the diagonal.
+ float fillCoverage = fillCoverageDia;
+
+ // The center line is sometimes not filled because of numerical issues. This fixes this.
+ float centerline = step(ubuf.strokeWidth * 0.01, dmin);
+ fillCoverage = fillCoverage * centerline + min(1., ubuf.strokeWidth * ubuf.matrixScale) * (1. - centerline);
+
+ fragColor = vec4(ubuf.strokeColor.rgb, 1.0) *ubuf.strokeColor.a * fillCoverage * ubuf.opacity
+ + ubuf.debug * vec4(0.0, 0.5, 1.0, 1.0) * (1.0 - fillCoverage) * ubuf.opacity;
+}
diff --git a/src/quick/scenegraph/shaders_ng/shapestroke.vert b/src/quick/scenegraph/shaders_ng/shapestroke.vert
new file mode 100644
index 0000000000..e358e059eb
--- /dev/null
+++ b/src/quick/scenegraph/shaders_ng/shapestroke.vert
@@ -0,0 +1,82 @@
+#version 440
+
+layout(location = 0) in vec4 vertexCoord;
+layout(location = 1) in vec2 inA;
+layout(location = 2) in vec2 inB;
+layout(location = 3) in vec2 inC;
+layout(location = 4) in vec2 normalVector;
+
+layout(location = 0) out vec4 P;
+layout(location = 1) out vec2 A;
+layout(location = 2) out vec2 B;
+layout(location = 3) out vec2 C;
+layout(location = 4) out vec2 HG;
+layout(location = 5) out float offset;
+
+
+layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 qt_Matrix[QSHADER_VIEW_COUNT];
+#else
+ mat4 qt_Matrix;
+#endif
+
+ float matrixScale;
+ float opacity;
+ float reserved2;
+ float reserved3;
+
+ vec4 strokeColor;
+
+ float strokeWidth;
+ float debug;
+ float reserved5;
+ float reserved6;
+} ubuf;
+
+#define SQRT2 1.41421356237
+
+float qdot(vec2 a, vec2 b)
+{
+ return a.x * b.x + a.y * b.y;
+}
+
+void main()
+{
+ P = vertexCoord + vec4(normalVector, 0.0, 0.0) * SQRT2/ubuf.matrixScale;
+
+ A = inA;
+ B = inB;
+ C = inC;
+
+ // Find the parameters H, G for the depressed cubic
+ // t^2+H*t+G=0
+ // that results from the equation
+ // Q'(s).(p-Q(s)) = 0
+ // The last parameter is the static offset between s and t:
+ // s = t - b/(3a)
+ // use it to get back the parameter t
+
+ // this is a constant for the curve
+ float a = -2. * qdot(A, A);
+ // this is a constant for the curve
+ float b = -3. * qdot(A, B);
+ //this is linear in p so it can be put into the shader with vertex data
+ float c = 2. * qdot(A, P.xy) - qdot(B, B) - 2. * qdot(A, C);
+ //this is linear in p so it can be put into the shader with vertex data
+ float d = qdot(B, P.xy) - qdot(B, C);
+ // convert to depressed cubic.
+ // both functions are linear in c and d and thus linear in p
+ float H = (3. * a * c - b * b) / (3. * a * a);
+ float G = (2. * b * b * b - 9. * a * b * c + 27. * a * a * d) / (27. * a * a * a);
+ HG = vec2(H, G);
+ offset = b/(3*a);
+
+
+
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = ubuf.qt_Matrix[gl_ViewIndex] * P;
+#else
+ gl_Position = ubuf.qt_Matrix * P;
+#endif
+}
diff --git a/src/quick/scenegraph/shaders_ng/smoothcolor.frag b/src/quick/scenegraph/shaders_ng/smoothcolor.frag
index ede283be0c..314a387922 100644
--- a/src/quick/scenegraph/shaders_ng/smoothcolor.frag
+++ b/src/quick/scenegraph/shaders_ng/smoothcolor.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec4 color;
diff --git a/src/quick/scenegraph/shaders_ng/smoothcolor.vert b/src/quick/scenegraph/shaders_ng/smoothcolor.vert
index 03a3ff8975..6ec10a7e70 100644
--- a/src/quick/scenegraph/shaders_ng/smoothcolor.vert
+++ b/src/quick/scenegraph/shaders_ng/smoothcolor.vert
@@ -7,22 +7,32 @@ layout(location = 2) in vec4 vertexOffset;
layout(location = 0) out vec4 color;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 pixelSize;
float opacity;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+};
void main()
{
- vec4 pos = ubuf.matrix * vertex;
+#if QSHADER_VIEW_COUNT >= 2
+ vec4 pos = matrix[gl_ViewIndex] * vertex;
+ vec4 m0 = matrix[gl_ViewIndex][0];
+ vec4 m1 = matrix[gl_ViewIndex][1];
+#else
+ vec4 pos = matrix * vertex;
+ vec4 m0 = matrix[0];
+ vec4 m1 = matrix[1];
+#endif
gl_Position = pos;
if (vertexOffset.x != 0.) {
- vec4 delta = ubuf.matrix[0] * vertexOffset.x;
+ vec4 delta = m0 * vertexOffset.x;
vec2 dir = delta.xy * pos.w - pos.xy * delta.w;
- vec2 ndir = .5 * ubuf.pixelSize * normalize(dir / ubuf.pixelSize);
+ vec2 ndir = .5 * pixelSize * normalize(dir / pixelSize);
dir -= ndir * delta.w * pos.w;
float numerator = dot(dir, ndir * pos.w * pos.w);
float scale = 0.0;
@@ -34,9 +44,9 @@ void main()
}
if (vertexOffset.y != 0.) {
- vec4 delta = ubuf.matrix[1] * vertexOffset.y;
+ vec4 delta = m1 * vertexOffset.y;
vec2 dir = delta.xy * pos.w - pos.xy * delta.w;
- vec2 ndir = .5 * ubuf.pixelSize * normalize(dir / ubuf.pixelSize);
+ vec2 ndir = .5 * pixelSize * normalize(dir / pixelSize);
dir -= ndir * delta.w * pos.w;
float numerator = dot(dir, ndir * pos.w * pos.w);
float scale = 0.0;
@@ -47,5 +57,5 @@ void main()
gl_Position += scale * delta;
}
- color = vertexColor * ubuf.opacity;
+ color = vertexColor * opacity;
}
diff --git a/src/quick/scenegraph/shaders_ng/smoothtexture.frag b/src/quick/scenegraph/shaders_ng/smoothtexture.frag
index b06764ad95..a7ddc57535 100644
--- a/src/quick/scenegraph/shaders_ng/smoothtexture.frag
+++ b/src/quick/scenegraph/shaders_ng/smoothtexture.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 texCoord;
diff --git a/src/quick/scenegraph/shaders_ng/smoothtexture.vert b/src/quick/scenegraph/shaders_ng/smoothtexture.vert
index 965c837852..4edde5472c 100644
--- a/src/quick/scenegraph/shaders_ng/smoothtexture.vert
+++ b/src/quick/scenegraph/shaders_ng/smoothtexture.vert
@@ -9,23 +9,33 @@ layout(location = 0) out vec2 texCoord;
layout(location = 1) out float vertexOpacity;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 qt_Matrix[QSHADER_VIEW_COUNT];
+#else
mat4 qt_Matrix;
+#endif
float opacity;
vec2 pixelSize;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+};
void main()
{
- vec4 pos = ubuf.qt_Matrix * vertex;
+#if QSHADER_VIEW_COUNT >= 2
+ vec4 pos = qt_Matrix[gl_ViewIndex] * vertex;
+ vec4 m0 = qt_Matrix[gl_ViewIndex][0];
+ vec4 m1 = qt_Matrix[gl_ViewIndex][1];
+#else
+ vec4 pos = qt_Matrix * vertex;
+ vec4 m0 = qt_Matrix[0];
+ vec4 m1 = qt_Matrix[1];
+#endif
gl_Position = pos;
texCoord = multiTexCoord;
if (vertexOffset.x != 0.) {
- vec4 delta = ubuf.qt_Matrix[0] * vertexOffset.x;
+ vec4 delta = m0 * vertexOffset.x;
vec2 dir = delta.xy * pos.w - pos.xy * delta.w;
- vec2 ndir = .5 * ubuf.pixelSize * normalize(dir / ubuf.pixelSize);
+ vec2 ndir = .5 * pixelSize * normalize(dir / pixelSize);
dir -= ndir * delta.w * pos.w;
float numerator = dot(dir, ndir * pos.w * pos.w);
float scale = 0.0;
@@ -38,9 +48,9 @@ void main()
}
if (vertexOffset.y != 0.) {
- vec4 delta = ubuf.qt_Matrix[1] * vertexOffset.y;
+ vec4 delta = m1 * vertexOffset.y;
vec2 dir = delta.xy * pos.w - pos.xy * delta.w;
- vec2 ndir = .5 * ubuf.pixelSize * normalize(dir / ubuf.pixelSize);
+ vec2 ndir = .5 * pixelSize * normalize(dir / pixelSize);
dir -= ndir * delta.w * pos.w;
float numerator = dot(dir, ndir * pos.w * pos.w);
float scale = 0.0;
@@ -57,5 +67,5 @@ void main()
if (onEdge && outerEdge)
vertexOpacity = 0.;
else
- vertexOpacity = ubuf.opacity;
+ vertexOpacity = opacity;
}
diff --git a/src/quick/scenegraph/shaders_ng/sprite.frag b/src/quick/scenegraph/shaders_ng/sprite.frag
index 338f5e957e..387be43bb1 100644
--- a/src/quick/scenegraph/shaders_ng/sprite.frag
+++ b/src/quick/scenegraph/shaders_ng/sprite.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec4 fTexS;
@@ -8,15 +11,19 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D tex;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec4 animPos;
vec3 animData;
float opacity;
-} ubuf;
+};
void main()
{
fragColor = mix(texture(tex, fTexS.xy),
texture(tex, fTexS.zw),
- progress) * ubuf.opacity;
+ progress) * opacity;
}
diff --git a/src/quick/scenegraph/shaders_ng/sprite.vert b/src/quick/scenegraph/shaders_ng/sprite.vert
index b76e2b206f..b693e31334 100644
--- a/src/quick/scenegraph/shaders_ng/sprite.vert
+++ b/src/quick/scenegraph/shaders_ng/sprite.vert
@@ -7,23 +7,29 @@ layout(location = 0) out vec4 fTexS;
layout(location = 1) out float progress;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec4 animPos; // x,y, x,y (two frames for interpolation)
vec3 animData; // w,h(premultiplied of anim), interpolation progress
float opacity;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+};
void main()
{
- progress = ubuf.animData.z;
+ progress = animData.z;
// Calculate frame location in texture
- fTexS.xy = ubuf.animPos.xy + vTex.xy * ubuf.animData.xy;
+ fTexS.xy = animPos.xy + vTex.xy * animData.xy;
// Next frame is also passed, for interpolation
- fTexS.zw = ubuf.animPos.zw + vTex.xy * ubuf.animData.xy;
+ fTexS.zw = animPos.zw + vTex.xy * animData.xy;
- gl_Position = ubuf.matrix * vec4(vPos.x, vPos.y, 0, 1);
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = matrix[gl_ViewIndex] * vec4(vPos.x, vPos.y, 0, 1);
+#else
+ gl_Position = matrix * vec4(vPos.x, vPos.y, 0, 1);
+#endif
}
diff --git a/src/quick/scenegraph/shaders_ng/stencilclip.frag b/src/quick/scenegraph/shaders_ng/stencilclip.frag
index 3f6222389d..ec4d3a05b1 100644
--- a/src/quick/scenegraph/shaders_ng/stencilclip.frag
+++ b/src/quick/scenegraph/shaders_ng/stencilclip.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) out vec4 fragColor;
diff --git a/src/quick/scenegraph/shaders_ng/styledtext.frag b/src/quick/scenegraph/shaders_ng/styledtext.frag
index 2e380dfeae..e55e10b05e 100644
--- a/src/quick/scenegraph/shaders_ng/styledtext.frag
+++ b/src/quick/scenegraph/shaders_ng/styledtext.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -9,19 +12,23 @@ layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
mat4 modelViewMatrix;
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 projectionMatrix[QSHADER_VIEW_COUNT];
+#else
mat4 projectionMatrix;
- vec4 color;
+#endif
vec2 textureScale;
float dpr;
+ vec4 color;
// the above must stay compatible with textmask/8bittextmask
vec4 styleColor;
vec2 shift;
-} ubuf;
+};
void main()
{
float glyph = texture(_qt_texture, sampleCoord).r;
float style = clamp(texture(_qt_texture, shiftedSampleCoord).r - glyph,
0.0, 1.0);
- fragColor = style * ubuf.styleColor + glyph * ubuf.color;
+ fragColor = style * styleColor + glyph * color;
}
diff --git a/src/quick/scenegraph/shaders_ng/styledtext.vert b/src/quick/scenegraph/shaders_ng/styledtext.vert
index 271dae8d8a..5e0a8fa5c9 100644
--- a/src/quick/scenegraph/shaders_ng/styledtext.vert
+++ b/src/quick/scenegraph/shaders_ng/styledtext.vert
@@ -8,21 +8,27 @@ layout(location = 1) out vec2 shiftedSampleCoord;
layout(std140, binding = 0) uniform buf {
mat4 modelViewMatrix;
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 projectionMatrix[QSHADER_VIEW_COUNT];
+#else
mat4 projectionMatrix;
- vec4 color;
+#endif
vec2 textureScale;
float dpr;
+ vec4 color;
// the above must stay compatible with textmask/8bittextmask
vec4 styleColor;
vec2 shift;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+};
void main()
{
- sampleCoord = tCoord * ubuf.textureScale;
- shiftedSampleCoord = (tCoord - ubuf.shift) * ubuf.textureScale;
- vec4 xformed = ubuf.modelViewMatrix * vCoord;
- gl_Position = ubuf.projectionMatrix * vec4(floor(xformed.xyz * ubuf.dpr + 0.5) / ubuf.dpr, xformed.w);
+ sampleCoord = tCoord * textureScale;
+ shiftedSampleCoord = (tCoord - shift) * textureScale;
+ vec4 xformed = modelViewMatrix * vCoord;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = projectionMatrix[gl_ViewIndex] * vec4(floor(xformed.xyz * dpr + 0.5) / dpr, xformed.w);
+#else
+ gl_Position = projectionMatrix * vec4(floor(xformed.xyz * dpr + 0.5) / dpr, xformed.w);
+#endif
}
diff --git a/src/quick/scenegraph/shaders_ng/styledtext_a.frag b/src/quick/scenegraph/shaders_ng/styledtext_a.frag
index 62e162c851..6a684c2abb 100644
--- a/src/quick/scenegraph/shaders_ng/styledtext_a.frag
+++ b/src/quick/scenegraph/shaders_ng/styledtext_a.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -9,19 +12,23 @@ layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
mat4 modelViewMatrix;
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 projectionMatrix[QSHADER_VIEW_COUNT];
+#else
mat4 projectionMatrix;
- vec4 color;
+#endif
vec2 textureScale;
float dpr;
+ vec4 color;
// the above must stay compatible with textmask/8bittextmask
vec4 styleColor;
vec2 shift;
-} ubuf;
+};
void main()
{
float glyph = texture(_qt_texture, sampleCoord).a; // take .a instead of .r
float style = clamp(texture(_qt_texture, shiftedSampleCoord).a - glyph,
0.0, 1.0);
- fragColor = style * ubuf.styleColor + glyph * ubuf.color;
+ fragColor = style * styleColor + glyph * color;
}
diff --git a/src/quick/scenegraph/shaders_ng/textmask.frag b/src/quick/scenegraph/shaders_ng/textmask.frag
index ed8da4cd30..0f9d6b4567 100644
--- a/src/quick/scenegraph/shaders_ng/textmask.frag
+++ b/src/quick/scenegraph/shaders_ng/textmask.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -7,14 +10,18 @@ layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
mat4 modelViewMatrix;
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 projectionMatrix[QSHADER_VIEW_COUNT];
+#else
mat4 projectionMatrix;
- vec4 color;
+#endif
vec2 textureScale;
float dpr;
-} ubuf;
+ vec4 color;
+};
void main()
{
vec4 glyph = texture(_qt_texture, sampleCoord);
- fragColor = vec4(glyph.rgb * ubuf.color.a, glyph.a);
+ fragColor = vec4(glyph.rgb * color.a, glyph.a);
}
diff --git a/src/quick/scenegraph/shaders_ng/textmask.vert b/src/quick/scenegraph/shaders_ng/textmask.vert
index e0b3c01bce..41fffa282f 100644
--- a/src/quick/scenegraph/shaders_ng/textmask.vert
+++ b/src/quick/scenegraph/shaders_ng/textmask.vert
@@ -7,17 +7,23 @@ layout(location = 0) out vec2 sampleCoord;
layout(std140, binding = 0) uniform buf {
mat4 modelViewMatrix;
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 projectionMatrix[QSHADER_VIEW_COUNT];
+#else
mat4 projectionMatrix;
- vec4 color;
+#endif
vec2 textureScale;
float dpr;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+ vec4 color;
+};
void main()
{
- sampleCoord = tCoord * ubuf.textureScale;
- vec4 xformed = ubuf.modelViewMatrix * vCoord;
- gl_Position = ubuf.projectionMatrix * vec4(floor(xformed.xyz * ubuf.dpr + 0.5) / ubuf.dpr, xformed.w);
+ sampleCoord = tCoord * textureScale;
+ vec4 xformed = modelViewMatrix * vCoord;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = projectionMatrix[gl_ViewIndex] * vec4(floor(xformed.xyz * dpr + 0.5) / dpr, xformed.w);
+#else
+ gl_Position = projectionMatrix * vec4(floor(xformed.xyz * dpr + 0.5) / dpr, xformed.w);
+#endif
}
diff --git a/src/quick/scenegraph/shaders_ng/texture.frag b/src/quick/scenegraph/shaders_ng/texture.frag
index bd22f817e0..a591e72ccf 100644
--- a/src/quick/scenegraph/shaders_ng/texture.frag
+++ b/src/quick/scenegraph/shaders_ng/texture.frag
@@ -1,16 +1,23 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 qt_TexCoord;
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 qt_Matrix[QSHADER_VIEW_COUNT];
+#else
mat4 qt_Matrix;
+#endif
float opacity;
-} ubuf;
+};
layout(binding = 1) uniform sampler2D qt_Texture;
void main()
{
- fragColor = texture(qt_Texture, qt_TexCoord) * ubuf.opacity;
+ fragColor = texture(qt_Texture, qt_TexCoord) * opacity;
}
diff --git a/src/quick/scenegraph/shaders_ng/texture.vert b/src/quick/scenegraph/shaders_ng/texture.vert
index 537852d2bd..ae8a27f4a6 100644
--- a/src/quick/scenegraph/shaders_ng/texture.vert
+++ b/src/quick/scenegraph/shaders_ng/texture.vert
@@ -6,14 +6,20 @@ layout(location = 1) in vec2 qt_VertexTexCoord;
layout(location = 0) out vec2 qt_TexCoord;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 qt_Matrix[QSHADER_VIEW_COUNT];
+#else
mat4 qt_Matrix;
+#endif
float opacity;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+};
void main()
{
qt_TexCoord = qt_VertexTexCoord;
- gl_Position = ubuf.qt_Matrix * qt_VertexPosition;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = qt_Matrix[gl_ViewIndex] * qt_VertexPosition;
+#else
+ gl_Position = qt_Matrix * qt_VertexPosition;
+#endif
}
diff --git a/src/quick/scenegraph/shaders_ng/vertexcolor.frag b/src/quick/scenegraph/shaders_ng/vertexcolor.frag
index ede283be0c..314a387922 100644
--- a/src/quick/scenegraph/shaders_ng/vertexcolor.frag
+++ b/src/quick/scenegraph/shaders_ng/vertexcolor.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec4 color;
diff --git a/src/quick/scenegraph/shaders_ng/vertexcolor.vert b/src/quick/scenegraph/shaders_ng/vertexcolor.vert
index bfb9a95073..792f663b86 100644
--- a/src/quick/scenegraph/shaders_ng/vertexcolor.vert
+++ b/src/quick/scenegraph/shaders_ng/vertexcolor.vert
@@ -6,14 +6,20 @@ layout(location = 1) in vec4 vertexColor;
layout(location = 0) out vec4 color;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
float opacity;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+};
void main()
{
- gl_Position = ubuf.matrix * vertexCoord;
- color = vertexColor * ubuf.opacity;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = matrix[gl_ViewIndex] * vertexCoord;
+#else
+ gl_Position = matrix * vertexCoord;
+#endif
+ color = vertexColor * opacity;
}
diff --git a/src/quick/scenegraph/shaders_ng/visualization.frag b/src/quick/scenegraph/shaders_ng/visualization.frag
index 29f718fe5d..378afc2088 100644
--- a/src/quick/scenegraph/shaders_ng/visualization.frag
+++ b/src/quick/scenegraph/shaders_ng/visualization.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 pos;
diff --git a/src/quick/scenegraph/util/qquadpath.cpp b/src/quick/scenegraph/util/qquadpath.cpp
new file mode 100644
index 0000000000..457b864d5c
--- /dev/null
+++ b/src/quick/scenegraph/util/qquadpath.cpp
@@ -0,0 +1,951 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qquadpath_p.h"
+
+#include <private/qsgcurveprocessor_p.h>
+
+#include <QtGui/private/qbezier_p.h>
+#include <QtMath>
+#include <QtCore/QLoggingCategory>
+#include <QtCore/QVarLengthArray>
+
+QT_BEGIN_NAMESPACE
+
+static qreal qt_scoreQuadratic(const QBezier &b, QPointF qcp)
+{
+ static bool init = false;
+ const int numSteps = 21;
+ Q_STATIC_ASSERT(numSteps % 2 == 1); // numTries must be odd
+ static qreal t2s[numSteps];
+ static qreal tmts[numSteps];
+ if (!init) {
+ // Precompute bezier factors
+ qreal t = 0.20;
+ const qreal step = (1 - (2 * t)) / (numSteps - 1);
+ for (int i = 0; i < numSteps; i++) {
+ t2s[i] = t * t;
+ tmts[i] = 2 * t * (1 - t);
+ t += step;
+ }
+ init = true;
+ }
+
+ const QPointF midPoint = b.midPoint();
+ auto distForIndex = [&](int i) -> qreal {
+ QPointF qp = (t2s[numSteps - 1 - i] * b.pt1()) + (tmts[i] * qcp) + (t2s[i] * b.pt4());
+ QPointF d = midPoint - qp;
+ return QPointF::dotProduct(d, d);
+ };
+
+ const int halfSteps = (numSteps - 1) / 2;
+ bool foundIt = false;
+ const qreal centerDist = distForIndex(halfSteps);
+ qreal minDist = centerDist;
+ // Search for the minimum in right half
+ for (int i = 0; i < halfSteps; i++) {
+ qreal tDist = distForIndex(halfSteps + 1 + i);
+ if (tDist < minDist) {
+ minDist = tDist;
+ } else {
+ foundIt = (i > 0);
+ break;
+ }
+ }
+ if (!foundIt) {
+ // Search in left half
+ minDist = centerDist;
+ for (int i = 0; i < halfSteps; i++) {
+ qreal tDist = distForIndex(halfSteps - 1 - i);
+ if (tDist < minDist) {
+ minDist = tDist;
+ } else {
+ foundIt = (i > 0);
+ break;
+ }
+ }
+ }
+ return foundIt ? minDist : centerDist;
+}
+
+static QPointF qt_quadraticForCubic(const QBezier &b)
+{
+ const QLineF st = b.startTangent();
+ const QLineF et = b.endTangent();
+ const QPointF midPoint = b.midPoint();
+ bool valid = true;
+ QPointF quadControlPoint;
+ if (st.intersects(et, &quadControlPoint) == QLineF::NoIntersection) {
+ valid = false;
+ } else {
+ // Check if intersection is on wrong side
+ const QPointF bl = b.pt4() - b.pt1();
+ const QPointF ml = midPoint - b.pt1();
+ const QPointF ql = quadControlPoint - b.pt1();
+ qreal cx1 = (ml.x() * bl.y()) - (ml.y() * bl.x());
+ qreal cx2 = (ql.x() * bl.y()) - (ql.y() * bl.x());
+ valid = (std::signbit(cx1) == std::signbit(cx2));
+ }
+ return valid ? quadControlPoint : midPoint;
+}
+
+static int qt_getInflectionPoints(const QBezier &orig, qreal *tpoints)
+{
+ auto isValidRoot = [](qreal r) {
+ return qIsFinite(r) && (r > 0) && (!qFuzzyIsNull(float(r))) && (r < 1)
+ && (!qFuzzyIsNull(float(r - 1)));
+ };
+
+ // normalize so pt1.x,pt1.y,pt4.y == 0
+ QTransform xf;
+ const QLineF l(orig.pt1(), orig.pt4());
+ xf.rotate(l.angle());
+ xf.translate(-orig.pt1().x(), -orig.pt1().y());
+ const QBezier n = orig.mapBy(xf);
+
+ const qreal x2 = n.pt2().x();
+ const qreal x3 = n.pt3().x();
+ const qreal x4 = n.pt4().x();
+ const qreal y2 = n.pt2().y();
+ const qreal y3 = n.pt3().y();
+
+ const qreal p = x3 * y2;
+ const qreal q = x4 * y2;
+ const qreal r = x2 * y3;
+ const qreal s = x4 * y3;
+
+ const qreal a = 18 * ((-3 * p) + (2 * q) + (3 * r) - s);
+ if (qFuzzyIsNull(float(a))) {
+ if (std::signbit(y2) != std::signbit(y3) && qFuzzyCompare(float(x4 - x3), float(x2))) {
+ tpoints[0] = 0.5; // approx
+ return 1;
+ } else if (!a) {
+ return 0;
+ }
+ }
+ const qreal b = 18 * (((3 * p) - q) - (3 * r));
+ const qreal c = 18 * (r - p);
+ const qreal rad = (b * b) - (4 * a * c);
+ if (rad < 0)
+ return 0;
+ const qreal sqr = qSqrt(rad);
+ const qreal root1 = (-b + sqr) / (2 * a);
+ const qreal root2 = (-b - sqr) / (2 * a);
+
+ int res = 0;
+ if (isValidRoot(root1))
+ tpoints[res++] = root1;
+ if (root2 != root1 && isValidRoot(root2))
+ tpoints[res++] = root2;
+
+ if (res == 2 && tpoints[0] > tpoints[1])
+ qSwap(tpoints[0], tpoints[1]);
+
+ return res;
+}
+
+static void qt_addToQuadratics(const QBezier &b, QPolygonF *p, int maxSplits, qreal maxDiff)
+{
+ QPointF qcp = qt_quadraticForCubic(b);
+ if (maxSplits <= 0 || qt_scoreQuadratic(b, qcp) < maxDiff) {
+ p->append(qcp);
+ p->append(b.pt4());
+ } else {
+ QBezier rhs = b;
+ QBezier lhs;
+ rhs.parameterSplitLeft(0.5, &lhs);
+ qt_addToQuadratics(lhs, p, maxSplits - 1, maxDiff);
+ qt_addToQuadratics(rhs, p, maxSplits - 1, maxDiff);
+ }
+}
+
+static void qt_toQuadratics(const QBezier &b, QPolygonF *out, qreal errorLimit = 0.01)
+{
+ out->resize(0);
+ out->append(b.pt1());
+
+ {
+ // Shortcut if the cubic is really a quadratic
+ const qreal f = 3.0 / 2.0;
+ const QPointF c1 = b.pt1() + f * (b.pt2() - b.pt1());
+ const QPointF c2 = b.pt4() + f * (b.pt3() - b.pt4());
+ if (c1 == c2) {
+ out->append(c1);
+ out->append(b.pt4());
+ return;
+ }
+ }
+
+ const QRectF cpr = b.bounds();
+ const QPointF dim = cpr.bottomRight() - cpr.topLeft();
+ qreal maxDiff = QPointF::dotProduct(dim, dim) * errorLimit * errorLimit; // maxdistance^2
+
+ qreal infPoints[2];
+ int numInfPoints = qt_getInflectionPoints(b, infPoints);
+ const int maxSubSplits = numInfPoints > 0 ? 2 : 3;
+ qreal t0 = 0;
+ // number of main segments == #inflectionpoints + 1
+ for (int i = 0; i < numInfPoints + 1; i++) {
+ qreal t1 = (i < numInfPoints) ? infPoints[i] : 1;
+ QBezier segment = b.bezierOnInterval(t0, t1);
+ qt_addToQuadratics(segment, out, maxSubSplits, maxDiff);
+ t0 = t1;
+ }
+}
+
+QVector2D QQuadPath::Element::pointAtFraction(float t) const
+{
+ if (isLine()) {
+ return sp + t * (ep - sp);
+ } else {
+ const float r = 1 - t;
+ return (r * r * sp) + (2 * t * r * cp) + (t * t * ep);
+ }
+}
+
+QQuadPath::Element QQuadPath::Element::segmentFromTo(float t0, float t1) const
+{
+ if (t0 <= 0 && t1 >= 1)
+ return *this;
+
+ Element part;
+ part.sp = pointAtFraction(t0);
+ part.ep = pointAtFraction(t1);
+
+ if (isLine()) {
+ part.cp = 0.5f * (part.sp + part.ep);
+ part.m_isLine = true;
+ } else {
+ // Split curve right at t0, yields { t0, rcp, endPoint } quad segment
+ const QVector2D rcp = (1 - t0) * controlPoint() + t0 * endPoint();
+ // Split that left at t1, yields { t0, lcp, t1 } quad segment
+ float segmentT = (t1 - t0) / (1 - t0);
+ part.cp = (1 - segmentT) * part.sp + segmentT * rcp;
+ }
+ return part;
+}
+
+QQuadPath::Element QQuadPath::Element::reversed() const {
+ Element swappedElement;
+ swappedElement.ep = sp;
+ swappedElement.cp = cp;
+ swappedElement.sp = ep;
+ swappedElement.m_isLine = m_isLine;
+ return swappedElement;
+}
+
+float QQuadPath::Element::extent() const
+{
+ // TBD: cache this value if we start using it a lot
+ QVector2D min(qMin(sp.x(), ep.x()), qMin(sp.y(), ep.y()));
+ QVector2D max(qMax(sp.x(), ep.x()), qMax(sp.y(), ep.y()));
+ if (!isLine()) {
+ min = QVector2D(qMin(min.x(), cp.x()), qMin(min.y(), cp.y()));
+ max = QVector2D(qMax(max.x(), cp.x()), qMax(max.y(), cp.y()));
+ }
+ return (max - min).length();
+}
+
+// Returns the number of intersections between element and a horizontal line at y.
+// The t values of max 2 intersection(s) are stored in the fractions array
+int QQuadPath::Element::intersectionsAtY(float y, float *fractions, bool swapXY) const
+{
+ Q_ASSERT(!isLine());
+
+ auto getY = [=](QVector2D p) -> float { return swapXY ? -p.x() : p.y(); };
+
+ const float y0 = getY(startPoint()) - y;
+ const float y1 = getY(controlPoint()) - y;
+ const float y2 = getY(endPoint()) - y;
+
+ int numRoots = 0;
+ const float a = y0 - (2 * y1) + y2;
+ if (a) {
+ const float b = (y1 * y1) - (y0 * y2);
+ if (b >= 0) {
+ const float sqr = qSqrt(b);
+ const float root1 = -(-y0 + y1 + sqr) / a;
+ if (qIsFinite(root1) && root1 >= 0 && root1 <= 1)
+ fractions[numRoots++] = root1;
+ const float root2 = (y0 - y1 + sqr) / a;
+ if (qIsFinite(root2) && root2 != root1 && root2 >= 0 && root2 <= 1)
+ fractions[numRoots++] = root2;
+ }
+ } else if (y1 != y2) {
+ const float root1 = (y2 - (2 * y1)) / (2 * (y2 - y1));
+ if (qIsFinite(root1) && root1 >= 0 && root1 <= 1)
+ fractions[numRoots++] = root1;
+ }
+
+ return numRoots;
+}
+
+static float crossProduct(const QVector2D &sp, const QVector2D &p, const QVector2D &ep)
+{
+ QVector2D v1 = ep - sp;
+ QVector2D v2 = p - sp;
+ return (v2.x() * v1.y()) - (v2.y() * v1.x());
+}
+
+bool QQuadPath::isPointOnLeft(const QVector2D &p, const QVector2D &sp, const QVector2D &ep)
+{
+ // Use cross product to compare directions of base vector and vector from start to p
+ return crossProduct(sp, p, ep) >= 0.0f;
+}
+
+bool QQuadPath::isPointOnLine(const QVector2D &p, const QVector2D &sp, const QVector2D &ep)
+{
+ return qFuzzyIsNull(crossProduct(sp, p, ep));
+}
+
+// Assumes sp != ep
+bool QQuadPath::isPointNearLine(const QVector2D &p, const QVector2D &sp, const QVector2D &ep)
+{
+ // epsilon is max length of p-to-baseline relative to length of baseline. So 0.01 means that
+ // the distance from p to the baseline must be less than 1% of the length of the baseline.
+ constexpr float epsilon = 0.01f;
+ QVector2D bv = ep - sp;
+ float bl2 = QVector2D::dotProduct(bv, bv);
+ float t = QVector2D::dotProduct(p - sp, bv) / bl2;
+ QVector2D pv = p - (sp + t * bv);
+ return (QVector2D::dotProduct(pv, pv) / bl2) < (epsilon * epsilon);
+}
+
+QVector2D QQuadPath::closestPointOnLine(const QVector2D &p, const QVector2D &sp, const QVector2D &ep)
+{
+ QVector2D line = ep - sp;
+ float t = QVector2D::dotProduct(p - sp, line) / QVector2D::dotProduct(line, line);
+ return sp + qBound(0.0f, t, 1.0f) * line;
+}
+
+// NOTE: it is assumed that subpaths are closed
+bool QQuadPath::contains(const QVector2D &point) const
+{
+ return contains(point, 0, elementCount() - 1);
+}
+
+bool QQuadPath::contains(const QVector2D &point, int fromIndex, int toIndex) const
+{
+ // if (!controlPointRect().contains(pt) : good opt when we add cpr caching
+ // return false;
+
+ int winding_number = 0;
+ for (int ei = fromIndex; ei <= toIndex; ei++) {
+ const Element &e = m_elements.at(ei);
+ int dir = 1;
+ float y1 = e.startPoint().y();
+ float y2 = e.endPoint().y();
+ if (y2 < y1) {
+ qSwap(y1, y2);
+ dir = -1;
+ }
+ if (e.m_isLine) {
+ if (point.y() < y1 || point.y() >= y2 || y1 == y2)
+ continue;
+ const float t = (point.y() - e.startPoint().y()) / (e.endPoint().y() - e.startPoint().y());
+ const float x = e.startPoint().x() + t * (e.endPoint().x() - e.startPoint().x());
+ if (x <= point.x())
+ winding_number += dir;
+ } else {
+ y1 = qMin(y1, e.controlPoint().y());
+ y2 = qMax(y2, e.controlPoint().y());
+ if (point.y() < y1 || point.y() >= y2)
+ continue;
+ float ts[2];
+ const int numRoots = e.intersectionsAtY(point.y(), ts);
+ // Count if there is exactly one intersection to the left
+ bool oneHit = false;
+ float tForHit = -1;
+ for (int i = 0; i < numRoots; i++) {
+ if (e.pointAtFraction(ts[i]).x() <= point.x()) {
+ oneHit = !oneHit;
+ tForHit = ts[i];
+ }
+ }
+ if (oneHit) {
+ dir = e.tangentAtFraction(tForHit).y() < 0 ? -1 : 1;
+ winding_number += dir;
+ }
+ }
+ };
+
+ return (fillRule() == Qt::WindingFill ? (winding_number != 0) : ((winding_number % 2) != 0));
+}
+
+// similar as contains. But we treat the element with the index elementIdx in a special way
+// that should be numerically more stable. The result is a contains for a point on the left
+// and for the right side of the element.
+QQuadPath::Element::FillSide QQuadPath::fillSideOf(int elementIdx, float elementT) const
+{
+ constexpr float toleranceT = 1e-3f;
+ const QVector2D point = m_elements.at(elementIdx).pointAtFraction(elementT);
+ const QVector2D tangent = m_elements.at(elementIdx).tangentAtFraction(elementT);
+
+ const bool swapXY = qAbs(tangent.x()) > qAbs(tangent.y());
+ auto getX = [=](QVector2D p) -> float { return swapXY ? p.y() : p.x(); };
+ auto getY = [=](QVector2D p) -> float { return swapXY ? -p.x() : p.y(); };
+
+ int winding_number = 0;
+ for (int i = 0; i < elementCount(); i++) {
+ const Element &e = m_elements.at(i);
+ int dir = 1;
+ float y1 = getY(e.startPoint());
+ float y2 = getY(e.endPoint());
+ if (y2 < y1) {
+ qSwap(y1, y2);
+ dir = -1;
+ }
+ if (e.m_isLine) {
+ if (getY(point) < y1 || getY(point) >= y2 || y1 == y2)
+ continue;
+ const float t = (getY(point) - getY(e.startPoint())) / (getY(e.endPoint()) - getY(e.startPoint()));
+ const float x = getX(e.startPoint()) + t * (getX(e.endPoint()) - getX(e.startPoint()));
+ if (x <= getX(point) && (i != elementIdx || qAbs(t - elementT) > toleranceT))
+ winding_number += dir;
+ } else {
+ y1 = qMin(y1, getY(e.controlPoint()));
+ y2 = qMax(y2, getY(e.controlPoint()));
+ if (getY(point) < y1 || getY(point) >= y2)
+ continue;
+ float ts[2];
+ const int numRoots = e.intersectionsAtY(getY(point), ts, swapXY);
+ // Count if there is exactly one intersection to the left
+ bool oneHit = false;
+ float tForHit = -1;
+ for (int j = 0; j < numRoots; j++) {
+ const float x = getX(e.pointAtFraction(ts[j]));
+ if (x <= getX(point) && (i != elementIdx || qAbs(ts[j] - elementT) > toleranceT)) {
+ oneHit = !oneHit;
+ tForHit = ts[j];
+ }
+ }
+ if (oneHit) {
+ dir = getY(e.tangentAtFraction(tForHit)) < 0 ? -1 : 1;
+ winding_number += dir;
+ }
+ }
+ };
+
+ int left_winding_number = winding_number;
+ int right_winding_number = winding_number;
+
+ int dir = getY(tangent) < 0 ? -1 : 1;
+
+ if (dir > 0)
+ left_winding_number += dir;
+ else
+ right_winding_number += dir;
+
+ bool leftInside = (fillRule() == Qt::WindingFill ? (left_winding_number != 0) : ((left_winding_number % 2) != 0));
+ bool rightInside = (fillRule() == Qt::WindingFill ? (right_winding_number != 0) : ((right_winding_number % 2) != 0));
+
+ if (leftInside && rightInside)
+ return QQuadPath::Element::FillSideBoth;
+ else if (leftInside)
+ return QQuadPath::Element::FillSideLeft;
+ else if (rightInside)
+ return QQuadPath::Element::FillSideRight;
+ else
+ return QQuadPath::Element::FillSideUndetermined; //should not happen except for numerical error.
+}
+
+void QQuadPath::addElement(const QVector2D &control, const QVector2D &endPoint, bool isLine)
+{
+ if (qFuzzyCompare(m_currentPoint, endPoint))
+ return; // 0 length element, skip
+
+ isLine = isLine || isPointNearLine(control, m_currentPoint, endPoint); // Turn flat quad into line
+
+ m_elements.resize(m_elements.size() + 1);
+ Element &elem = m_elements.last();
+ elem.sp = m_currentPoint;
+ elem.cp = isLine ? (0.5f * (m_currentPoint + endPoint)) : control;
+ elem.ep = endPoint;
+ elem.m_isLine = isLine;
+ elem.m_isSubpathStart = m_subPathToStart;
+ m_subPathToStart = false;
+ m_currentPoint = endPoint;
+}
+
+void QQuadPath::addElement(const Element &e)
+{
+ m_subPathToStart = false;
+ m_currentPoint = e.endPoint();
+ m_elements.append(e);
+}
+
+#if !defined(QQUADPATH_CONVEX_CHECK_ERROR_MARGIN)
+# define QQUICKSHAPECURVERENDERER_CONVEX_CHECK_ERROR_MARGIN (1.0f / 32.0f)
+#endif
+
+QQuadPath::Element::CurvatureFlags QQuadPath::coordinateOrderOfElement(const QQuadPath::Element &element) const
+{
+ QVector2D baseLine = element.endPoint() - element.startPoint();
+ QVector2D midPoint = element.midPoint();
+ // At the midpoint, the tangent of a quad is parallel to the baseline
+ QVector2D normal = QVector2D(-baseLine.y(), baseLine.x()).normalized();
+ float delta = qMin(element.extent() / 100, QQUICKSHAPECURVERENDERER_CONVEX_CHECK_ERROR_MARGIN);
+ QVector2D justRightOfMid = midPoint + (normal * delta);
+ bool pathContainsPoint = contains(justRightOfMid);
+ return pathContainsPoint ? Element::FillOnRight : Element::CurvatureFlags(0);
+}
+
+QQuadPath QQuadPath::fromPainterPath(const QPainterPath &path, PathHints hints)
+{
+ QQuadPath res;
+ res.reserve(path.elementCount());
+ res.setFillRule(path.fillRule());
+
+ const bool isQuadratic = hints & PathQuadratic;
+
+ QPolygonF quads;
+ QPointF sp;
+ for (int i = 0; i < path.elementCount(); ++i) {
+ QPainterPath::Element element = path.elementAt(i);
+
+ QPointF ep(element);
+ switch (element.type) {
+ case QPainterPath::MoveToElement:
+ res.moveTo(QVector2D(ep));
+ break;
+ case QPainterPath::LineToElement:
+ res.lineTo(QVector2D(ep));
+ break;
+ case QPainterPath::CurveToElement: {
+ QPointF cp1 = ep;
+ QPointF cp2(path.elementAt(++i));
+ ep = path.elementAt(++i);
+ if (isQuadratic) {
+ const qreal f = 3.0 / 2.0;
+ const QPointF cp = sp + f * (cp1 - sp);
+ res.quadTo(QVector2D(cp), QVector2D(ep));
+ } else {
+ QBezier b = QBezier::fromPoints(sp, cp1, cp2, ep);
+ qt_toQuadratics(b, &quads);
+ for (int i = 1; i < quads.size(); i += 2) {
+ QVector2D cp(quads[i]);
+ QVector2D ep(quads[i + 1]);
+ res.quadTo(cp, ep);
+ }
+ }
+ break;
+ }
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ sp = ep;
+ }
+
+ res.setPathHints(hints | PathQuadratic);
+ return res;
+}
+
+void QQuadPath::addCurvatureData()
+{
+ // We use the convention that the inside of a curve is on the *right* side of the
+ // direction of the baseline.Thus, as long as this is true: if the control point is
+ // on the left side of the baseline, the curve is convex and otherwise it is
+ // concave. The paths we get can be arbitrary order, but each subpath will have a
+ // consistent order. Therefore, for the first curve element in a subpath, we can
+ // determine if the direction already follows the convention or not, and then we
+ // can easily detect curvature of all subsequent elements in the subpath.
+
+ static bool checkAnomaly = qEnvironmentVariableIntValue("QT_QUICKSHAPES_CHECK_ALL_CURVATURE") != 0;
+ const bool pathHasFillOnRight = testHint(PathFillOnRight);
+
+ Element::CurvatureFlags flags = Element::CurvatureUndetermined;
+ for (QQuadPath::Element &element : m_elements) {
+ Q_ASSERT(element.childCount() == 0);
+ if (element.isSubpathStart()) {
+ if (pathHasFillOnRight && !checkAnomaly)
+ flags = Element::FillOnRight;
+ else
+ flags = coordinateOrderOfElement(element);
+ } else if (checkAnomaly) {
+ Element::CurvatureFlags newFlags = coordinateOrderOfElement(element);
+ if (flags != newFlags) {
+ qDebug() << "Curvature anomaly detected:" << element
+ << "Subpath fill on right:" << (flags & Element::FillOnRight)
+ << "Element fill on right:" << (newFlags & Element::FillOnRight);
+ flags = newFlags;
+ }
+ }
+
+ if (element.isLine()) {
+ element.m_curvatureFlags = flags;
+ } else {
+ bool controlPointOnLeft = element.isControlPointOnLeft();
+ bool isFillOnRight = flags & Element::FillOnRight;
+ bool isConvex = controlPointOnLeft == isFillOnRight;
+
+ if (isConvex)
+ element.m_curvatureFlags = Element::CurvatureFlags(flags | Element::Convex);
+ else
+ element.m_curvatureFlags = flags;
+ }
+ }
+}
+
+QRectF QQuadPath::controlPointRect() const
+{
+ QRectF res;
+ if (elementCount()) {
+ QVector2D min, max;
+ min = max = m_elements.constFirst().sp;
+ // No need to recurse, as split curve's controlpoints are within the parent curve's
+ for (const QQuadPath::Element &e : std::as_const(m_elements)) {
+ min.setX(std::min({ min.x(), e.sp.x(), e.cp.x(), e.ep.x() }));
+ min.setY(std::min({ min.y(), e.sp.y(), e.cp.y(), e.ep.y() }));
+ max.setX(std::max({ max.x(), e.sp.x(), e.cp.x(), e.ep.x() }));
+ max.setY(std::max({ max.y(), e.sp.y(), e.cp.y(), e.ep.y() }));
+ }
+ res = QRectF(min.toPointF(), max.toPointF());
+ }
+ return res;
+}
+
+// Count leaf elements
+int QQuadPath::elementCountRecursive() const
+{
+ int count = 0;
+ iterateElements([&](const QQuadPath::Element &, int) { count++; });
+ return count;
+}
+
+QPainterPath QQuadPath::toPainterPath() const
+{
+ // Currently only converts the main, unsplit path; no need for the split path identified
+ QPainterPath res;
+ res.reserve(elementCount());
+ res.setFillRule(fillRule());
+ for (const Element &element : m_elements) {
+ if (element.m_isSubpathStart)
+ res.moveTo(element.startPoint().toPointF());
+ if (element.m_isLine)
+ res.lineTo(element.endPoint().toPointF());
+ else
+ res.quadTo(element.controlPoint().toPointF(), element.endPoint().toPointF());
+ };
+ return res;
+}
+
+QString QQuadPath::asSvgString() const
+{
+ QString res;
+ QTextStream str(&res);
+ for (const Element &element : m_elements) {
+ if (element.isSubpathStart())
+ str << "M " << element.startPoint().x() << " " << element.startPoint().y() << " ";
+ if (element.isLine())
+ str << "L " << element.endPoint().x() << " " << element.endPoint().y() << " ";
+ else
+ str << "Q " << element.controlPoint().x() << " " << element.controlPoint().y() << " "
+ << element.endPoint().x() << " " << element.endPoint().y() << " ";
+ }
+ return res;
+}
+
+// Returns a new path since doing it inline would probably be less efficient
+// (technically changing it from O(n) to O(n^2))
+// Note that this function should be called before splitting any elements,
+// so we can assume that the structure is a list and not a tree
+QQuadPath QQuadPath::subPathsClosed(bool *didClose) const
+{
+ Q_ASSERT(m_childElements.isEmpty());
+ bool closed = false;
+ QQuadPath res = *this;
+ res.m_subPathToStart = false;
+ res.m_elements = {};
+ res.m_elements.reserve(elementCount());
+ int subStart = -1;
+ int prevElement = -1;
+ for (int i = 0; i < elementCount(); i++) {
+ const auto &element = m_elements.at(i);
+ if (element.m_isSubpathStart) {
+ if (subStart >= 0 && m_elements[i - 1].ep != m_elements[subStart].sp) {
+ res.m_currentPoint = m_elements[i - 1].ep;
+ res.lineTo(m_elements[subStart].sp);
+ closed = true;
+ auto &endElement = res.m_elements.last();
+ endElement.m_isSubpathEnd = true;
+ // lineTo() can bail out if the points are too close.
+ // In that case, just change the end point to be equal to the start
+ // (No need to test because the assignment is a no-op otherwise).
+ endElement.ep = m_elements[subStart].sp;
+ } else if (prevElement >= 0) {
+ res.m_elements[prevElement].m_isSubpathEnd = true;
+ }
+ subStart = i;
+ }
+ res.m_elements.append(element);
+ prevElement = res.m_elements.size() - 1;
+ }
+
+ if (subStart >= 0 && m_elements.last().ep != m_elements[subStart].sp) {
+ res.m_currentPoint = m_elements.last().ep;
+ res.lineTo(m_elements[subStart].sp);
+ closed = true;
+ }
+ if (!res.m_elements.isEmpty()) {
+ auto &endElement = res.m_elements.last();
+ endElement.m_isSubpathEnd = true;
+ endElement.ep = m_elements[subStart].sp;
+ }
+
+ if (didClose)
+ *didClose = closed;
+ return res;
+}
+
+QQuadPath QQuadPath::flattened() const
+{
+ QQuadPath res;
+ res.reserve(elementCountRecursive());
+ iterateElements([&](const QQuadPath::Element &elem, int) { res.m_elements.append(elem); });
+ res.setPathHints(pathHints());
+ res.setFillRule(fillRule());
+ return res;
+}
+
+class ElementCutter
+{
+public:
+ ElementCutter(const QQuadPath::Element &element)
+ : m_element(element)
+ {
+ m_currentPoint = m_element.startPoint();
+ if (m_element.isLine())
+ m_lineLength = (m_element.endPoint() - m_element.startPoint()).length();
+ else
+ fillLUT();
+ }
+
+ bool consume(float length)
+ {
+ m_lastT = m_currentT;
+ m_lastPoint = m_currentPoint;
+ float nextCut = m_consumed + length;
+ float cutT = m_element.isLine() ? nextCut / m_lineLength : tForLength(nextCut);
+ if (cutT < 1) {
+ m_currentT = cutT;
+ m_currentPoint = m_element.pointAtFraction(m_currentT);
+ m_consumed = nextCut;
+ return true;
+ } else {
+ m_currentT = 1;
+ m_currentPoint = m_element.endPoint();
+ return false;
+ }
+ }
+
+ QVector2D currentCutPoint()
+ {
+ return m_currentPoint;
+ }
+
+ QVector2D currentControlPoint()
+ {
+ Q_ASSERT(!m_element.isLine());
+ // Split curve right at lastT, yields { lastPoint, rcp, endPoint } quad segment
+ QVector2D rcp = (1 - m_lastT) * m_element.controlPoint() + m_lastT * m_element.endPoint();
+ // Split that left at currentT, yields { lastPoint, lcp, currentPoint } quad segment
+ float segmentT = (m_currentT - m_lastT) / (1 - m_lastT);
+ QVector2D lcp = (1 - segmentT) * m_lastPoint + segmentT * rcp;
+ return lcp;
+ }
+
+ float lastLength()
+ {
+ float elemLength = m_element.isLine() ? m_lineLength : m_lut.last();
+ return elemLength - m_consumed;
+ }
+
+private:
+ void fillLUT()
+ {
+ Q_ASSERT(!m_element.isLine());
+ QVector2D ap = m_element.startPoint() - 2 * m_element.controlPoint() + m_element.endPoint();
+ QVector2D bp = 2 * m_element.controlPoint() - 2 * m_element.startPoint();
+ float A = 4 * QVector2D::dotProduct(ap, ap);
+ float B = 4 * QVector2D::dotProduct(ap, bp);
+ float C = QVector2D::dotProduct(bp, bp);
+ float b = B / (2 * A);
+ float c = C / A;
+ float k = c - (b * b);
+ float l2 = b * std::sqrt(b * b + k);
+ float lnom = b + std::sqrt(b * b + k);
+ float l0 = 0.5f * std::sqrt(A);
+
+ m_lut.resize(LUTSize, 0);
+ for (int i = 1; i < LUTSize; i++) {
+ float t = float(i) / (LUTSize - 1);
+ float u = t + b;
+ float w = std::sqrt(u * u + k);
+ float l1 = u * w;
+ float lden = u + w;
+ float l3 = k * std::log(std::fabs(lden / lnom));
+ float res = l0 * (l1 - l2 + l3);
+ m_lut[i] = res;
+ }
+ }
+
+ float tForLength(float length)
+ {
+ Q_ASSERT(!m_element.isLine());
+ Q_ASSERT(!m_lut.isEmpty());
+
+ float res = 2; // I.e. invalid, outside [0, 1] range
+ auto it = std::upper_bound(m_lut.cbegin(), m_lut.cend(), length);
+ if (it != m_lut.cend()) {
+ float nextLength = *it--;
+ float prevLength = *it;
+ int prevIndex = std::distance(m_lut.cbegin(), it);
+ float fraction = (length - prevLength) / (nextLength - prevLength);
+ res = (prevIndex + fraction) / (LUTSize - 1);
+ }
+ return res;
+ }
+
+ const QQuadPath::Element &m_element;
+ float m_lastT = 0;
+ float m_currentT = 0;
+ QVector2D m_lastPoint;
+ QVector2D m_currentPoint;
+ float m_consumed = 0;
+ // For line elements:
+ float m_lineLength;
+ // For quadratic curve elements:
+ static constexpr int LUTSize = 21;
+ QVarLengthArray<float, LUTSize> m_lut;
+};
+
+QQuadPath QQuadPath::dashed(qreal lineWidth, const QList<qreal> &dashPattern, qreal dashOffset) const
+{
+ QVarLengthArray<float, 16> pattern;
+ float patternLength = 0;
+ for (int i = 0; i < 2 * (dashPattern.length() / 2); i++) {
+ float dashLength = qMax(lineWidth * dashPattern[i], qreal(0));
+ pattern.append(dashLength);
+ patternLength += dashLength;
+ }
+ if (patternLength == 0)
+ return {};
+
+ int startIndex = 0;
+ float startOffset = std::fmod(lineWidth * dashOffset, patternLength);
+ if (startOffset < 0)
+ startOffset += patternLength;
+ for (float dashLength : pattern) {
+ if (dashLength > startOffset)
+ break;
+ startIndex = (startIndex + 1) % pattern.size(); // The % guards against accuracy issues
+ startOffset -= dashLength;
+ }
+
+ int dashIndex = startIndex;
+ float offset = startOffset;
+ QQuadPath res;
+ for (int i = 0; i < elementCount(); i++) {
+ const Element &element = elementAt(i);
+ if (element.isSubpathStart()) {
+ res.moveTo(element.startPoint());
+ dashIndex = startIndex;
+ offset = startOffset;
+ }
+ ElementCutter cutter(element);
+ while (true) {
+ bool gotAll = cutter.consume(pattern.at(dashIndex) - offset);
+ QVector2D nextPoint = cutter.currentCutPoint();
+ if (dashIndex & 1)
+ res.moveTo(nextPoint); // gap
+ else if (element.isLine())
+ res.lineTo(nextPoint); // dash in line
+ else
+ res.quadTo(cutter.currentControlPoint(), nextPoint); // dash in curve
+ if (gotAll) {
+ offset = 0;
+ dashIndex = (dashIndex + 1) % pattern.size();
+ } else {
+ offset += cutter.lastLength();
+ break;
+ }
+ }
+ }
+ res.setFillRule(fillRule());
+ res.setPathHints(pathHints());
+ return res;
+}
+
+void QQuadPath::splitElementAt(int index)
+{
+ const int newChildIndex = m_childElements.size();
+ m_childElements.resize(newChildIndex + 2);
+ Element &parent = elementAt(index);
+ parent.m_numChildren = 2;
+ parent.m_firstChildIndex = newChildIndex;
+
+ Element &quad1 = m_childElements[newChildIndex];
+ const QVector2D mp = parent.midPoint();
+ quad1.sp = parent.sp;
+ quad1.cp = 0.5f * (parent.sp + parent.cp);
+ quad1.ep = mp;
+ quad1.m_isSubpathStart = parent.m_isSubpathStart;
+ quad1.m_isSubpathEnd = false;
+ quad1.m_curvatureFlags = parent.m_curvatureFlags;
+ quad1.m_isLine = parent.m_isLine; //### || isPointNearLine(quad1.cp, quad1.sp, quad1.ep);
+
+ Element &quad2 = m_childElements[newChildIndex + 1];
+ quad2.sp = mp;
+ quad2.cp = 0.5f * (parent.ep + parent.cp);
+ quad2.ep = parent.ep;
+ quad2.m_isSubpathStart = false;
+ quad2.m_isSubpathEnd = parent.m_isSubpathEnd;
+ quad2.m_curvatureFlags = parent.m_curvatureFlags;
+ quad2.m_isLine = parent.m_isLine; //### || isPointNearLine(quad2.cp, quad2.sp, quad2.ep);
+
+#ifndef QT_NO_DEBUG
+ if (qFuzzyCompare(quad1.sp, quad1.ep) || qFuzzyCompare(quad2.sp, quad2.ep))
+ qCDebug(lcSGCurveProcessor) << "Splitting has resulted in ~null quad";
+#endif
+}
+
+static void printElement(QDebug stream, const QQuadPath::Element &element)
+{
+ auto printPoint = [&](QVector2D p) { stream << "(" << p.x() << ", " << p.y() << ") "; };
+ stream << "{ ";
+ printPoint(element.startPoint());
+ printPoint(element.controlPoint());
+ printPoint(element.endPoint());
+ stream << "} " << (element.isLine() ? "L " : "C ") << (element.isConvex() ? "X " : "O ")
+ << (element.isSubpathStart() ? "S" : element.isSubpathEnd() ? "E" : "");
+}
+
+QDebug operator<<(QDebug stream, const QQuadPath::Element &element)
+{
+ QDebugStateSaver saver(stream);
+ stream.nospace();
+ stream << "QuadPath::Element( ";
+ printElement(stream, element);
+ stream << " )";
+ return stream;
+}
+
+QDebug operator<<(QDebug stream, const QQuadPath &path)
+{
+ QDebugStateSaver saver(stream);
+ stream.nospace();
+ stream << "QuadPath(" << path.elementCount() << " main elements, "
+ << path.elementCountRecursive() << " leaf elements, "
+ << (path.fillRule() == Qt::OddEvenFill ? "OddEven" : "Winding") << Qt::endl;
+ int count = 0;
+ path.iterateElements([&](const QQuadPath::Element &e, int) {
+ stream << " " << count++ << (e.isSubpathStart() ? " >" : " ");
+ printElement(stream, e);
+ stream << Qt::endl;
+ });
+ stream << ")";
+ return stream;
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/util/qquadpath_p.h b/src/quick/scenegraph/util/qquadpath_p.h
new file mode 100644
index 0000000000..98ef5b664c
--- /dev/null
+++ b/src/quick/scenegraph/util/qquadpath_p.h
@@ -0,0 +1,341 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQUADPATH_P_H
+#define QQUADPATH_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qrect.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qdebug.h>
+#include <QtGui/qvector2d.h>
+#include <QtGui/qpainterpath.h>
+#include <QtQuick/qtquickexports.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_QUICK_EXPORT QQuadPath
+{
+public:
+ // This is a copy of the flags in QQuickShapePath ### TODO: use a common definition
+ enum PathHint : quint8 {
+ PathLinear = 0x1,
+ PathQuadratic = 0x2,
+ PathConvex = 0x4,
+ PathFillOnRight = 0x8,
+ PathSolid = 0x10,
+ PathNonIntersecting = 0x20,
+ PathNonOverlappingControlPointTriangles = 0x40
+ };
+ Q_DECLARE_FLAGS(PathHints, PathHint)
+
+ class Q_QUICK_EXPORT Element
+ {
+ public:
+ Element ()
+ : m_isSubpathStart(false), m_isSubpathEnd(false), m_isLine(false)
+ {
+ }
+
+ Element (QVector2D s, QVector2D c, QVector2D e)
+ : sp(s), cp(c), ep(e), m_isSubpathStart(false), m_isSubpathEnd(false), m_isLine(false)
+ {
+ }
+
+ bool isSubpathStart() const
+ {
+ return m_isSubpathStart;
+ }
+
+ bool isSubpathEnd() const
+ {
+ return m_isSubpathEnd;
+ }
+
+ bool isLine() const
+ {
+ return m_isLine;
+ }
+
+ bool isConvex() const
+ {
+ return m_curvatureFlags & Convex;
+ }
+
+ QVector2D startPoint() const
+ {
+ return sp;
+ }
+
+ QVector2D controlPoint() const
+ {
+ return cp;
+ }
+
+ QVector2D endPoint() const
+ {
+ return ep;
+ }
+
+ QVector2D midPoint() const
+ {
+ return isLine() ? 0.5f * (sp + ep) : (0.25f * sp) + (0.5f * cp) + (0.25 * ep);
+ }
+
+ /* For a curve, returns the control point. For a line, returns an arbitrary point on the
+ * inside side of the line (assuming the curvature has been set for the path). The point
+ * doesn't need to actually be inside the shape: it just makes for easier calculations
+ * later when it is at the same side as the fill. */
+ QVector2D referencePoint() const
+ {
+ if (isLine()) {
+ QVector2D normal(sp.y() - ep.y(), ep.x() - sp.x());
+ return m_curvatureFlags & Element::FillOnRight ? sp + normal : sp - normal;
+ } else {
+ return cp;
+ }
+ }
+
+ Element segmentFromTo(float t0, float t1) const;
+
+ Element reversed() const;
+
+ int childCount() const { return m_numChildren; }
+
+ int indexOfChild(int childNumber) const
+ {
+ Q_ASSERT(childNumber >= 0 && childNumber < childCount());
+ return -(m_firstChildIndex + 1 + childNumber);
+ }
+
+ QVector2D pointAtFraction(float t) const;
+
+ QVector2D tangentAtFraction(float t) const
+ {
+ return isLine() ? (ep - sp) : ((1 - t) * 2 * (cp - sp)) + (t * 2 * (ep - cp));
+ }
+
+ QVector2D normalAtFraction(float t) const
+ {
+ const QVector2D tan = tangentAtFraction(t);
+ return QVector2D(-tan.y(), tan.x());
+ }
+
+ float extent() const;
+
+ void setAsConvex(bool isConvex)
+ {
+ if (isConvex)
+ m_curvatureFlags = Element::CurvatureFlags(m_curvatureFlags | Element::Convex);
+ else
+ m_curvatureFlags = Element::CurvatureFlags(m_curvatureFlags & ~Element::Convex);
+ }
+
+ void setFillOnRight(bool isFillOnRight)
+ {
+ if (isFillOnRight)
+ m_curvatureFlags = Element::CurvatureFlags(m_curvatureFlags | Element::FillOnRight);
+ else
+ m_curvatureFlags = Element::CurvatureFlags(m_curvatureFlags & ~Element::FillOnRight);
+ }
+
+ bool isFillOnRight() const { return m_curvatureFlags & FillOnRight; }
+
+ bool isControlPointOnLeft() const
+ {
+ return isPointOnLeft(cp, sp, ep);
+ }
+
+ enum CurvatureFlags : quint8 {
+ CurvatureUndetermined = 0,
+ FillOnRight = 1,
+ Convex = 2
+ };
+
+ enum FillSide : quint8 {
+ FillSideUndetermined = 0,
+ FillSideRight = 1,
+ FillSideLeft = 2,
+ FillSideBoth = 3
+ };
+
+ private:
+ int intersectionsAtY(float y, float *fractions, bool swapXY = false) const;
+
+ QVector2D sp;
+ QVector2D cp;
+ QVector2D ep;
+ int m_firstChildIndex = 0;
+ quint8 m_numChildren = 0;
+ CurvatureFlags m_curvatureFlags = CurvatureUndetermined;
+ quint8 m_isSubpathStart : 1;
+ quint8 m_isSubpathEnd : 1;
+ quint8 m_isLine : 1;
+ friend class QQuadPath;
+ friend Q_QUICK_EXPORT QDebug operator<<(QDebug, const QQuadPath::Element &);
+ };
+
+ void moveTo(const QVector2D &to)
+ {
+ m_subPathToStart = true;
+ m_currentPoint = to;
+ }
+
+ void lineTo(const QVector2D &to)
+ {
+ addElement({}, to, true);
+ }
+
+ void quadTo(const QVector2D &control, const QVector2D &to)
+ {
+ addElement(control, to);
+ }
+
+ Element &elementAt(int i)
+ {
+ return i < 0 ? m_childElements[-(i + 1)] : m_elements[i];
+ }
+
+ const Element &elementAt(int i) const
+ {
+ return i < 0 ? m_childElements[-(i + 1)] : m_elements[i];
+ }
+
+ int indexOfChildAt(int i, int childNumber) const
+ {
+ return elementAt(i).indexOfChild(childNumber);
+ }
+
+ QRectF controlPointRect() const;
+
+ Qt::FillRule fillRule() const { return m_windingFill ? Qt::WindingFill : Qt::OddEvenFill; }
+ void setFillRule(Qt::FillRule rule) { m_windingFill = (rule == Qt::WindingFill); }
+
+ void reserve(int size) { m_elements.reserve(size); }
+ int elementCount() const { return m_elements.size(); }
+ bool isEmpty() const { return m_elements.size() == 0; }
+ int elementCountRecursive() const;
+
+ static QQuadPath fromPainterPath(const QPainterPath &path, PathHints hints = {});
+ QPainterPath toPainterPath() const;
+ QString asSvgString() const;
+
+ QQuadPath subPathsClosed(bool *didClose = nullptr) const;
+ void addCurvatureData();
+ QQuadPath flattened() const;
+ QQuadPath dashed(qreal lineWidth, const QList<qreal> &dashPattern, qreal dashOffset = 0) const;
+ void splitElementAt(int index);
+ bool contains(const QVector2D &point) const;
+ bool contains(const QVector2D &point, int fromIndex, int toIndex) const;
+ Element::FillSide fillSideOf(int elementIdx, float elementT) const;
+
+ template<typename Func>
+ void iterateChildrenOf(Element &e, Func &&lambda)
+ {
+ const int lastChildIndex = e.m_firstChildIndex + e.childCount() - 1;
+ for (int i = e.m_firstChildIndex; i <= lastChildIndex; i++) {
+ Element &c = m_childElements[i];
+ if (c.childCount() > 0)
+ iterateChildrenOf(c, lambda);
+ else
+ lambda(c, -(i + 1));
+ }
+ }
+
+ template<typename Func>
+ void iterateChildrenOf(const Element &e, Func &&lambda) const
+ {
+ const int lastChildIndex = e.m_firstChildIndex + e.childCount() - 1;
+ for (int i = e.m_firstChildIndex; i <= lastChildIndex; i++) {
+ const Element &c = m_childElements[i];
+ if (c.childCount() > 0)
+ iterateChildrenOf(c, lambda);
+ else
+ lambda(c, -(i + 1));
+ }
+ }
+
+ template<typename Func>
+ void iterateElements(Func &&lambda)
+ {
+ for (int i = 0; i < m_elements.size(); i++) {
+ Element &e = m_elements[i];
+ if (e.childCount() > 0)
+ iterateChildrenOf(e, lambda);
+ else
+ lambda(e, i);
+ }
+ }
+
+ template<typename Func>
+ void iterateElements(Func &&lambda) const
+ {
+ for (int i = 0; i < m_elements.size(); i++) {
+ const Element &e = m_elements[i];
+ if (e.childCount() > 0)
+ iterateChildrenOf(e, lambda);
+ else
+ lambda(e, i);
+ }
+ }
+
+ static QVector2D closestPointOnLine(const QVector2D &p, const QVector2D &sp, const QVector2D &ep);
+ static bool isPointOnLeft(const QVector2D &p, const QVector2D &sp, const QVector2D &ep);
+ static bool isPointOnLine(const QVector2D &p, const QVector2D &sp, const QVector2D &ep);
+ static bool isPointNearLine(const QVector2D &p, const QVector2D &sp, const QVector2D &ep);
+
+ bool testHint(PathHint hint) const
+ {
+ return m_hints.testFlag(hint);
+ }
+
+ void setHint(PathHint hint, bool on = true)
+ {
+ m_hints.setFlag(hint, on);
+ }
+
+ PathHints pathHints() const
+ {
+ return m_hints;
+ }
+
+ void setPathHints(PathHints newHints)
+ {
+ m_hints = newHints;
+ }
+
+private:
+ void addElement(const QVector2D &control, const QVector2D &to, bool isLine = false);
+ void addElement(const Element &e);
+ Element::CurvatureFlags coordinateOrderOfElement(const Element &element) const;
+
+ friend Q_QUICK_EXPORT QDebug operator<<(QDebug, const QQuadPath &);
+
+ QList<Element> m_elements;
+ QList<Element> m_childElements;
+ QVector2D m_currentPoint;
+ bool m_subPathToStart = true;
+ bool m_windingFill = false;
+ PathHints m_hints;
+
+ friend class QSGCurveProcessor;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QQuadPath::PathHints);
+
+Q_QUICK_EXPORT QDebug operator<<(QDebug, const QQuadPath::Element &);
+Q_QUICK_EXPORT QDebug operator<<(QDebug, const QQuadPath &);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/quick/scenegraph/util/qsgareaallocator.cpp b/src/quick/scenegraph/util/qsgareaallocator.cpp
index 977af81ab7..914b5b6c32 100644
--- a/src/quick/scenegraph/util/qsgareaallocator.cpp
+++ b/src/quick/scenegraph/util/qsgareaallocator.cpp
@@ -13,16 +13,13 @@
QT_BEGIN_NAMESPACE
-namespace
+enum SplitType
{
- enum SplitType
- {
- VerticalSplit,
- HorizontalSplit
- };
+ VerticalSplit,
+ HorizontalSplit
+};
- static const int maxMargin = 2;
-}
+static const int maxMargin = 2;
struct QSGAreaAllocatorNode
{
diff --git a/src/quick/scenegraph/util/qsgareaallocator_p.h b/src/quick/scenegraph/util/qsgareaallocator_p.h
index 07bc1488a2..fa53dde037 100644
--- a/src/quick/scenegraph/util/qsgareaallocator_p.h
+++ b/src/quick/scenegraph/util/qsgareaallocator_p.h
@@ -23,7 +23,7 @@ QT_BEGIN_NAMESPACE
class QRect;
class QPoint;
struct QSGAreaAllocatorNode;
-class Q_QUICK_PRIVATE_EXPORT QSGAreaAllocator
+class Q_QUICK_EXPORT QSGAreaAllocator
{
public:
QSGAreaAllocator(const QSize &size);
diff --git a/src/quick/scenegraph/util/qsgdefaultimagenode_p.h b/src/quick/scenegraph/util/qsgdefaultimagenode_p.h
index 7a541d5d96..344acd5c55 100644
--- a/src/quick/scenegraph/util/qsgdefaultimagenode_p.h
+++ b/src/quick/scenegraph/util/qsgdefaultimagenode_p.h
@@ -22,7 +22,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QSGDefaultImageNode : public QSGImageNode
+class Q_QUICK_EXPORT QSGDefaultImageNode : public QSGImageNode
{
public:
QSGDefaultImageNode();
diff --git a/src/quick/scenegraph/util/qsgdefaultninepatchnode_p.h b/src/quick/scenegraph/util/qsgdefaultninepatchnode_p.h
index 298ed31237..ce33e449ab 100644
--- a/src/quick/scenegraph/util/qsgdefaultninepatchnode_p.h
+++ b/src/quick/scenegraph/util/qsgdefaultninepatchnode_p.h
@@ -22,7 +22,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QSGDefaultNinePatchNode : public QSGNinePatchNode
+class Q_QUICK_EXPORT QSGDefaultNinePatchNode : public QSGNinePatchNode
{
public:
QSGDefaultNinePatchNode();
diff --git a/src/quick/scenegraph/util/qsgdefaultpainternode.cpp b/src/quick/scenegraph/util/qsgdefaultpainternode.cpp
index 2d6f6acf08..874e6dac38 100644
--- a/src/quick/scenegraph/util/qsgdefaultpainternode.cpp
+++ b/src/quick/scenegraph/util/qsgdefaultpainternode.cpp
@@ -108,7 +108,8 @@ void QSGDefaultPainterNode::paint()
}
painter.setCompositionMode(QPainter::CompositionMode_Source);
- painter.fillRect(clipRect, m_fillColor);
+ if (m_fillColor.isValid())
+ painter.fillRect(clipRect, m_fillColor);
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
m_item->paint(&painter);
@@ -167,7 +168,7 @@ void QSGDefaultPainterNode::updateRenderTarget()
if (!m_image.isNull() && !m_dirtyGeometry)
return;
- m_image = QImage(m_textureSize, QImage::Format_ARGB32_Premultiplied);
+ m_image = QImage(m_textureSize, QImage::Format_RGBA8888_Premultiplied);
m_image.fill(Qt::transparent);
if (!m_texture) {
diff --git a/src/quick/scenegraph/util/qsgdefaultpainternode_p.h b/src/quick/scenegraph/util/qsgdefaultpainternode_p.h
index 17e158204b..4038aa0733 100644
--- a/src/quick/scenegraph/util/qsgdefaultpainternode_p.h
+++ b/src/quick/scenegraph/util/qsgdefaultpainternode_p.h
@@ -27,7 +27,7 @@ QT_BEGIN_NAMESPACE
class QSGDefaultRenderContext;
-class Q_QUICK_PRIVATE_EXPORT QSGPainterTexture : public QSGPlainTexture
+class Q_QUICK_EXPORT QSGPainterTexture : public QSGPlainTexture
{
public:
QSGPainterTexture();
@@ -40,7 +40,7 @@ private:
QRect m_dirty_rect;
};
-class Q_QUICK_PRIVATE_EXPORT QSGDefaultPainterNode : public QSGPainterNode
+class Q_QUICK_EXPORT QSGDefaultPainterNode : public QSGPainterNode
{
public:
QSGDefaultPainterNode(QQuickPaintedItem *item);
diff --git a/src/quick/scenegraph/util/qsgflatcolormaterial.cpp b/src/quick/scenegraph/util/qsgflatcolormaterial.cpp
index c7502c91fb..9c491cb7df 100644
--- a/src/quick/scenegraph/util/qsgflatcolormaterial.cpp
+++ b/src/quick/scenegraph/util/qsgflatcolormaterial.cpp
@@ -9,7 +9,7 @@ QT_BEGIN_NAMESPACE
class FlatColorMaterialRhiShader : public QSGMaterialShader
{
public:
- FlatColorMaterialRhiShader();
+ FlatColorMaterialRhiShader(int viewCount);
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
@@ -18,10 +18,10 @@ public:
QSGMaterialType FlatColorMaterialRhiShader::type;
-FlatColorMaterialRhiShader::FlatColorMaterialRhiShader()
+FlatColorMaterialRhiShader::FlatColorMaterialRhiShader(int viewCount)
{
- setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/flatcolor.vert.qsb"));
- setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/flatcolor.frag.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/flatcolor.vert.qsb"), viewCount);
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/flatcolor.frag.qsb"), viewCount);
}
bool FlatColorMaterialRhiShader::updateUniformData(RenderState &state,
@@ -33,11 +33,15 @@ bool FlatColorMaterialRhiShader::updateUniformData(RenderState &state,
QSGFlatColorMaterial *mat = static_cast<QSGFlatColorMaterial *>(newMaterial);
bool changed = false;
QByteArray *buf = state.uniformData();
-
- if (state.isMatrixDirty()) {
- const QMatrix4x4 m = state.combinedMatrix();
- memcpy(buf->data(), m.constData(), 64);
- changed = true;
+ const int shaderMatrixCount = newMaterial->viewCount();
+ const int matrixCount = qMin(state.projectionMatrixCount(), shaderMatrixCount);
+
+ for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
+ if (state.isMatrixDirty()) {
+ const QMatrix4x4 m = state.combinedMatrix(viewIndex);
+ memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
+ changed = true;
+ }
}
const QColor &c = mat->color();
@@ -47,7 +51,7 @@ bool FlatColorMaterialRhiShader::updateUniformData(RenderState &state,
const float opacity = state.opacity() * a;
QVector4D v(r * opacity, g * opacity, b * opacity, opacity);
Q_ASSERT(sizeof(v) == 16);
- memcpy(buf->data() + 64, &v, 16);
+ memcpy(buf->data() + 64 * shaderMatrixCount, &v, 16);
changed = true;
}
@@ -130,7 +134,7 @@ QSGMaterialType *QSGFlatColorMaterial::type() const
QSGMaterialShader *QSGFlatColorMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
Q_UNUSED(renderMode);
- return new FlatColorMaterialRhiShader;
+ return new FlatColorMaterialRhiShader(viewCount());
}
diff --git a/src/quick/scenegraph/util/qsggradientcache.cpp b/src/quick/scenegraph/util/qsggradientcache.cpp
new file mode 100644
index 0000000000..ae88cd2e3e
--- /dev/null
+++ b/src/quick/scenegraph/util/qsggradientcache.cpp
@@ -0,0 +1,121 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsggradientcache_p.h"
+
+#include <QtGui/private/qdrawhelper_p.h>
+#include <QtGui/rhi/qrhi.h>
+
+#include <QtQuick/qsgtexture.h>
+#include <QtQuick/private/qsgplaintexture_p.h>
+
+QT_BEGIN_NAMESPACE
+
+static void generateGradientColorTable(const QSGGradientCacheKey &gradient,
+ uint *colorTable, int size, float opacity)
+{
+ int pos = 0;
+ const QGradientStops &s = gradient.stops;
+ Q_ASSERT(!s.isEmpty());
+ const bool colorInterpolation = true;
+
+ uint alpha = qRound(opacity * 256);
+ uint current_color = ARGB_COMBINE_ALPHA(s[0].second.rgba(), alpha);
+ qreal incr = 1.0 / qreal(size);
+ qreal fpos = 1.5 * incr;
+ colorTable[pos++] = ARGB2RGBA(qPremultiply(current_color));
+
+ while (fpos <= s.first().first) {
+ colorTable[pos] = colorTable[pos - 1];
+ pos++;
+ fpos += incr;
+ }
+
+ if (colorInterpolation)
+ current_color = qPremultiply(current_color);
+
+ const int sLast = s.size() - 1;
+ for (int i = 0; i < sLast; ++i) {
+ qreal delta = 1/(s[i+1].first - s[i].first);
+ uint next_color = ARGB_COMBINE_ALPHA(s[i + 1].second.rgba(), alpha);
+ if (colorInterpolation)
+ next_color = qPremultiply(next_color);
+
+ while (fpos < s[i+1].first && pos < size) {
+ int dist = int(256 * ((fpos - s[i].first) * delta));
+ int idist = 256 - dist;
+ if (colorInterpolation)
+ colorTable[pos] = ARGB2RGBA(INTERPOLATE_PIXEL_256(current_color, idist, next_color, dist));
+ else
+ colorTable[pos] = ARGB2RGBA(qPremultiply(INTERPOLATE_PIXEL_256(current_color, idist, next_color, dist)));
+ ++pos;
+ fpos += incr;
+ }
+ current_color = next_color;
+ }
+
+ uint last_color = ARGB2RGBA(qPremultiply(ARGB_COMBINE_ALPHA(s[sLast].second.rgba(), alpha)));
+ for ( ; pos < size; ++pos)
+ colorTable[pos] = last_color;
+
+ colorTable[size-1] = last_color;
+}
+
+QSGGradientCache::~QSGGradientCache()
+{
+ qDeleteAll(m_textures);
+}
+
+QSGGradientCache *QSGGradientCache::cacheForRhi(QRhi *rhi)
+{
+ static QHash<QRhi *, QSGGradientCache *> caches;
+ auto it = caches.constFind(rhi);
+ if (it != caches.constEnd())
+ return *it;
+
+ QSGGradientCache *cache = new QSGGradientCache;
+ rhi->addCleanupCallback([cache](QRhi *rhi) {
+ caches.remove(rhi);
+ delete cache;
+ });
+ caches.insert(rhi, cache);
+ return cache;
+}
+
+QSGTexture *QSGGradientCache::get(const QSGGradientCacheKey &grad)
+{
+ QSGPlainTexture *tx = m_textures[grad];
+ if (!tx) {
+ static const int W = 1024; // texture size is 1024x1
+ QImage gradTab(W, 1, QImage::Format_RGBA8888_Premultiplied);
+ if (!grad.stops.isEmpty())
+ generateGradientColorTable(grad, reinterpret_cast<uint *>(gradTab.bits()), W, 1.0f);
+ else
+ gradTab.fill(Qt::black);
+ tx = new QSGPlainTexture;
+ tx->setImage(gradTab);
+ switch (grad.spread) {
+ case QGradient::PadSpread:
+ tx->setHorizontalWrapMode(QSGTexture::ClampToEdge);
+ tx->setVerticalWrapMode(QSGTexture::ClampToEdge);
+ break;
+ case QGradient::RepeatSpread:
+ tx->setHorizontalWrapMode(QSGTexture::Repeat);
+ tx->setVerticalWrapMode(QSGTexture::Repeat);
+ break;
+ case QGradient::ReflectSpread:
+ tx->setHorizontalWrapMode(QSGTexture::MirroredRepeat);
+ tx->setVerticalWrapMode(QSGTexture::MirroredRepeat);
+ break;
+ default:
+ qWarning("Unknown gradient spread mode %d", grad.spread);
+ break;
+ }
+ tx->setFiltering(QSGTexture::Linear);
+ m_textures[grad] = tx;
+ }
+ return tx;
+}
+
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/util/qsggradientcache_p.h b/src/quick/scenegraph/util/qsggradientcache_p.h
new file mode 100644
index 0000000000..f384821978
--- /dev/null
+++ b/src/quick/scenegraph/util/qsggradientcache_p.h
@@ -0,0 +1,72 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGGRADIENTCACHE_P_H
+#define QSGGRADIENTCACHE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qhash.h>
+#include <QtGui/qbrush.h>
+
+#include <QtQuick/qtquickexports.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSGTexture;
+class QSGPlainTexture;
+class QRhi;
+
+struct Q_QUICK_EXPORT QSGGradientCacheKey
+{
+ QSGGradientCacheKey(const QGradientStops &stops, QGradient::Spread spread)
+ : stops(stops), spread(spread)
+ { }
+ QGradientStops stops;
+ QGradient::Spread spread;
+ bool operator==(const QSGGradientCacheKey &other) const
+ {
+ return spread == other.spread && stops == other.stops;
+ }
+};
+
+inline size_t qHash(const QSGGradientCacheKey &v, size_t seed = 0)
+{
+ size_t h = seed + v.spread;
+ for (int i = 0; i < 3 && i < v.stops.size(); ++i)
+ h += v.stops[i].second.rgba();
+ return h;
+}
+
+class Q_QUICK_EXPORT QSGGradientCache
+{
+public:
+ struct GradientDesc { // can fully describe a linear/radial/conical gradient
+ QGradientStops stops;
+ QGradient::Spread spread = QGradient::PadSpread;
+ QPointF a; // start (L) or center point (R/C)
+ QPointF b; // end (L) or focal point (R)
+ qreal v0; // center radius (R) or start angle (C)
+ qreal v1; // focal radius (R)
+ };
+
+ ~QSGGradientCache();
+ static QSGGradientCache *cacheForRhi(QRhi *rhi);
+ QSGTexture *get(const QSGGradientCacheKey &grad);
+
+private:
+ QHash<QSGGradientCacheKey, QSGPlainTexture *> m_textures;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGGRADIENTCACHE_P_H
diff --git a/src/quick/scenegraph/util/qsgplaintexture.cpp b/src/quick/scenegraph/util/qsgplaintexture.cpp
index b262047c38..e3ea9ffc17 100644
--- a/src/quick/scenegraph/util/qsgplaintexture.cpp
+++ b/src/quick/scenegraph/util/qsgplaintexture.cpp
@@ -8,7 +8,7 @@
#include <private/qqmlglobal_p.h>
#include <QtGui/qguiapplication.h>
#include <QtGui/qpa/qplatformnativeinterface.h>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
#include <QtQuick/private/qsgrhisupport_p.h>
#include <qtquick_tracepoints_p.h>
@@ -67,7 +67,8 @@ void QSGPlainTexture::setTexture(QRhiTexture *texture) // RHI only
void QSGPlainTexture::setTextureFromNativeTexture(QRhi *rhi,
quint64 nativeObjectHandle,
- int nativeLayout, uint nativeFormat,
+ int nativeLayoutOrState,
+ uint nativeFormat,
const QSize &size,
QQuickWindow::CreateTextureOptions options,
QQuickWindowPrivate::TextureFromNativeTextureFlags flags)
@@ -90,7 +91,7 @@ void QSGPlainTexture::setTextureFromNativeTexture(QRhi *rhi,
QRhiTexture *t = rhi->newTexture(format, size, 1, texFlags);
// ownership of the native object is never taken
- t->createFrom({nativeObjectHandle, nativeLayout});
+ t->createFrom({nativeObjectHandle, nativeLayoutOrState});
setTexture(t);
}
diff --git a/src/quick/scenegraph/util/qsgplaintexture_p.h b/src/quick/scenegraph/util/qsgplaintexture_p.h
index 17b9e41413..caa14fa9df 100644
--- a/src/quick/scenegraph/util/qsgplaintexture_p.h
+++ b/src/quick/scenegraph/util/qsgplaintexture_p.h
@@ -23,7 +23,7 @@ QT_BEGIN_NAMESPACE
class QSGPlainTexturePrivate;
-class Q_QUICK_PRIVATE_EXPORT QSGPlainTexture : public QSGTexture
+class Q_QUICK_EXPORT QSGPlainTexture : public QSGTexture
{
Q_OBJECT
Q_DECLARE_PRIVATE(QSGPlainTexture)
@@ -53,7 +53,7 @@ public:
void setTexture(QRhiTexture *texture);
void setTextureFromNativeTexture(QRhi *rhi,
quint64 nativeObjectHandle,
- int nativeLayout,
+ int nativeLayoutOrState,
uint nativeFormat,
const QSize &size,
QQuickWindow::CreateTextureOptions options,
diff --git a/src/quick/scenegraph/util/qsgrhiatlastexture_p.h b/src/quick/scenegraph/util/qsgrhiatlastexture_p.h
index b766a8f400..5eb9c68495 100644
--- a/src/quick/scenegraph/util/qsgrhiatlastexture_p.h
+++ b/src/quick/scenegraph/util/qsgrhiatlastexture_p.h
@@ -19,7 +19,7 @@
#include <QtQuick/private/qsgplaintexture_p.h>
#include <QtQuick/private/qsgareaallocator_p.h>
#include <QtGui/QSurface>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
QT_BEGIN_NAMESPACE
diff --git a/src/quick/scenegraph/util/qsgsimpletexturenode.cpp b/src/quick/scenegraph/util/qsgsimpletexturenode.cpp
index 74512f6eb1..b8583654e3 100644
--- a/src/quick/scenegraph/util/qsgsimpletexturenode.cpp
+++ b/src/quick/scenegraph/util/qsgsimpletexturenode.cpp
@@ -256,7 +256,7 @@ void QSGSimpleTextureNode::setTextureCoordinatesTransform(QSGSimpleTextureNode::
return;
d->texCoordMode = mode;
qsgsimpletexturenode_update(&m_geometry, texture(), m_rect, d->sourceRect, d->texCoordMode);
- markDirty(DirtyMaterial);
+ markDirty(DirtyGeometry | DirtyMaterial);
}
/*!
diff --git a/src/quick/scenegraph/util/qsgtextnode.cpp b/src/quick/scenegraph/util/qsgtextnode.cpp
new file mode 100644
index 0000000000..6466fd1298
--- /dev/null
+++ b/src/quick/scenegraph/util/qsgtextnode.cpp
@@ -0,0 +1,320 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsgtextnode.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QSGTextNode
+
+ \brief The QSGTextNode class is a class for drawing text layouts and text documents in
+ the Qt Quick scene graph.
+ \inmodule QtQuick
+ \since 6.7
+
+ QSGTextNode can be useful for creating custom Qt Quick items that require text. It is used
+ in Qt Quick by the Text, TextEdit and TextInput elements.
+
+ You can create QSGTextNode objects using QQuickWindow::createTextNode(). The addTextLayout()
+ and addTextDocument() functions provide ways to add text to the QSGTextNode. The text must
+ already be laid out.
+
+ \note Properties must be set before \l addTextLayout() or \l addTextDocument() are called in
+ order to have an effect.
+
+ \note The destruction of QSGTextNode has to be managed with care. In particular, since it
+ references graphics resources, it must be deleted when the Qt Quick scene graph is invalidated.
+ If the node is part of the graph and has the \c OwnedByParent flag set (which is the default),
+ this will happen automatically. However, if the \c OwnedByParent flag is cleared and the node is
+ disposed of manually, care must be taken to do this when the scene graph is invalidated. This
+ can be done by connecting to the \l{QQuickWindow::sceneGraphInvalidated()} signal, or by
+ implementing a slot in the QQuickItem subclass which is named \c{invalidateSceneGraph()}.
+ See also the documentation of QQuickItem for more details.
+ */
+
+/*!
+ \enum QSGTextNode::TextStyle
+
+ This enum type describes styles that can be applied to text rendering.
+
+ \value Normal The text is drawn without any style applied.
+ \value Outline The text is drawn with an outline.
+ \value Raised The text is drawn raised.
+ \value Sunken The text is drawn sunken.
+
+ \sa setTextStyle(), setStyleColor()
+*/
+
+/*!
+ \enum QSGTextNode::RenderType
+
+ This enum type describes type of glyph node used for rendering the text.
+
+ \value QtRendering Text is rendered using a scalable distance field for each glyph.
+ \value NativeRendering Text is rendered using a platform-specific technique.
+ \value CurveRendering Text is rendered using a curve rasterizer running directly on the
+ graphics hardware.
+
+ Select \c NativeRendering if you prefer text to look native on the target platform and do
+ not require advanced features such as transformation of the text. Using such features in
+ combination with the NativeRendering render type will lend poor and sometimes pixelated
+ results.
+
+ Both \c Text.QtRendering and \c Text.CurveRendering are hardware-accelerated techniques.
+ \c QtRendering is the faster of the two, but uses more memory and will exhibit rendering
+ artifacts at large sizes. \c CurveRendering should be considered as an alternative in cases
+ where \c QtRendering does not give good visual results or where reducing graphics memory
+ consumption is a priority.
+
+ \sa setRenderType(), setRenderTypeQuality()
+*/
+
+/*!
+ \fn void QSGTextNode::setColor(QColor color)
+
+ Sets the main color to use when rendering the text to \a color.
+
+ The default is black: \c QColor(0, 0, 0).
+*/
+
+/*!
+ \fn QColor QSGTextNode::color() const
+
+ Returns the main color used when rendering the text.
+*/
+
+/*!
+ \fn void QSGTextNode::setStyleColor(QColor styleColor)
+
+ Sets the style color to use when rendering the text to \a styleColor.
+
+ The default is black: \c QColor(0, 0, 0).
+
+ \sa setTextStyle()
+*/
+
+/*!
+ \fn QColor QSGTextNode::styleColor() const
+
+ Returns the style color used when rendering the text.
+
+ \sa textStyle()
+*/
+
+/*!
+ \fn void QSGTextNode::setTextStyle(QSGTextNode::TextStyle textStyle)
+
+ Sets the style of the rendered text to \a textStyle. The default is \c Normal.
+
+ \sa setStyleColor()
+*/
+
+/*!
+ \fn QSGTextNode::TextStyle QSGTextNode::textStyle()
+
+ Returns the style of the rendered text.
+
+ \sa styleColor()
+*/
+
+/*!
+ \fn void QSGTextNode::setLinkColor(QColor linkColor)
+
+ Sets the color of or hyperlinks to \a linkColor in the text.
+
+ The default is blue: \c QColor(0, 0, 255).
+*/
+
+/*!
+ \fn QColor QSGTextNode::linkColor() const
+
+ Returns the color of hyperlinks in the text.
+*/
+
+/*!
+ \fn void QSGTextNode::setSelectionColor(QColor color)
+
+ Sets the color of the selection background to \a color when any part of the text is
+ marked as selected.
+
+ The default is dark blue: \c QColor(0, 0, 128).
+*/
+
+/*!
+ \fn QColor QSGTextNode::selectionColor() const
+
+ Returns the color of the selection background when any part of the text is marked as selected.
+*/
+
+/*!
+ \fn QColor QSGTextNode::selectionTextColor() const
+
+ Returns the color of the selection text when any part of the text is marked as selected.
+*/
+
+/*!
+ \fn void QSGTextNode::setSelectionTextColor(QColor selectionTextColor)
+
+ Sets the color of the selection text to \a selectionTextColor when any part of the text is
+ marked as selected.
+
+ The default is white: \c QColor(255, 255, 255).
+*/
+
+
+/*!
+ \fn void QSGTextNode::setRenderType(RenderType renderType)
+
+ Sets the type of glyph node in use to \a renderType.
+
+ The default is \l QtRendering.
+*/
+
+/*!
+ \fn QSGTextNode::RenderType QSGTextNode::renderType() const
+
+ Returns the type of glyph node used for rendering the text.
+*/
+
+/*!
+ \fn void QSGTextNode::setRenderTypeQuality(int renderTypeQuality)
+
+ If the \l renderType() in use supports it, set the quality to use when rendering the text.
+ When supported, this can be used to trade visual fidelity for execution speed or memory.
+
+ When the \a renderTypeQuality is < 0, the default quality is used.
+
+ The \a renderTypeQuality can be any integer, although limitations imposed by the underlying
+ graphics hardware may be encountered if extreme values are set. The Qt Quick Text element
+ operates with the following predefined values:
+
+ \value DefaultRenderTypeQuality -1 (default)
+ \value LowRenderTypeQuality 26
+ \value NormalRenderTypeQuality 52
+ \value HighRenderTypeQuality 104
+ \value VeryHighRenderTypeQuality 208
+
+ This value is currently only respected by the QtRendering render type. Setting it changes the
+ resolution of the distance fields used to represent the glyphs. Setting it above normal will
+ cause memory consumption to increase, but reduces filtering artifacts on very large text.
+
+ The default is -1.
+*/
+
+/*!
+ \fn int QSGTextNode::renderTypeQuality() const
+
+ Returns the render type quality of the node. See \l setRenderTypeQuality() for details.
+*/
+
+/*!
+ \fn void QSGTextNode::setFiltering(QSGTexture::Filtering filtering)
+
+ Sets the sampling mode used when scaling images that are part of the displayed text to
+ \a filtering. For smoothly scaled images, use \l{QSGTexture::Linear} here.
+
+ The default is \l{QSGTexture::Nearest}.
+
+ \sa filtering()
+*/
+
+/*!
+ \fn QSGTexture::Filtering QSGTextNode::filtering() const
+
+ Returns the sampling mode used when scaling images that are part of the displayed text.
+
+ \sa setFiltering()
+*/
+
+/*!
+ \fn void QSGTextNode::setViewport(const QRectF &viewport)
+
+ Sets the bounding rect of the viewport where the text is displayed to \a viewport. Providing
+ this information makes it possible for the QSGTextNode to optimize which parts of the text
+ layout or document are included in the scene graph.
+
+ The default is a default-constructed QRectF. For this viewport, all contents will be included
+ in the graph.
+*/
+
+/*!
+ \fn QRectF QSGTextNode::viewport() const
+
+ Returns the current viewport set for this QSGTextNode.
+*/
+
+/*!
+ \fn QSGTextNode::addTextLayout(QPointF position, QTextLayout *layout, int selectionStart = -1, int selectionCount = -1, int lineStart = 0, int lineCount = -1)
+
+ Adds the contents of \a layout to the text node at \a position. If \a selectionStart is >= 0,
+ then this marks the first character in a selected area of \a selectionCount number of
+ characters. The selection is represented as a background fill with the \l selectionColor() and
+ the selected text is rendered in the \l selectionTextColor().
+
+ For convenience, \a lineStart and \a lineCount can be used to select the range of \l QTextLine
+ objects to include from the layout. This can be useful, for instance, when creating elided
+ layouts. If \a lineCount is < 0, then the the node will include the lines from \a lineStart to
+ the end of the layout.
+
+ This function forwards its arguments to the virtual function doAddTextLayout().
+
+ \sa clear(), doAddTextLayout()
+*/
+
+/*!
+ \fn QSGTextNode::addTextDocument(QPointF position, QTextDocument *document, int selectionStart = -1, int selectionCount = -1)
+
+ Adds the contents of \a document to the text node at \a position. If \a selectionStart is >= 0,
+ then this marks the first character in a selected area of \a selectionCount number of
+ characters. The selection is represented as a background fill with the \l selectionColor() and
+ the selected text is rendered in the \l selectionTextColor().
+
+ This function forwards its arguments to the virtual function doAddTextDocument().
+
+ \sa clear(), doAddTextDocument()
+*/
+
+/*!
+ \fn QSGTextNode::doAddTextLayout(QPointF position, QTextLayout *layout, int selectionStart, int selectionCount, int lineStart, int lineCount)
+
+ Virtual function called by addTextLayout(), which converts the contents of \a layout to scene
+ graph nodes and adds them to the current node at \a position.
+
+ If \a selectionStart is >= 0, then this marks the first character in a selected area of
+ \a selectionCount number of characters. The selection is represented as a background fill with
+ the \l selectionColor() and the selected text is rendered in the \l selectionTextColor().
+
+ For convenience, \a lineStart and \a lineCount can be used to select the range of \l QTextLine
+ objects to include from the layout. This can be useful, for instance, when creating elided
+ layouts. If \a lineCount is < 0, then the the node will include the lines from \a lineStart to
+ the end of the layout.
+
+ \sa clear(), addTextLayout()
+*/
+
+/*!
+ \fn QSGTextNode::doAddTextDocument(QPointF position, QTextDocument *document, int selectionStart, int selectionCount)
+
+ Virtual function called by addTextDocument(), which converts the contents of \a document to
+ scene graph nodes and adds them to the current node at \a position.
+
+ If \a selectionStart is >= 0, then this marks the first character in a selected area of
+ \a selectionCount number of characters. The selection is represented as a background fill with
+ the \l selectionColor() and the selected text is rendered in the \l selectionTextColor().
+
+ \sa clear(), addTextDocument()
+*/
+
+/*!
+ \fn QSGTextNode::clear()
+
+ Clears the contents of the node, deleting nodes and other data that represents the layouts
+ and documents that have been added to it.
+
+ \sa addTextLayout(), addTextDocument()
+*/
+
+QSGTextNode::~QSGTextNode() = default;
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/util/qsgtextnode.h b/src/quick/scenegraph/util/qsgtextnode.h
new file mode 100644
index 0000000000..47431929af
--- /dev/null
+++ b/src/quick/scenegraph/util/qsgtextnode.h
@@ -0,0 +1,101 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGTEXTNODE_H
+#define QSGTEXTNODE_H
+
+#include <QtGui/qtextlayout.h>
+#include <QtQuick/qsgnode.h>
+#include <QtQuick/qsgtexture.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_QUICK_EXPORT QSGTextNode : public QSGTransformNode
+{
+public:
+ ~QSGTextNode() override;
+
+ // Should match the TextStyle in qquicktext_p.h
+ enum TextStyle : quint8
+ {
+ Normal,
+ Outline,
+ Raised,
+ Sunken
+ };
+
+ // Should match the RenderType in qquicktext_p.h
+ enum RenderType: quint8
+ {
+ QtRendering,
+ NativeRendering,
+ CurveRendering
+ };
+
+ virtual void setColor(QColor color) = 0;
+ virtual QColor color() const = 0;
+
+ virtual void setTextStyle(TextStyle textStyle) = 0;
+ virtual TextStyle textStyle() = 0;
+
+ virtual void setStyleColor(QColor styleColor) = 0;
+ virtual QColor styleColor() const = 0;
+
+ virtual void setLinkColor(QColor linkColor) = 0;
+ virtual QColor linkColor() const = 0;
+
+ virtual void setSelectionColor(QColor selectionColor) = 0;
+ virtual QColor selectionColor() const = 0;
+
+ virtual void setSelectionTextColor(QColor selectionTextColor) = 0;
+ virtual QColor selectionTextColor() const = 0;
+
+ virtual void setRenderType(RenderType renderType) = 0;
+ virtual RenderType renderType() const = 0;
+
+ virtual void setRenderTypeQuality(int renderTypeQuality) = 0;
+ virtual int renderTypeQuality() const = 0;
+
+ virtual void setFiltering(QSGTexture::Filtering) = 0;
+ virtual QSGTexture::Filtering filtering() const = 0;
+
+ virtual void clear() = 0;
+
+ virtual void setViewport(const QRectF &viewport) = 0;
+ virtual QRectF viewport() const = 0;
+
+ void addTextLayout(QPointF position,
+ QTextLayout *layout,
+ int selectionStart = -1,
+ int selectionCount = -1,
+ int lineStart = 0,
+ int lineCount = -1)
+ {
+ doAddTextLayout(position, layout, selectionStart, selectionCount, lineStart, lineCount);
+ }
+
+ void addTextDocument(QPointF position,
+ QTextDocument *document,
+ int selectionStart = -1,
+ int selectionCount = -1)
+ {
+ doAddTextDocument(position, document, selectionStart, selectionCount);
+ }
+
+private:
+ virtual void doAddTextLayout(QPointF position,
+ QTextLayout *layout,
+ int selectionStart,
+ int selectionCount,
+ int lineStart,
+ int lineCount) = 0;
+ virtual void doAddTextDocument(QPointF position,
+ QTextDocument *document,
+ int selectionStart,
+ int selectionCount) = 0;
+
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGTEXTNODE_H
diff --git a/src/quick/scenegraph/util/qsgtexturematerial.cpp b/src/quick/scenegraph/util/qsgtexturematerial.cpp
index 6a02aef992..c573284f47 100644
--- a/src/quick/scenegraph/util/qsgtexturematerial.cpp
+++ b/src/quick/scenegraph/util/qsgtexturematerial.cpp
@@ -3,7 +3,7 @@
#include "qsgtexturematerial_p.h"
#include <private/qsgtexture_p.h>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
QT_BEGIN_NAMESPACE
@@ -13,21 +13,24 @@ inline static bool isPowerOfTwo(int x)
return x == (x & -x);
}
-QSGOpaqueTextureMaterialRhiShader::QSGOpaqueTextureMaterialRhiShader()
+QSGOpaqueTextureMaterialRhiShader::QSGOpaqueTextureMaterialRhiShader(int viewCount)
{
- setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/opaquetexture.vert.qsb"));
- setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/opaquetexture.frag.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/opaquetexture.vert.qsb"), viewCount);
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/opaquetexture.frag.qsb"), viewCount);
}
-bool QSGOpaqueTextureMaterialRhiShader::updateUniformData(RenderState &state, QSGMaterial *, QSGMaterial *)
+bool QSGOpaqueTextureMaterialRhiShader::updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *)
{
bool changed = false;
QByteArray *buf = state.uniformData();
+ const int matrixCount = qMin(state.projectionMatrixCount(), newMaterial->viewCount());
- if (state.isMatrixDirty()) {
- const QMatrix4x4 m = state.combinedMatrix();
- memcpy(buf->data(), m.constData(), 64);
- changed = true;
+ for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
+ if (state.isMatrixDirty()) {
+ const QMatrix4x4 m = state.combinedMatrix(viewIndex);
+ memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
+ changed = true;
+ }
}
return changed;
@@ -142,7 +145,7 @@ QSGMaterialType *QSGOpaqueTextureMaterial::type() const
QSGMaterialShader *QSGOpaqueTextureMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
Q_UNUSED(renderMode);
- return new QSGOpaqueTextureMaterialRhiShader;
+ return new QSGOpaqueTextureMaterialRhiShader(viewCount());
}
@@ -333,24 +336,26 @@ QSGMaterialType *QSGTextureMaterial::type() const
QSGMaterialShader *QSGTextureMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
Q_UNUSED(renderMode);
- return new QSGTextureMaterialRhiShader;
+ return new QSGTextureMaterialRhiShader(viewCount());
}
-QSGTextureMaterialRhiShader::QSGTextureMaterialRhiShader()
+QSGTextureMaterialRhiShader::QSGTextureMaterialRhiShader(int viewCount)
+ : QSGOpaqueTextureMaterialRhiShader(viewCount)
{
- setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/texture.vert.qsb"));
- setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/texture.frag.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/texture.vert.qsb"), viewCount);
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/texture.frag.qsb"), viewCount);
}
bool QSGTextureMaterialRhiShader::updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
{
bool changed = false;
QByteArray *buf = state.uniformData();
+ const int shaderMatrixCount = newMaterial->viewCount();
if (state.isOpacityDirty()) {
const float opacity = state.opacity();
- memcpy(buf->data() + 64, &opacity, 4);
+ memcpy(buf->data() + 64 * shaderMatrixCount, &opacity, 4);
changed = true;
}
diff --git a/src/quick/scenegraph/util/qsgtexturematerial_p.h b/src/quick/scenegraph/util/qsgtexturematerial_p.h
index 5472ea4aa6..9334d6126b 100644
--- a/src/quick/scenegraph/util/qsgtexturematerial_p.h
+++ b/src/quick/scenegraph/util/qsgtexturematerial_p.h
@@ -20,10 +20,10 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QSGOpaqueTextureMaterialRhiShader : public QSGMaterialShader
+class Q_QUICK_EXPORT QSGOpaqueTextureMaterialRhiShader : public QSGMaterialShader
{
public:
- QSGOpaqueTextureMaterialRhiShader();
+ QSGOpaqueTextureMaterialRhiShader(int viewCount);
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
void updateSampledImage(RenderState &state, int binding, QSGTexture **texture, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
@@ -32,7 +32,7 @@ public:
class QSGTextureMaterialRhiShader : public QSGOpaqueTextureMaterialRhiShader
{
public:
- QSGTextureMaterialRhiShader();
+ QSGTextureMaterialRhiShader(int viewCount);
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
};
diff --git a/src/quick/scenegraph/util/qsgtransform.cpp b/src/quick/scenegraph/util/qsgtransform.cpp
new file mode 100644
index 0000000000..7efb014c33
--- /dev/null
+++ b/src/quick/scenegraph/util/qsgtransform.cpp
@@ -0,0 +1,10 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsgtransform_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QMatrix4x4 QSGTransform::m_identity;
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/util/qsgtransform_p.h b/src/quick/scenegraph/util/qsgtransform_p.h
new file mode 100644
index 0000000000..f19d7a0efd
--- /dev/null
+++ b/src/quick/scenegraph/util/qsgtransform_p.h
@@ -0,0 +1,105 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGTRANSFORM_P_H
+#define QSGTRANSFORM_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QSharedPointer>
+#include <QMatrix4x4>
+#include <QtQuick/qtquickexports.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_QUICK_EXPORT QSGTransform
+{
+public:
+ void setMatrix(const QMatrix4x4 &matrix)
+ {
+ if (matrix.isIdentity())
+ m_matrixPtr.clear();
+ else
+ m_matrixPtr = QSharedPointer<QMatrix4x4>::create(matrix);
+ m_invertedPtr.clear();
+ }
+
+ QMatrix4x4 matrix() const
+ {
+ return m_matrixPtr ? *m_matrixPtr : m_identity;
+ }
+
+ bool isIdentity() const
+ {
+ return !m_matrixPtr;
+ }
+
+ bool operator==(const QMatrix4x4 &other) const
+ {
+ return m_matrixPtr ? (other == *m_matrixPtr) : other.isIdentity();
+ }
+
+ bool operator!=(const QMatrix4x4 &other) const
+ {
+ return !(*this == other);
+ }
+
+ bool operator==(const QSGTransform &other) const
+ {
+ return (m_matrixPtr == other.m_matrixPtr)
+ || (m_matrixPtr && other.m_matrixPtr && *m_matrixPtr == *other.m_matrixPtr);
+ }
+
+ bool operator!=(const QSGTransform &other) const
+ {
+ return !(*this == other);
+ }
+
+ int compareTo(const QSGTransform &other) const
+ {
+ int diff = 0;
+ if (m_matrixPtr != other.m_matrixPtr) {
+ if (m_matrixPtr.isNull()) {
+ diff = -1;
+ } else if (other.m_matrixPtr.isNull()) {
+ diff = 1;
+ } else {
+ const float *ptr1 = m_matrixPtr->constData();
+ const float *ptr2 = other.m_matrixPtr->constData();
+ for (int i = 0; i < 16 && !diff; i++) {
+ float d = ptr1[i] - ptr2[i];
+ if (d != 0)
+ diff = (d > 0) ? 1 : -1;
+ }
+ }
+ }
+ return diff;
+ }
+
+ const float *invertedData() const
+ {
+ if (!m_matrixPtr)
+ return m_identity.constData();
+ if (!m_invertedPtr)
+ m_invertedPtr = QSharedPointer<QMatrix4x4>::create(m_matrixPtr->inverted());
+ return m_invertedPtr->constData();
+ }
+
+private:
+ static QMatrix4x4 m_identity;
+ QSharedPointer<QMatrix4x4> m_matrixPtr;
+ mutable QSharedPointer<QMatrix4x4> m_invertedPtr;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGTRANSFORM_P_H
diff --git a/src/quick/scenegraph/util/qsgvertexcolormaterial.cpp b/src/quick/scenegraph/util/qsgvertexcolormaterial.cpp
index 7d5250f5ea..0d2643effa 100644
--- a/src/quick/scenegraph/util/qsgvertexcolormaterial.cpp
+++ b/src/quick/scenegraph/util/qsgvertexcolormaterial.cpp
@@ -8,33 +8,35 @@ QT_BEGIN_NAMESPACE
class QSGVertexColorMaterialRhiShader : public QSGMaterialShader
{
public:
- QSGVertexColorMaterialRhiShader();
+ QSGVertexColorMaterialRhiShader(int viewCount);
- bool updateUniformData(RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) override;
+ bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
};
-QSGVertexColorMaterialRhiShader::QSGVertexColorMaterialRhiShader()
+QSGVertexColorMaterialRhiShader::QSGVertexColorMaterialRhiShader(int viewCount)
{
- setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/vertexcolor.vert.qsb"));
- setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/vertexcolor.frag.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/vertexcolor.vert.qsb"), viewCount);
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/vertexcolor.frag.qsb"), viewCount);
}
-bool QSGVertexColorMaterialRhiShader::updateUniformData(RenderState &state,
- QSGMaterial * /*newEffect*/,
- QSGMaterial * /*oldEffect*/)
+bool QSGVertexColorMaterialRhiShader::updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *)
{
bool changed = false;
QByteArray *buf = state.uniformData();
-
- if (state.isMatrixDirty()) {
- const QMatrix4x4 m = state.combinedMatrix();
- memcpy(buf->data(), m.constData(), 64);
- changed = true;
+ const int shaderMatrixCount = newMaterial->viewCount();
+ const int matrixCount = qMin(state.projectionMatrixCount(), shaderMatrixCount);
+
+ for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
+ if (state.isMatrixDirty()) {
+ const QMatrix4x4 m = state.combinedMatrix(viewIndex);
+ memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
+ changed = true;
+ }
}
if (state.isOpacityDirty()) {
const float opacity = state.opacity();
- memcpy(buf->data() + 64, &opacity, 4);
+ memcpy(buf->data() + 64 * shaderMatrixCount, &opacity, 4);
changed = true;
}
@@ -114,7 +116,7 @@ QSGMaterialType *QSGVertexColorMaterial::type() const
QSGMaterialShader *QSGVertexColorMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
Q_UNUSED(renderMode);
- return new QSGVertexColorMaterialRhiShader;
+ return new QSGVertexColorMaterialRhiShader(viewCount());
}
QT_END_NAMESPACE
diff --git a/src/quick/util/qminimalflatset_p.h b/src/quick/util/qminimalflatset_p.h
deleted file mode 100644
index a7aed41305..0000000000
--- a/src/quick/util/qminimalflatset_p.h
+++ /dev/null
@@ -1,143 +0,0 @@
-// Copyright (C) 2022 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#ifndef QMINIMALFLATSET_P_H
-#define QMINIMALFLATSET_P_H
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists purely as an
-// implementation detail. This header file may change from version to
-// version without notice, or even be removed.
-//
-// We mean it.
-//
-
-#include <QtQuick/qtquickglobal.h>
-
-#include <QtCore/qcontainerfwd.h>
-#include <QtCore/private/qglobal_p.h>
-
-//#define QMINIMAL_FLAT_SET_DEBUG
-#ifdef QMINIMAL_FLAT_SET_DEBUG
-# include <QtCore/qscopeguard.h>
-# include <QtCore/qdebug.h>
-# define QMINIMAL_FLAT_SET_PRINT_AT_END \
- const auto sg = qScopeGuard([&] { qDebug() << this << *this; });
-#else
-# define QMINIMAL_FLAT_SET_PRINT_AT_END
-#endif
-
-#include <algorithm> // for std::lower_bound
-
-QT_BEGIN_NAMESPACE
-
-/*
- This is a minimal version of a QFlatSet, the std::set version of QFlatMap.
- Like QFlatMap, it has linear insertion and removal, not logarithmic, like
- real QMap and std::set, so it's only a good container if you either have
- very few entries or lots, but with separate setup and lookup stages.
- Because a full QFlatSet would be 10x the work on writing this minimal one,
- we keep it here for now. When more users pop up and the class has matured a
- bit, we can consider moving it alongside QFlatMap in QtCore.
-*/
-
-template <typename T, typename Container = QList<T>>
-class QMinimalFlatSet
-{
- Container c;
-public:
- // compiler-generated default ctor is ok!
- // compiler-generated copy/move ctor/assignment operators are ok!
- // compiler-generated dtor is ok!
-
- using const_iterator = typename Container::const_iterator;
- using iterator = const_iterator;
- using const_reverse_iterator = typename Container::const_reverse_iterator;
- using reverse_iterator = const_reverse_iterator;
- using value_type = T;
-
- iterator begin() const { return c.cbegin(); }
- iterator end() const { return c.cend(); }
- iterator cbegin() const { return begin(); }
- iterator cend() const { return cend(); }
-
- reverse_iterator rbegin() const { return c.crbegin(); }
- reverse_iterator rend() const { return c.crend(); }
- reverse_iterator crbegin() const { return rbegin(); }
- reverse_iterator crend() const { return rend(); }
-
- void clear() {
- QMINIMAL_FLAT_SET_PRINT_AT_END
- c.clear();
- }
- auto size() const { return c.size(); }
- auto count() const { return size(); }
- bool isEmpty() const { return size() == 0; }
-
- std::pair<iterator, bool> insert(value_type &&v)
- {
- QMINIMAL_FLAT_SET_PRINT_AT_END
- const auto r = lookup(v);
- if (r.exists)
- return {r.it, false};
- else
- return {c.insert(r.it, std::move(v)), true};
- }
-
- std::pair<iterator, bool> insert(const value_type &v)
- {
- QMINIMAL_FLAT_SET_PRINT_AT_END
- const auto r = lookup(v);
- if (r.exists)
- return {r.it, false};
- else
- return {c.insert(r.it, v), true};
- }
-
- void erase(const value_type &v)
- {
- QMINIMAL_FLAT_SET_PRINT_AT_END
- const auto r = lookup(v);
- if (r.exists)
- c.erase(r.it);
- }
- void remove(const value_type &v) { erase(v); }
-
- bool contains(const value_type &v) const
- {
- return lookup(v).exists;
- }
-
- const Container &values() const & { return c; }
- Container values() && { return std::move(c); }
-
-private:
- auto lookup(const value_type &v) const
- {
- struct R {
- iterator it;
- bool exists;
- };
-
- const auto it = std::lower_bound(c.cbegin(), c.cend(), v);
- return R{it, it != c.cend() && !(v < *it)};
- }
-
-#ifdef QMINIMAL_FLAT_SET_DEBUG
- friend QDebug operator<<(QDebug dbg, const QMinimalFlatSet &set)
- {
- const QDebugStateSaver saver(dbg);
- dbg.nospace() << "QMinimalFlatSet{";
- for (auto &e : set)
- dbg << e << ", ";
- return dbg << "}";
- }
-#endif
-};
-
-QT_END_NAMESPACE
-
-#endif // QMINIMALFLATSET_P_H
diff --git a/src/quick/util/qquickanimation.cpp b/src/quick/util/qquickanimation.cpp
index 509026192f..2f3bc66013 100644
--- a/src/quick/util/qquickanimation.cpp
+++ b/src/quick/util/qquickanimation.cpp
@@ -165,6 +165,27 @@ QQmlProperty QQuickAbstractAnimationPrivate::createProperty(QObject *obj, const
return prop;
}
+void QQuickAbstractAnimationPrivate::animationGroupDirty()
+{
+ Q_ASSERT(group != nullptr);
+ if (!componentComplete)
+ return;
+
+ auto *animGroupPriv = static_cast<QQuickAnimationGroupPrivate *>(QQuickAnimationGroupPrivate::get(group));
+ if (animGroupPriv->running && !animGroupPriv->animationDirty) {
+ animGroupPriv->animationDirty = true;
+
+ if (animGroupPriv->animationInstance && group->currentTime() == 0) {
+ // restart if the animation didn't proceed yet.
+ animGroupPriv->restartFromCurrentLoop();
+ }
+ }
+
+ // check the animationGroup is one of another animationGroup members
+ if (animGroupPriv->group)
+ animGroupPriv->animationGroupDirty();
+}
+
/*!
\qmlsignal QtQuick::Animation::started()
@@ -269,6 +290,11 @@ void QQuickAbstractAnimation::setRunning(bool r)
// Therefore, the state of d->running will in that case be different than r if we are back in
// the root stack frame of the recursive calls to setRunning()
emit runningChanged(d->running);
+ } else if (d->animationInstance) {
+ // If there was a recursive call, make sure the d->running is set correctly
+ d->running = d->animationInstance->isRunning();
+ } else {
+ d->running = r;
}
}
@@ -696,6 +722,8 @@ void QQuickPauseAnimation::setDuration(int duration)
return;
d->duration = duration;
emit durationChanged(duration);
+ if (d->group)
+ d->animationGroupDirty();
}
QAbstractAnimationJob* QQuickPauseAnimation::transition(QQuickStateActions &actions,
@@ -1052,7 +1080,7 @@ QAbstractAnimationJob* QQuickScriptAction::transition(QQuickStateActions &action
PropertyAction object) so that the rotation animation begins with the
correct transform origin.
- \sa {Animation and Transitions in Qt Quick}, {Qt QML}
+ \sa {Animation and Transitions in Qt Quick}, {Qt Qml}
*/
QQuickPropertyAction::QQuickPropertyAction(QObject *parent)
: QQuickAbstractAnimation(*(new QQuickPropertyActionPrivate), parent)
@@ -1076,6 +1104,8 @@ void QQuickPropertyAction::setTargetObject(QObject *o)
return;
d->target = o;
emit targetChanged();
+ if (d->group)
+ d->animationGroupDirty();
}
QString QQuickPropertyAction::property() const
@@ -1091,6 +1121,8 @@ void QQuickPropertyAction::setProperty(const QString &n)
return;
d->propertyName = n;
emit propertyChanged();
+ if (d->group)
+ d->animationGroupDirty();
}
/*!
@@ -1121,6 +1153,8 @@ void QQuickPropertyAction::setProperties(const QString &p)
return;
d->properties = p;
emit propertiesChanged(p);
+ if (d->group)
+ d->animationGroupDirty();
}
QQmlListProperty<QObject> QQuickPropertyAction::targets()
@@ -1159,7 +1193,7 @@ QVariant QQuickPropertyAction::value() const
void QQuickPropertyAction::setValue(const QVariant &v)
{
Q_D(QQuickPropertyAction);
- if (d->value.isNull || d->value != v) {
+ if (!d->value.isValid() || d->value != v) {
d->value = v;
emit valueChanged(v);
}
@@ -1620,14 +1654,16 @@ void QQuickRotationAnimation::setTo(qreal t)
Possible values are:
- \list
- \li RotationAnimation.Numerical (default) - Rotate by linearly interpolating between the two numbers.
- A rotation from 10 to 350 will rotate 340 degrees clockwise.
- \li RotationAnimation.Clockwise - Rotate clockwise between the two values
- \li RotationAnimation.Counterclockwise - Rotate counterclockwise between the two values
- \li RotationAnimation.Shortest - Rotate in the direction that produces the shortest animation path.
- A rotation from 10 to 350 will rotate 20 degrees counterclockwise.
- \endlist
+ \value RotationAnimation.Numerical
+ (default) Rotate by linearly interpolating between the two numbers.
+ A rotation from \c 10 to \c 350 will rotate 340 degrees clockwise.
+ \value RotationAnimation.Clockwise
+ Rotate clockwise between the two values
+ \value RotationAnimation.Counterclockwise
+ Rotate counterclockwise between the two values
+ \value RotationAnimation.Shortest
+ Rotate in the direction that produces the shortest animation path.
+ A rotation from \c 10 to \c 350 will rotate \c 20 degrees counterclockwise.
*/
QQuickRotationAnimation::RotationDirection QQuickRotationAnimation::direction() const
{
@@ -1720,6 +1756,36 @@ void QQuickAnimationGroupPrivate::removeLast_animation(QQmlListProperty<QQuickAb
q->d_func()->animations.last()->setGroup(nullptr);
}
+void QQuickAnimationGroupPrivate::restartFromCurrentLoop()
+{
+ Q_Q(QQuickAnimationGroup);
+ if (!animationDirty)
+ return;
+
+ animationDirty = false;
+
+ Q_ASSERT(animationInstance);
+ const int currentLoop = animationInstance->currentLoop();
+
+ QSignalBlocker signalBlocker(q);
+ q->stop();
+ q->start();
+
+ Q_ASSERT(animationInstance);
+ // Restarting adjusts animationInstance's loopCount
+ // Since we just want to start it from this loop,
+ // it will be restored again.
+ if (loopCount != -1)
+ animationInstance->setLoopCount(loopCount - currentLoop);
+}
+
+void QQuickAnimationGroupPrivate::animationCurrentLoopChanged(QAbstractAnimationJob *)
+{
+ if (!animationDirty)
+ return;
+ restartFromCurrentLoop();
+}
+
QQuickAnimationGroup::~QQuickAnimationGroup()
{
Q_D(QQuickAnimationGroup);
@@ -2084,7 +2150,7 @@ void QQuickBulkValueAnimator::debugAnimation(QDebug d) const
Note that PropertyAnimation inherits the abstract \l Animation type.
This includes additional properties and methods for controlling the animation.
- \section1 Modifying Properties Duration Animations
+ \section1 Modifying running animations
Since Qt 6.4, it is possible to set the \l from, \l to, \l duration, and
\l easing properties on a top-level animation while it is running. The
@@ -2133,6 +2199,8 @@ void QQuickPropertyAnimation::setDuration(int duration)
if (d->componentComplete && d->running)
d->ourPropertiesDirty = true;
emit durationChanged(duration);
+ if (d->group)
+ d->animationGroupDirty();
}
/*!
@@ -2162,6 +2230,8 @@ void QQuickPropertyAnimation::setFrom(const QVariant &f)
if (d->componentComplete && d->running)
d->ourPropertiesDirty = true;
emit fromChanged();
+ if (d->group)
+ d->animationGroupDirty();
}
/*!
@@ -2191,6 +2261,8 @@ void QQuickPropertyAnimation::setTo(const QVariant &t)
if (d->componentComplete && d->running)
d->ourPropertiesDirty = true;
emit toChanged();
+ if (d->group)
+ d->animationGroupDirty();
}
/*!
@@ -2423,6 +2495,8 @@ void QQuickPropertyAnimation::setEasing(const QEasingCurve &e)
if (d->componentComplete && d->running)
d->ourPropertiesDirty = true;
emit easingChanged(e);
+ if (d->group)
+ d->animationGroupDirty();
}
QObject *QQuickPropertyAnimation::target() const
@@ -2438,6 +2512,8 @@ void QQuickPropertyAnimation::setTargetObject(QObject *o)
return;
d->target = o;
emit targetChanged();
+ if (d->group)
+ d->animationGroupDirty();
}
QString QQuickPropertyAnimation::property() const
@@ -2453,6 +2529,8 @@ void QQuickPropertyAnimation::setProperty(const QString &n)
return;
d->propertyName = n;
emit propertyChanged();
+ if (d->group)
+ d->animationGroupDirty();
}
QString QQuickPropertyAnimation::properties() const
@@ -2469,6 +2547,8 @@ void QQuickPropertyAnimation::setProperties(const QString &prop)
d->properties = prop;
emit propertiesChanged(prop);
+ if (d->group)
+ d->animationGroupDirty();
}
/*!
@@ -2574,7 +2654,38 @@ void QQuickPropertyAnimation::setProperties(const QString &prop)
QQmlListProperty<QObject> QQuickPropertyAnimation::targets()
{
Q_D(QQuickPropertyAnimation);
- return QQmlListProperty<QObject>(this, &(d->targets));
+ using ListPtr = QList<QPointer<QObject>> *;
+ using LP = QQmlListProperty<QObject>;
+ LP::AppendFunction appendFn = [](LP *prop, QObject *value)
+ {
+ static_cast<ListPtr>(prop->data)->append(value);
+ };
+ LP::CountFunction countFn = [](LP *prop)
+ {
+ return static_cast<ListPtr>(prop->data)->size();
+ };
+
+ LP::AtFunction atFn = [](LP *prop, qsizetype index) -> QObject *
+ {
+ return static_cast<ListPtr>(prop->data)->at(index);
+ };
+
+ LP::ClearFunction clearFN = [](LP *prop)
+ {
+ return static_cast<ListPtr>(prop->data)->clear();
+ };
+
+ LP::ReplaceFunction replaceFn = [](LP *prop, qsizetype index, QObject *value)
+ {
+ static_cast<ListPtr>(prop->data)->replace(index, value);
+ };
+
+ LP::RemoveLastFunction removeLastFn = [](LP *prop)
+ {
+ static_cast<ListPtr>(prop->data)->removeLast();
+ };
+
+ return QQmlListProperty<QObject>(this, &(d->targets), appendFn, countFn, atFn, clearFN, replaceFn, removeLastFn);
}
/*!
@@ -2646,7 +2757,7 @@ QQuickStateActions QQuickPropertyAnimation::createTransitionActions(QQuickStateA
if (!d->propertyName.isEmpty())
props << d->propertyName;
- QList<QObject*> targets = d->targets;
+ QList<QPointer<QObject>> targets = d->targets;
if (d->target)
targets.append(d->target);
@@ -2675,10 +2786,14 @@ QQuickStateActions QQuickPropertyAnimation::createTransitionActions(QQuickStateA
for (int i = 0; i < props.size(); ++i) {
for (int j = 0; j < targets.size(); ++j) {
+ const auto& guarded = targets.at(j);
+ if (guarded.isNull())
+ continue;
+ QObject *target = guarded.get();
QQuickStateAction myAction;
QString errorMessage;
const QString &propertyName = props.at(i);
- myAction.property = d->createProperty(targets.at(j), propertyName, this, &errorMessage);
+ myAction.property = d->createProperty(target, propertyName, this, &errorMessage);
if (myAction.property.isValid()) {
if (usingDefaultProperties)
successfullyCreatedDefaultProperty = true;
diff --git a/src/quick/util/qquickanimation_p.h b/src/quick/util/qquickanimation_p.h
index 14f738ea7f..61e52869f5 100644
--- a/src/quick/util/qquickanimation_p.h
+++ b/src/quick/util/qquickanimation_p.h
@@ -31,7 +31,7 @@ QT_BEGIN_NAMESPACE
class QQuickAbstractAnimationPrivate;
class QQuickAnimationGroup;
-class Q_QUICK_PRIVATE_EXPORT QQuickAbstractAnimation : public QObject, public QQmlPropertyValueSource, public QQmlParserStatus
+class Q_QUICK_EXPORT QQuickAbstractAnimation : public QObject, public QQmlPropertyValueSource, public QQmlParserStatus
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickAbstractAnimation)
@@ -125,7 +125,7 @@ private:
};
class QQuickPauseAnimationPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickPauseAnimation : public QQuickAbstractAnimation
+class Q_QUICK_EXPORT QQuickPauseAnimation : public QQuickAbstractAnimation
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickPauseAnimation)
@@ -152,7 +152,7 @@ protected:
};
class QQuickScriptActionPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickScriptAction : public QQuickAbstractAnimation
+class Q_QUICK_EXPORT QQuickScriptAction : public QQuickAbstractAnimation
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickScriptAction)
@@ -180,7 +180,7 @@ protected:
};
class QQuickPropertyActionPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickPropertyAction : public QQuickAbstractAnimation
+class Q_QUICK_EXPORT QQuickPropertyAction : public QQuickAbstractAnimation
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickPropertyAction)
@@ -227,7 +227,7 @@ protected:
};
class QQuickPropertyAnimationPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickPropertyAnimation : public QQuickAbstractAnimation
+class Q_QUICK_EXPORT QQuickPropertyAnimation : public QQuickAbstractAnimation
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickPropertyAnimation)
@@ -292,7 +292,7 @@ Q_SIGNALS:
void propertyChanged();
};
-class Q_QUICK_PRIVATE_EXPORT QQuickColorAnimation : public QQuickPropertyAnimation
+class Q_QUICK_EXPORT QQuickColorAnimation : public QQuickPropertyAnimation
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickPropertyAnimation)
@@ -312,7 +312,7 @@ public:
void setTo(const QColor &);
};
-class Q_QUICK_PRIVATE_EXPORT QQuickNumberAnimation : public QQuickPropertyAnimation
+class Q_QUICK_EXPORT QQuickNumberAnimation : public QQuickPropertyAnimation
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickPropertyAnimation)
@@ -339,7 +339,7 @@ private:
void init();
};
-class Q_QUICK_PRIVATE_EXPORT QQuickVector3dAnimation : public QQuickPropertyAnimation
+class Q_QUICK_EXPORT QQuickVector3dAnimation : public QQuickPropertyAnimation
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickPropertyAnimation)
@@ -361,7 +361,7 @@ public:
};
class QQuickRotationAnimationPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickRotationAnimation : public QQuickPropertyAnimation
+class Q_QUICK_EXPORT QQuickRotationAnimation : public QQuickPropertyAnimation
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickRotationAnimation)
@@ -392,7 +392,7 @@ Q_SIGNALS:
};
class QQuickAnimationGroupPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickAnimationGroup : public QQuickAbstractAnimation
+class Q_QUICK_EXPORT QQuickAnimationGroup : public QQuickAbstractAnimation
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickAnimationGroup)
@@ -411,7 +411,7 @@ protected:
QQuickAnimationGroup(QQuickAnimationGroupPrivate &dd, QObject *parent);
};
-class Q_QUICK_PRIVATE_EXPORT QQuickSequentialAnimation : public QQuickAnimationGroup
+class Q_QUICK_EXPORT QQuickSequentialAnimation : public QQuickAnimationGroup
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickAnimationGroup)
@@ -430,7 +430,7 @@ protected:
QObject *defaultTarget = nullptr) override;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickParallelAnimation : public QQuickAnimationGroup
+class Q_QUICK_EXPORT QQuickParallelAnimation : public QQuickAnimationGroup
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickAnimationGroup)
@@ -452,16 +452,4 @@ protected:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickAbstractAnimation)
-QML_DECLARE_TYPE(QQuickPauseAnimation)
-QML_DECLARE_TYPE(QQuickScriptAction)
-QML_DECLARE_TYPE(QQuickPropertyAction)
-QML_DECLARE_TYPE(QQuickPropertyAnimation)
-QML_DECLARE_TYPE(QQuickColorAnimation)
-QML_DECLARE_TYPE(QQuickNumberAnimation)
-QML_DECLARE_TYPE(QQuickSequentialAnimation)
-QML_DECLARE_TYPE(QQuickParallelAnimation)
-QML_DECLARE_TYPE(QQuickVector3dAnimation)
-QML_DECLARE_TYPE(QQuickRotationAnimation)
-
#endif // QQUICKANIMATION_H
diff --git a/src/quick/util/qquickanimation_p_p.h b/src/quick/util/qquickanimation_p_p.h
index a12a35f070..49f26726ee 100644
--- a/src/quick/util/qquickanimation_p_p.h
+++ b/src/quick/util/qquickanimation_p_p.h
@@ -31,7 +31,7 @@
#include <QDebug>
#include <private/qobject_p.h>
-
+#include <QtCore/qpointer.h>
QT_BEGIN_NAMESPACE
@@ -134,7 +134,7 @@ private:
T *m_instance;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickAbstractAnimationPrivate : public QObjectPrivate, public QAnimationJobChangeListener
+class Q_QUICK_EXPORT QQuickAbstractAnimationPrivate : public QObjectPrivate, public QAnimationJobChangeListener
{
Q_DECLARE_PUBLIC(QQuickAbstractAnimation)
public:
@@ -164,6 +164,7 @@ public:
QAbstractAnimationJob* animationInstance;
static QQmlProperty createProperty(QObject *obj, const QString &str, QObject *infoObj, QString *errorMessage = nullptr);
+ void animationGroupDirty();
};
class QQuickPauseAnimationPrivate : public QQuickAbstractAnimationPrivate
@@ -217,7 +218,7 @@ class QQuickAnimationGroupPrivate : public QQuickAbstractAnimationPrivate
Q_DECLARE_PUBLIC(QQuickAnimationGroup)
public:
QQuickAnimationGroupPrivate()
- : QQuickAbstractAnimationPrivate() {}
+ : QQuickAbstractAnimationPrivate(), animationDirty(false) {}
static void append_animation(QQmlListProperty<QQuickAbstractAnimation> *list, QQuickAbstractAnimation *role);
static QQuickAbstractAnimation *at_animation(QQmlListProperty<QQuickAbstractAnimation> *list, qsizetype index);
@@ -227,9 +228,13 @@ public:
QQuickAbstractAnimation *role);
static void removeLast_animation(QQmlListProperty<QQuickAbstractAnimation> *list);
QList<QQuickAbstractAnimation *> animations;
+
+ void restartFromCurrentLoop();
+ void animationCurrentLoopChanged(QAbstractAnimationJob *job) override;
+ bool animationDirty: 1;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickPropertyAnimationPrivate : public QQuickAbstractAnimationPrivate
+class Q_QUICK_EXPORT QQuickPropertyAnimationPrivate : public QQuickAbstractAnimationPrivate
{
Q_DECLARE_PUBLIC(QQuickPropertyAnimation)
public:
@@ -245,7 +250,7 @@ public:
QObject *target;
QString propertyName;
QString properties;
- QList<QObject *> targets;
+ QList<QPointer<QObject>> targets;
QList<QObject *> exclude;
QString defaultProperties;
diff --git a/src/quick/util/qquickanimationcontroller_p.h b/src/quick/util/qquickanimationcontroller_p.h
index 8150fb9709..6ce1fcc7a3 100644
--- a/src/quick/util/qquickanimationcontroller_p.h
+++ b/src/quick/util/qquickanimationcontroller_p.h
@@ -22,7 +22,7 @@
QT_BEGIN_NAMESPACE
class QQuickAnimationControllerPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickAnimationController : public QObject, public QQmlFinalizerHook
+class Q_QUICK_EXPORT QQuickAnimationController : public QObject, public QQmlFinalizerHook
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(QQuickAnimationController)
@@ -60,6 +60,4 @@ private Q_SLOTS:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickAnimationController)
-
#endif // QQUICKANIMATIONCONTROLLER_H
diff --git a/src/quick/util/qquickanimator.cpp b/src/quick/util/qquickanimator.cpp
index 7b73e9e59b..986f695fef 100644
--- a/src/quick/util/qquickanimator.cpp
+++ b/src/quick/util/qquickanimator.cpp
@@ -247,7 +247,8 @@ QAbstractAnimationJob *QQuickAnimator::transition(QQuickStateActions &actions,
Q_D(QQuickAnimator);
if (d->defaultProperty.isValid() && propertyName() != d->defaultProperty.name()) {
- qDebug() << Q_FUNC_INFO << "property name conflict...";
+ qmlWarning(this) << "property name conflict: \""
+ << propertyName() << "\" != \"" << d->defaultProperty.name() << "\"";
return nullptr;
}
@@ -448,14 +449,16 @@ QQuickAnimatorJob *QQuickRotationAnimator::createJob() const {
Possible values are:
- \list
- \li RotationAnimator.Numerical (default) - Rotate by linearly interpolating between the two numbers.
- A rotation from 10 to 350 will rotate 340 degrees clockwise.
- \li RotationAnimator.Clockwise - Rotate clockwise between the two values
- \li RotationAnimator.Counterclockwise - Rotate counterclockwise between the two values
- \li RotationAnimator.Shortest - Rotate in the direction that produces the shortest animation path.
- A rotation from 10 to 350 will rotate 20 degrees counterclockwise.
- \endlist
+ \value RotationAnimator.Numerical
+ (default) Rotate by linearly interpolating between the two numbers.
+ A rotation from 10 to 350 will rotate 340 degrees clockwise.
+ \value RotationAnimator.Clockwise
+ Rotate clockwise between the two values
+ \value RotationAnimator.Counterclockwise
+ Rotate counterclockwise between the two values
+ \value RotationAnimator.Shortest
+ Rotate in the direction that produces the shortest animation path.
+ A rotation from 10 to 350 will rotate 20 degrees counterclockwise.
*/
void QQuickRotationAnimator::setDirection(RotationDirection dir)
{
diff --git a/src/quick/util/qquickanimator_p.h b/src/quick/util/qquickanimator_p.h
index 0dd5738464..fc864359c7 100644
--- a/src/quick/util/qquickanimator_p.h
+++ b/src/quick/util/qquickanimator_p.h
@@ -23,7 +23,7 @@ class QQuickItem;
class QQuickAnimatorJob;
class QQuickAnimatorPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickAnimator : public QQuickAbstractAnimation
+class Q_QUICK_EXPORT QQuickAnimator : public QQuickAbstractAnimation
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickAnimator)
@@ -73,7 +73,7 @@ Q_SIGNALS:
void fromChanged(qreal from);
};
-class Q_QUICK_PRIVATE_EXPORT QQuickScaleAnimator : public QQuickAnimator
+class Q_QUICK_EXPORT QQuickScaleAnimator : public QQuickAnimator
{
Q_OBJECT
QML_NAMED_ELEMENT(ScaleAnimator)
@@ -85,7 +85,7 @@ protected:
QString propertyName() const override { return QStringLiteral("scale"); }
};
-class Q_QUICK_PRIVATE_EXPORT QQuickXAnimator : public QQuickAnimator
+class Q_QUICK_EXPORT QQuickXAnimator : public QQuickAnimator
{
Q_OBJECT
QML_NAMED_ELEMENT(XAnimator)
@@ -97,7 +97,7 @@ protected:
QString propertyName() const override { return QStringLiteral("x"); }
};
-class Q_QUICK_PRIVATE_EXPORT QQuickYAnimator : public QQuickAnimator
+class Q_QUICK_EXPORT QQuickYAnimator : public QQuickAnimator
{
Q_OBJECT
QML_NAMED_ELEMENT(YAnimator)
@@ -109,7 +109,7 @@ protected:
QString propertyName() const override { return QStringLiteral("y"); }
};
-class Q_QUICK_PRIVATE_EXPORT QQuickOpacityAnimator : public QQuickAnimator
+class Q_QUICK_EXPORT QQuickOpacityAnimator : public QQuickAnimator
{
Q_OBJECT
QML_NAMED_ELEMENT(OpacityAnimator)
@@ -122,7 +122,7 @@ protected:
};
class QQuickRotationAnimatorPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickRotationAnimator : public QQuickAnimator
+class Q_QUICK_EXPORT QQuickRotationAnimator : public QQuickAnimator
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickRotationAnimator)
@@ -149,7 +149,7 @@ protected:
#if QT_CONFIG(quick_shadereffect)
class QQuickUniformAnimatorPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickUniformAnimator : public QQuickAnimator
+class Q_QUICK_EXPORT QQuickUniformAnimator : public QQuickAnimator
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickUniformAnimator)
@@ -174,13 +174,4 @@ protected:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickAnimator)
-QML_DECLARE_TYPE(QQuickXAnimator)
-QML_DECLARE_TYPE(QQuickYAnimator)
-QML_DECLARE_TYPE(QQuickScaleAnimator)
-QML_DECLARE_TYPE(QQuickRotationAnimator)
-QML_DECLARE_TYPE(QQuickOpacityAnimator)
-#if QT_CONFIG(quick_shadereffect)
-QML_DECLARE_TYPE(QQuickUniformAnimator)
-#endif
#endif // QQUICKANIMATOR_P_H
diff --git a/src/quick/util/qquickanimator_p_p.h b/src/quick/util/qquickanimator_p_p.h
index 29dd34a8e8..d138fc4f72 100644
--- a/src/quick/util/qquickanimator_p_p.h
+++ b/src/quick/util/qquickanimator_p_p.h
@@ -19,6 +19,8 @@
#include "qquickanimation_p_p.h"
#include <QtQuick/qquickitem.h>
+#include <QtCore/qpointer.h>
+
QT_BEGIN_NAMESPACE
class QQuickAnimatorJob;
diff --git a/src/quick/util/qquickanimatorjob.cpp b/src/quick/util/qquickanimatorjob.cpp
index 1ac87b6b74..83d5181c34 100644
--- a/src/quick/util/qquickanimatorjob.cpp
+++ b/src/quick/util/qquickanimatorjob.cpp
@@ -592,7 +592,7 @@ void QQuickUniformAnimatorJob::setTarget(QQuickItem *target)
void QQuickUniformAnimatorJob::updateCurrentTime(int time)
{
- if (!m_effect)
+ if (!m_effect || m_target != m_effect)
return;
m_value = m_from + (m_to - m_from) * progress(time);
@@ -617,7 +617,7 @@ void QQuickUniformAnimatorJob::postSync()
void QQuickUniformAnimatorJob::invalidate()
{
- m_effect = nullptr;
+
}
#endif
diff --git a/src/quick/util/qquickanimatorjob_p.h b/src/quick/util/qquickanimatorjob_p.h
index fef29ac385..16e4a7063d 100644
--- a/src/quick/util/qquickanimatorjob_p.h
+++ b/src/quick/util/qquickanimatorjob_p.h
@@ -23,6 +23,7 @@
#include <QtQuick/qquickitem.h>
#include <QtCore/qeasingcurve.h>
+#include <QtCore/qpointer.h>
QT_BEGIN_NAMESPACE
@@ -35,7 +36,7 @@ class QQuickAnimatorController;
class QSGOpacityNode;
-class Q_QUICK_PRIVATE_EXPORT QQuickAnimatorProxyJob : public QObject, public QAbstractAnimationJob
+class Q_QUICK_EXPORT QQuickAnimatorProxyJob : public QObject, public QAbstractAnimationJob
{
Q_OBJECT
@@ -77,7 +78,7 @@ private:
InternalState m_internalState;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickAnimatorJob : public QAbstractAnimationJob
+class Q_QUICK_EXPORT QQuickAnimatorJob : public QAbstractAnimationJob
{
public:
virtual void setTarget(QQuickItem *target);
@@ -209,28 +210,28 @@ protected:
Helper *m_helper;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickScaleAnimatorJob : public QQuickTransformAnimatorJob
+class Q_QUICK_EXPORT QQuickScaleAnimatorJob : public QQuickTransformAnimatorJob
{
public:
void updateCurrentTime(int time) override;
void writeBack() override;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickXAnimatorJob : public QQuickTransformAnimatorJob
+class Q_QUICK_EXPORT QQuickXAnimatorJob : public QQuickTransformAnimatorJob
{
public:
void updateCurrentTime(int time) override;
void writeBack() override;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickYAnimatorJob : public QQuickTransformAnimatorJob
+class Q_QUICK_EXPORT QQuickYAnimatorJob : public QQuickTransformAnimatorJob
{
public:
void updateCurrentTime(int time) override;
void writeBack() override;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickRotationAnimatorJob : public QQuickTransformAnimatorJob
+class Q_QUICK_EXPORT QQuickRotationAnimatorJob : public QQuickTransformAnimatorJob
{
public:
QQuickRotationAnimatorJob();
@@ -245,7 +246,7 @@ private:
QQuickRotationAnimator::RotationDirection m_direction;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickOpacityAnimatorJob : public QQuickAnimatorJob
+class Q_QUICK_EXPORT QQuickOpacityAnimatorJob : public QQuickAnimatorJob
{
public:
QQuickOpacityAnimatorJob();
@@ -262,7 +263,7 @@ private:
#if QT_CONFIG(quick_shadereffect)
class QQuickShaderEffect;
-class Q_QUICK_PRIVATE_EXPORT QQuickUniformAnimatorJob : public QQuickAnimatorJob
+class Q_QUICK_EXPORT QQuickUniformAnimatorJob : public QQuickAnimatorJob
{
public:
QQuickUniformAnimatorJob();
@@ -280,7 +281,7 @@ public:
private:
QByteArray m_uniform;
- QQuickShaderEffect *m_effect = nullptr;
+ QPointer<QQuickShaderEffect> m_effect;
};
#endif
diff --git a/src/quick/util/qquickapplication.cpp b/src/quick/util/qquickapplication.cpp
index bee3b7313f..2cb8fe36ba 100644
--- a/src/quick/util/qquickapplication.cpp
+++ b/src/quick/util/qquickapplication.cpp
@@ -225,6 +225,7 @@ QQuickApplication::QQuickApplication(QObject *parent)
connect(guiApp, &QGuiApplication::applicationDisplayNameChanged,
this, &QQuickApplication::displayNameChanged);
+ connect(guiApp, &QGuiApplication::primaryScreenChanged, this, &QQuickApplication::updateScreens);
connect(guiApp, &QGuiApplication::screenAdded, this, &QQuickApplication::updateScreens);
connect(guiApp, &QGuiApplication::screenRemoved, this, &QQuickApplication::updateScreens);
updateScreens();
diff --git a/src/quick/util/qquickapplication_p.h b/src/quick/util/qquickapplication_p.h
index 680e25063d..540411756b 100644
--- a/src/quick/util/qquickapplication_p.h
+++ b/src/quick/util/qquickapplication_p.h
@@ -28,17 +28,17 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QQuickApplication : public QQmlApplication
+class Q_QUICK_EXPORT QQuickApplication : public QQmlApplication
{
Q_OBJECT
- Q_PROPERTY(bool active READ active NOTIFY activeChanged) // deprecated, use 'state' instead
- Q_PROPERTY(Qt::LayoutDirection layoutDirection READ layoutDirection NOTIFY layoutDirectionChanged)
- Q_PROPERTY(bool supportsMultipleWindows READ supportsMultipleWindows CONSTANT)
- Q_PROPERTY(Qt::ApplicationState state READ state NOTIFY stateChanged)
- Q_PROPERTY(QFont font READ font CONSTANT)
- Q_PROPERTY(QString displayName READ displayName WRITE setDisplayName NOTIFY displayNameChanged)
- Q_PROPERTY(QQmlListProperty<QQuickScreenInfo> screens READ screens NOTIFY screensChanged)
- Q_PROPERTY(QStyleHints *styleHints READ styleHints CONSTANT)
+ Q_PROPERTY(bool active READ active NOTIFY activeChanged FINAL) // deprecated, use 'state' instead
+ Q_PROPERTY(Qt::LayoutDirection layoutDirection READ layoutDirection NOTIFY layoutDirectionChanged FINAL)
+ Q_PROPERTY(bool supportsMultipleWindows READ supportsMultipleWindows CONSTANT FINAL)
+ Q_PROPERTY(Qt::ApplicationState state READ state NOTIFY stateChanged FINAL)
+ Q_PROPERTY(QFont font READ font CONSTANT FINAL)
+ Q_PROPERTY(QString displayName READ displayName WRITE setDisplayName NOTIFY displayNameChanged FINAL)
+ Q_PROPERTY(QQmlListProperty<QQuickScreenInfo> screens READ screens NOTIFY screensChanged FINAL)
+ Q_PROPERTY(QStyleHints *styleHints READ styleHints CONSTANT FINAL)
QML_NAMED_ELEMENT(Application)
QML_SINGLETON
@@ -74,6 +74,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickApplication)
-
#endif // QQUICKAPPLICATION_P_H
diff --git a/src/quick/util/qquickbehavior.cpp b/src/quick/util/qquickbehavior.cpp
index 2522db37eb..dd8ddad7ca 100644
--- a/src/quick/util/qquickbehavior.cpp
+++ b/src/quick/util/qquickbehavior.cpp
@@ -14,6 +14,7 @@
#include <private/qquickanimatorjob_p.h>
#include <private/qobject_p.h>
+#include <QtCore/qpointer.h>
QT_BEGIN_NAMESPACE
@@ -151,7 +152,7 @@ public:
UntypedProxyProperty::UntypedProxyProperty(QUntypedBindable bindable, QQuickBehaviorPrivate *behavior) :
m_sourcePropertyData(QUntypedBindablePrivate::getPropertyData(bindable)),
m_sourceInterface(QUntypedBindablePrivate::getInterface(bindable)),
- m_storage(QVariant(QUntypedBindablePrivate::getInterface(bindable)->metaType()))
+ m_storage(QVariant(bindable.metaType()))
{
behavior->setSource(m_bindingData);
}
@@ -182,7 +183,7 @@ UntypedProxyProperty::UntypedProxyProperty(QUntypedBindable bindable, QQuickBeha
state change. For general advice on using Behaviors to animate state changes, see
\l{Using Qt Quick Behaviors with States}.
- \sa {Animation and Transitions in Qt Quick}, {Qt Quick Examples - Animation#Behaviors}{Behavior example}, {Qt QML}
+ \sa {Animation and Transitions in Qt Quick}, {Qt Quick Examples - Animation#Behaviors}{Behavior example}, {Qt Qml}
*/
@@ -406,7 +407,7 @@ void QQuickBehavior::write(const QVariant &value)
QList<QQmlProperty> after;
auto *newInstance = d->animation->transition(actions, after, QQuickAbstractAnimation::Forward);
- Q_ASSERT(newInstance != d->animationInstance);
+ Q_ASSERT(!newInstance || newInstance != d->animationInstance);
delete d->animationInstance;
d->animationInstance = newInstance;
diff --git a/src/quick/util/qquickbehavior_p.h b/src/quick/util/qquickbehavior_p.h
index 416417b178..f9b34eee19 100644
--- a/src/quick/util/qquickbehavior_p.h
+++ b/src/quick/util/qquickbehavior_p.h
@@ -26,7 +26,7 @@ QT_BEGIN_NAMESPACE
class QQuickAbstractAnimation;
class QQuickBehaviorPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickBehavior : public QObject, public QQmlPropertyValueInterceptor, public QQmlFinalizerHook
+class Q_QUICK_EXPORT QQuickBehavior : public QObject, public QQmlPropertyValueInterceptor, public QQmlFinalizerHook
{
Q_OBJECT
Q_INTERFACES(QQmlFinalizerHook)
@@ -70,6 +70,4 @@ Q_SIGNALS:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickBehavior)
-
#endif // QQUICKBEHAVIOR_H
diff --git a/src/quick/util/qquickdeliveryagent.cpp b/src/quick/util/qquickdeliveryagent.cpp
index 794ac70a25..acb082af82 100644
--- a/src/quick/util/qquickdeliveryagent.cpp
+++ b/src/quick/util/qquickdeliveryagent.cpp
@@ -18,21 +18,21 @@
#include <QtQuick/private/qquickrendercontrol_p.h>
#include <QtQuick/private/qquickwindow_p.h>
+#include <QtCore/qpointer.h>
+
#include <memory>
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcTouch, "qt.quick.touch")
-Q_LOGGING_CATEGORY(lcTouchCmprs, "qt.quick.touch.compression")
+Q_STATIC_LOGGING_CATEGORY(lcTouchCmprs, "qt.quick.touch.compression")
Q_LOGGING_CATEGORY(lcTouchTarget, "qt.quick.touch.target")
Q_LOGGING_CATEGORY(lcMouse, "qt.quick.mouse")
-Q_LOGGING_CATEGORY(lcMouseTarget, "qt.quick.mouse.target")
-Q_LOGGING_CATEGORY(lcTablet, "qt.quick.tablet")
+Q_STATIC_LOGGING_CATEGORY(lcMouseTarget, "qt.quick.mouse.target")
+Q_STATIC_LOGGING_CATEGORY(lcTablet, "qt.quick.tablet")
Q_LOGGING_CATEGORY(lcPtr, "qt.quick.pointer")
-Q_LOGGING_CATEGORY(lcPtrLoc, "qt.quick.pointer.localization")
-Q_LOGGING_CATEGORY(lcPtrGrab, "qt.quick.pointer.grab")
-Q_LOGGING_CATEGORY(lcWheelTarget, "qt.quick.wheel.target")
-Q_LOGGING_CATEGORY(lcGestureTarget, "qt.quick.gesture.target")
+Q_STATIC_LOGGING_CATEGORY(lcPtrLoc, "qt.quick.pointer.localization")
+Q_STATIC_LOGGING_CATEGORY(lcWheelTarget, "qt.quick.wheel.target")
Q_LOGGING_CATEGORY(lcHoverTrace, "qt.quick.hover.trace")
Q_LOGGING_CATEGORY(lcFocus, "qt.quick.focus")
@@ -96,6 +96,14 @@ bool QQuickDeliveryAgentPrivate::checkIfDoubleTapped(ulong newPressEventTimestam
return doubleClicked;
}
+/*! \internal
+ \deprecated events are handled by methods in which the event is an argument
+
+ Accessor for use by legacy methods such as QQuickItem::grabMouse(),
+ QQuickItem::ungrabMouse(), and QQuickItem::grabTouchPoints() which
+ are not given sufficient context to do the grabbing.
+ We should remove eventsInDelivery in Qt 7.
+*/
QPointerEvent *QQuickDeliveryAgentPrivate::eventInDelivery() const
{
if (eventsInDelivery.isEmpty())
@@ -305,6 +313,16 @@ static inline bool windowHasFocus(QQuickWindow *win)
return win == focusWindow || QQuickRenderControlPrivate::isRenderWindowFor(win, focusWindow) || !focusWindow;
}
+static QQuickItem *findFurthestFocusScopeAncestor(QQuickItem *item)
+{
+ QQuickItem *parentItem = item->parentItem();
+
+ if (parentItem && parentItem->flags() & QQuickItem::ItemIsFocusScope)
+ return findFurthestFocusScopeAncestor(parentItem);
+
+ return item;
+}
+
#ifdef Q_OS_WEBOS
// Temporary fix for webOS until multi-seat is implemented see QTBUG-85272
static inline bool singleWindowOnScreen(QQuickWindow *win)
@@ -408,7 +426,7 @@ void QQuickDeliveryAgentPrivate::setFocusInScope(QQuickItem *scope, QQuickItem *
}
}
- if (newActiveFocusItem && rootItem->hasFocus()) {
+ if (newActiveFocusItem && (rootItem->hasFocus() || (rootItem->window()->type() == Qt::Popup))) {
activeFocusItem = newActiveFocusItem;
QQuickItemPrivate::get(newActiveFocusItem)->activeFocus = true;
@@ -447,6 +465,16 @@ void QQuickDeliveryAgentPrivate::setFocusInScope(QQuickItem *scope, QQuickItem *
if (isSubsceneAgent) {
auto da = QQuickWindowPrivate::get(rootItem->window())->deliveryAgent;
qCDebug(lcFocus) << " delegating setFocusInScope to" << da;
+
+ // When setting subFocusItem, hierarchy is important. Each focus ancestor's
+ // subFocusItem must be its nearest descendant with focus. Changing the rootItem's
+ // subFocusItem to 'item' here would make 'item' the subFocusItem of all ancestor
+ // focus scopes up until root item.
+ // That is why we should avoid altering subFocusItem until having traversed
+ // all the focus hierarchy.
+ QQuickItem *ancestorFS = findFurthestFocusScopeAncestor(item);
+ if (ancestorFS != item)
+ options |= QQuickDeliveryAgentPrivate::DontChangeSubFocusItem;
QQuickWindowPrivate::get(rootItem->window())->deliveryAgentPrivate()->setFocusInScope(da->rootItem(), item, reason, options);
}
if (oldActiveFocusItem == activeFocusItem)
@@ -599,16 +627,14 @@ bool QQuickDeliveryAgentPrivate::clearHover(ulong timestamp)
const QPointF lastPos = window->mapFromGlobal(QGuiApplicationPrivate::lastCursorPosition);
const auto modifiers = QGuiApplication::keyboardModifiers();
- const bool clearHover = true;
- for (auto hoverItem : hoverItems) {
- auto item = hoverItem.first;
- if (item)
- deliverHoverEventToItem(item, lastPos, lastPos, modifiers, timestamp, clearHover);
+ for (const auto &[item, id] : hoverItems) {
+ if (item) {
+ deliverHoverEventToItem(item, lastPos, lastPos, modifiers, timestamp, HoverChange::Clear);
+ Q_ASSERT(id == 0);
+ }
}
- hoverItems.clear();
-
return true;
}
@@ -624,6 +650,27 @@ void QQuickDeliveryAgentPrivate::updateFocusItemTransform()
#endif
}
+/*!
+ Returns the item that should get active focus when the
+ root focus scope gets active focus.
+*/
+QQuickItem *QQuickDeliveryAgentPrivate::focusTargetItem() const
+{
+ if (activeFocusItem)
+ return activeFocusItem;
+
+ Q_ASSERT(rootItem);
+ QQuickItem *targetItem = rootItem;
+
+ while (targetItem->isFocusScope()
+ && targetItem->scopedFocusItem()
+ && targetItem->scopedFocusItem()->isEnabled()) {
+ targetItem = targetItem->scopedFocusItem();
+ }
+
+ return targetItem;
+}
+
/*! \internal
If called during event delivery, returns the agent that is delivering the
event, without checking whether \a item is reachable from there.
@@ -657,6 +704,10 @@ QQuickDeliveryAgent::Transform::~Transform()
{
}
+/*! \internal
+ Get the QQuickRootItem or subscene root item on behalf of which
+ this delivery agent was constructed to handle events.
+*/
QQuickItem *QQuickDeliveryAgent::rootItem() const
{
Q_D(const QQuickDeliveryAgent);
@@ -690,6 +741,13 @@ void QQuickDeliveryAgent::setSceneTransform(QQuickDeliveryAgent::Transform *tran
d->sceneTransform = transform;
}
+/*!
+ Handle \a ev on behalf of this delivery agent's window or subscene.
+
+ This is the usual main entry point for every incoming event:
+ QQuickWindow::event() and QQuick3DViewport::forwardEventToSubscenes()
+ both call this function.
+*/
bool QQuickDeliveryAgent::event(QEvent *ev)
{
Q_D(QQuickDeliveryAgent);
@@ -782,16 +840,7 @@ bool QQuickDeliveryAgent::event(QEvent *ev)
case QEvent::InputMethod:
case QEvent::InputMethodQuery:
{
- QQuickItem *target = d->activeFocusItem;
- // while an input method delivers the event, this window might still be inactive
- if (!target) {
- target = d->rootItem;
- if (!target || !target->isEnabled())
- break;
- // see setFocusInScope for a similar loop
- while (target->isFocusScope() && target->scopedFocusItem() && target->scopedFocusItem()->isEnabled())
- target = target->scopedFocusItem();
- }
+ QQuickItem *target = d->focusTargetItem();
if (target)
QCoreApplication::sendEvent(target, ev);
}
@@ -1018,8 +1067,7 @@ bool QQuickDeliveryAgentPrivate::deliverHoverEvent(
// Prune the list for items that are no longer hovered
for (auto it = hoverItems.begin(); it != hoverItems.end();) {
- auto item = (*it).first.data();
- auto hoverId = (*it).second;
+ const auto &[item, hoverId] = *it;
if (hoverId == currentHoverId) {
// Still being hovered
it++;
@@ -1027,10 +1075,8 @@ bool QQuickDeliveryAgentPrivate::deliverHoverEvent(
// No longer hovered. If hoverId is 0, it means that we have sent a HoverLeave
// event to the item already, and it can just be removed from the list. Note that
// the item can have been deleted as well.
- if (item && hoverId != 0) {
- const bool clearHover = true;
- deliverHoverEventToItem(item, scenePos, lastScenePos, modifiers, timestamp, clearHover);
- }
+ if (item && hoverId != 0)
+ deliverHoverEventToItem(item, scenePos, lastScenePos, modifiers, timestamp, HoverChange::Clear);
it = hoverItems.erase(it);
}
}
@@ -1111,10 +1157,7 @@ bool QQuickDeliveryAgentPrivate::deliverHoverEventRecursive(
// All decendants have been visited.
// Now deliver the event to the item
- return deliverHoverEventToItem(item, scenePos, lastScenePos, modifiers, timestamp, false);
-
- // Continue propagation / recursion
- return false;
+ return deliverHoverEventToItem(item, scenePos, lastScenePos, modifiers, timestamp, HoverChange::Set);
}
/*! \internal
@@ -1127,13 +1170,14 @@ bool QQuickDeliveryAgentPrivate::deliverHoverEventRecursive(
*/
bool QQuickDeliveryAgentPrivate::deliverHoverEventToItem(
QQuickItem *item, const QPointF &scenePos, const QPointF &lastScenePos,
- Qt::KeyboardModifiers modifiers, ulong timestamp, bool clearHover)
+ Qt::KeyboardModifiers modifiers, ulong timestamp, HoverChange hoverChange)
{
QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
const QPointF localPos = item->mapFromScene(scenePos);
const QPointF globalPos = item->mapToGlobal(localPos);
const bool isHovering = item->contains(localPos);
- const bool wasHovering = hoverItems.contains(item);
+ const auto hoverItemIterator = hoverItems.find(item);
+ const bool wasHovering = hoverItemIterator != hoverItems.end() && hoverItemIterator.value() != 0;
qCDebug(lcHoverTrace) << "item:" << item << "scene pos:" << scenePos << "localPos:" << localPos
<< "wasHovering:" << wasHovering << "isHovering:" << isHovering;
@@ -1143,20 +1187,24 @@ bool QQuickDeliveryAgentPrivate::deliverHoverEventToItem(
// Start by sending out enter/move/leave events to the item.
// Note that hoverEnabled only controls if we should send out hover events to the
// item itself. HoverHandlers are not included, and are dealt with separately below.
- if (itemPrivate->hoverEnabled && isHovering && !clearHover) {
+ if (itemPrivate->hoverEnabled && isHovering && hoverChange == HoverChange::Set) {
// Add the item to the list of hovered items (if it doesn't exist there
// from before), and update hoverId to mark that it's (still) hovered.
// Also set hoveredLeafItemFound, so that only propagate in a straight
// line towards the root from now on.
hoveredLeafItemFound = true;
- hoverItems[item] = currentHoverId;
+ if (hoverItemIterator != hoverItems.end())
+ hoverItemIterator.value() = currentHoverId;
+ else
+ hoverItems[item] = currentHoverId;
+
if (wasHovering)
accepted = sendHoverEvent(QEvent::HoverMove, item, scenePos, lastScenePos, modifiers, timestamp);
else
accepted = sendHoverEvent(QEvent::HoverEnter, item, scenePos, lastScenePos, modifiers, timestamp);
} else if (wasHovering) {
// A leave should never stop propagation
- hoverItems[item] = 0;
+ hoverItemIterator.value() = 0;
sendHoverEvent(QEvent::HoverLeave, item, scenePos, lastScenePos, modifiers, timestamp);
}
@@ -1170,7 +1218,7 @@ bool QQuickDeliveryAgentPrivate::deliverHoverEventToItem(
// Note that since a HoverHandler can have a margin, a HoverHandler
// can be hovered even if the item itself is not.
- if (clearHover) {
+ if (hoverChange == HoverChange::Clear) {
// Note: a leave should never stop propagation
QHoverEvent hoverEvent(QEvent::HoverLeave, scenePos, globalPos, lastScenePos, modifiers);
hoverEvent.setTimestamp(timestamp);
@@ -1197,7 +1245,10 @@ bool QQuickDeliveryAgentPrivate::deliverHoverEventToItem(
// Mark the whole item as updated, even if only the handler is
// actually in a hovered state (because of HoverHandler.margins)
hoveredLeafItemFound = true;
- hoverItems[item] = currentHoverId;
+ if (hoverItemIterator != hoverItems.end())
+ hoverItemIterator.value() = currentHoverId;
+ else
+ hoverItems[item] = currentHoverId;
if (hh->isBlocking()) {
qCDebug(lcHoverTrace) << "skipping rest of hover delivery due to blocking" << hh;
accepted = true;
@@ -1321,6 +1372,13 @@ void QQuickDeliveryAgentPrivate::handleWindowDeactivate(QQuickWindow *win)
}
}
+void QQuickDeliveryAgentPrivate::handleWindowHidden(QQuickWindow *win)
+{
+ qCDebug(lcFocus) << "hidden" << win->title();
+ clearHover();
+ lastMousePosition = QPointF();
+}
+
bool QQuickDeliveryAgentPrivate::allUpdatedPointsAccepted(const QPointerEvent *ev)
{
for (auto &point : ev->points()) {
@@ -1595,6 +1653,9 @@ void QQuickDeliveryAgentPrivate::handleTouchEvent(QTouchEvent *event)
}
}
+/*!
+ Handle \a event on behalf of this delivery agent's window or subscene.
+*/
void QQuickDeliveryAgentPrivate::handleMouseEvent(QMouseEvent *event)
{
Q_Q(QQuickDeliveryAgent);
@@ -1651,6 +1712,19 @@ void QQuickDeliveryAgentPrivate::handleMouseEvent(QMouseEvent *event)
}
}
+/*! \internal
+ Flush events before a frame is rendered in \a win.
+
+ This is here because of compressTouchEvent(): we need to ensure that
+ coalesced touch events are actually delivered in time to cause the desired
+ reactions of items and their handlers. And then since it was introduced
+ because of that, we started using this function for once-per-frame hover
+ events too, to take care of changing hover state when an item animates
+ under the mouse cursor at a time that the mouse cursor is not moving.
+
+ This is done before QQuickItem::updatePolish() is called on all the items
+ that requested polishing.
+*/
void QQuickDeliveryAgentPrivate::flushFrameSynchronousEvents(QQuickWindow *win)
{
Q_Q(QQuickDeliveryAgent);
@@ -1679,7 +1753,13 @@ void QQuickDeliveryAgentPrivate::flushFrameSynchronousEvents(QQuickWindow *win)
if (frameSynchronousHoverEnabled && !win->mouseGrabberItem() &&
!lastMousePosition.isNull() && QQuickWindowPrivate::get(win)->dirtyItemList) {
qCDebug(lcHoverTrace) << q << "delivering frame-sync hover to root @" << lastMousePosition;
- deliverHoverEvent(lastMousePosition, lastMousePosition, QGuiApplication::keyboardModifiers(), 0);
+ if (deliverHoverEvent(lastMousePosition, lastMousePosition, QGuiApplication::keyboardModifiers(), 0)) {
+#if QT_CONFIG(cursor)
+ QQuickWindowPrivate::get(rootItem->window())->updateCursor(
+ sceneTransform ? sceneTransform->map(lastMousePosition) : lastMousePosition, rootItem);
+#endif
+ }
+
qCDebug(lcHoverTrace) << q << "frame-sync hover delivery done";
}
#else
@@ -1691,6 +1771,14 @@ void QQuickDeliveryAgentPrivate::flushFrameSynchronousEvents(QQuickWindow *win)
QQuickDeliveryAgentPrivate::currentEventDeliveryAgent = deliveringAgent;
}
+/*! \internal
+ React to the fact that \a grabber underwent a grab \a transition
+ while an item or handler was handling \a point from \a event.
+ I.e. handle the QPointingDevice::grabChanged() signal.
+
+ This notifies the relevant items and/or pointer handlers, and
+ does cleanup when grabs are lost or relinquished.
+*/
void QQuickDeliveryAgentPrivate::onGrabChanged(QObject *grabber, QPointingDevice::GrabTransition transition,
const QPointerEvent *event, const QEventPoint &point)
{
@@ -1698,14 +1786,11 @@ void QQuickDeliveryAgentPrivate::onGrabChanged(QObject *grabber, QPointingDevice
const bool grabGained = (transition == QPointingDevice::GrabTransition::GrabExclusive ||
transition == QPointingDevice::GrabTransition::GrabPassive);
- QQuickDeliveryAgent *deliveryAgent = nullptr;
-
// note: event can be null, if the signal was emitted from QPointingDevicePrivate::removeGrabber(grabber)
if (auto *handler = qmlobject_cast<QQuickPointerHandler *>(grabber)) {
if (handler->parentItem()) {
auto itemPriv = QQuickItemPrivate::get(handler->parentItem());
- deliveryAgent = itemPriv->deliveryAgent();
- if (deliveryAgent == q) {
+ if (itemPriv->deliveryAgent() == q) {
handler->onGrabChanged(handler, transition, const_cast<QPointerEvent *>(event),
const_cast<QEventPoint &>(point));
}
@@ -1719,50 +1804,42 @@ void QQuickDeliveryAgentPrivate::onGrabChanged(QObject *grabber, QPointingDevice
handler->onGrabChanged(handler, transition, const_cast<QPointerEvent *>(event),
const_cast<QEventPoint &>(point));
}
- } else {
+ } else if (auto *grabberItem = qmlobject_cast<QQuickItem *>(grabber)) {
switch (transition) {
case QPointingDevice::CancelGrabExclusive:
case QPointingDevice::UngrabExclusive:
- if (auto *item = qmlobject_cast<QQuickItem *>(grabber)) {
- bool filtered = false;
- if (isDeliveringTouchAsMouse() ||
- point.device()->type() == QInputDevice::DeviceType::Mouse ||
- point.device()->type() == QInputDevice::DeviceType::TouchPad) {
- QMutableSinglePointEvent e(QEvent::UngrabMouse, point.device(), point);
- hasFiltered.clear();
- filtered = sendFilteredMouseEvent(&e, item, item->parentItem());
- if (!filtered) {
- lastUngrabbed = item;
- item->mouseUngrabEvent();
- }
+ if (isDeliveringTouchAsMouse()
+ || point.device()->type() == QInputDevice::DeviceType::Mouse
+ || point.device()->type() == QInputDevice::DeviceType::TouchPad) {
+ QMutableSinglePointEvent e(QEvent::UngrabMouse, point.device(), point);
+ hasFiltered.clear();
+ if (!sendFilteredMouseEvent(&e, grabberItem, grabberItem->parentItem())) {
+ lastUngrabbed = grabberItem;
+ grabberItem->mouseUngrabEvent();
}
- if (point.device()->type() == QInputDevice::DeviceType::TouchScreen) {
- bool allReleasedOrCancelled = true;
- if (transition == QPointingDevice::UngrabExclusive && event) {
- for (const auto &pt : event->points()) {
- if (pt.state() != QEventPoint::State::Released) {
- allReleasedOrCancelled = false;
- break;
- }
+ }
+ if (point.device()->type() == QInputDevice::DeviceType::TouchScreen) {
+ bool allReleasedOrCancelled = true;
+ if (transition == QPointingDevice::UngrabExclusive && event) {
+ for (const auto &pt : event->points()) {
+ if (pt.state() != QEventPoint::State::Released) {
+ allReleasedOrCancelled = false;
+ break;
}
}
- if (allReleasedOrCancelled)
- item->touchUngrabEvent();
}
+ if (allReleasedOrCancelled)
+ grabberItem->touchUngrabEvent();
}
break;
default:
break;
}
- auto grabberItem = static_cast<QQuickItem *>(grabber); // cannot be a handler: we checked above
- if (grabberItem) {
- auto itemPriv = QQuickItemPrivate::get(grabberItem);
- deliveryAgent = itemPriv->deliveryAgent();
- // An item that is NOT a subscene root needs to track whether it got a grab via a subscene delivery agent,
- // whereas the subscene root item already knows it has its own DA.
- if (isSubsceneAgent && grabGained && (!itemPriv->extra.isAllocated() || !itemPriv->extra->subsceneDeliveryAgent))
- itemPriv->maybeHasSubsceneDeliveryAgent = true;
- }
+ auto *itemPriv = QQuickItemPrivate::get(grabberItem);
+ // An item that is NOT a subscene root needs to track whether it got a grab via a subscene delivery agent,
+ // whereas the subscene root item already knows it has its own DA.
+ if (isSubsceneAgent && grabGained && (!itemPriv->extra.isAllocated() || !itemPriv->extra->subsceneDeliveryAgent))
+ itemPriv->maybeHasSubsceneDeliveryAgent = true;
}
if (currentEventDeliveryAgent == q && event && event->device()) {
@@ -1794,6 +1871,15 @@ void QQuickDeliveryAgentPrivate::onGrabChanged(QObject *grabber, QPointingDevice
}
}
+/*! \internal
+ Called when a QPointingDevice is detected, to ensure that the
+ QPointingDevice::grabChanged() signal is connected to
+ QQuickDeliveryAgentPrivate::onGrabChanged().
+
+ \c knownPointingDevices is maintained only to track signal connections, and
+ should not be used for other purposes. The usual place to get a list of all
+ devices is QInputDevice::devices().
+*/
void QQuickDeliveryAgentPrivate::ensureDeviceConnected(const QPointingDevice *dev)
{
Q_Q(QQuickDeliveryAgent);
@@ -1804,6 +1890,13 @@ void QQuickDeliveryAgentPrivate::ensureDeviceConnected(const QPointingDevice *de
QObject::connect(dev, &QObject::destroyed, q, [this, dev] {this->knownPointingDevices.removeAll(dev);});
}
+/*! \internal
+ The entry point for delivery of \a event after determining that it \e is a
+ pointer event, and either does not need to be coalesced in
+ compressTouchEvent(), or already has been.
+
+ When it returns, event delivery is done.
+*/
void QQuickDeliveryAgentPrivate::deliverPointerEvent(QPointerEvent *event)
{
Q_Q(QQuickDeliveryAgent);
@@ -1873,11 +1966,36 @@ void QQuickDeliveryAgentPrivate::deliverPointerEvent(QPointerEvent *event)
lastUngrabbed = nullptr;
}
-// check if item or any of its child items contain the point, or if any pointer handler "wants" the point
+/*! \internal
+ Returns a list of all items that are spatially relevant to receive \a event
+ occurring at \a point, starting with \a item and recursively checking all
+ the children.
+ \list
+ \li If an item has pointer handlers, call
+ QQuickPointerHandler::wantsEventPoint()
+ on every handler to decide whether the item is eligible.
+ \li Otherwise, if \a checkMouseButtons is \c true, it means we are
+ finding targets for a mouse event, so no item for which
+ acceptedMouseButtons() is NoButton will be added.
+ \li Otherwise, if \a checkAcceptsTouch is \c true, it means we are
+ finding targets for a touch event, so either acceptTouchEvents() must
+ return true \e or it must accept a synthesized mouse event. I.e. if
+ acceptTouchEvents() returns false, it gets added only if
+ acceptedMouseButtons() is true.
+ \li If QQuickItem::clip() is \c true \e and the \a point is outside of
+ QQuickItem::clipRect(), its children are also omitted. (We stop the
+ recursion, because any clipped-off portions of children under \a point
+ are invisible.)
+ \li Ignore any item in a subscene that "belongs to" a different
+ DeliveryAgent. (In current practice, this only happens in 2D scenes in
+ Qt Quick 3D.)
+ \endlist
+
+ The list returned from this function is the list of items that will be
+ "visited" when delivering any event for which QPointerEvent::isBeginEvent()
+ is \c true.
+*/
// FIXME: should this be iterative instead of recursive?
-// If checkMouseButtons is true, it means we are finding targets for a mouse event, so no item for which acceptedMouseButtons() is NoButton will be added.
-// If checkAcceptsTouch is true, it means we are finding targets for a touch event, so either acceptTouchEvents() must return true OR
-// it must accept a synth. mouse event, thus if acceptTouchEvents() returns false but acceptedMouseButtons() is true, gets added; if not, it doesn't.
QVector<QQuickItem *> QQuickDeliveryAgentPrivate::pointerTargets(QQuickItem *item, const QPointerEvent *event, const QEventPoint &point,
bool checkMouseButtons, bool checkAcceptsTouch) const
{
@@ -1889,7 +2007,7 @@ QVector<QQuickItem *> QQuickDeliveryAgentPrivate::pointerTargets(QQuickItem *ite
qCDebug(lcPtrLoc) << q << "point" << point.id() << point.scenePosition() << "->" << itemPos << ": relevant?" << relevant << "to" << item << point;
// if the item clips, we can potentially return early
if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) {
- if (!relevant)
+ if (!item->clipRect().contains(itemPos))
return targets;
}
@@ -1927,8 +2045,10 @@ QVector<QQuickItem *> QQuickDeliveryAgentPrivate::pointerTargets(QQuickItem *ite
return targets;
}
-// return the joined lists
-// list1 has priority, common items come last
+/*! \internal
+ Returns a joined list consisting of the items in \a list1 and \a list2.
+ \a list1 has priority; common items come last.
+*/
QVector<QQuickItem *> QQuickDeliveryAgentPrivate::mergePointerTargets(const QVector<QQuickItem *> &list1, const QVector<QQuickItem *> &list2) const
{
QVector<QQuickItem *> targets = list1;
@@ -1998,6 +2118,18 @@ void QQuickDeliveryAgentPrivate::deliverUpdatedPoints(QPointerEvent *event)
}
if (!relevantPassiveGrabbers.isEmpty())
deliverToPassiveGrabbers(relevantPassiveGrabbers, event);
+
+ // Ensure that HoverHandlers are updated, in case no items got dirty so far and there's no update request
+ if (event->type() == QEvent::TouchUpdate) {
+ for (const auto &[item, id] : hoverItems) {
+ if (item) {
+ bool res = deliverHoverEventToItem(item, point.scenePosition(), point.sceneLastPosition(),
+ event->modifiers(), event->timestamp(), HoverChange::Set);
+ // if the event was accepted, then the item's ID must be valid
+ Q_ASSERT(!res || hoverItems.value(item));
+ }
+ }
+ }
}
if (done)
@@ -2030,7 +2162,45 @@ void QQuickDeliveryAgentPrivate::deliverUpdatedPoints(QPointerEvent *event)
}
}
-// Deliver an event containing newly pressed or released touch points
+/*! \internal
+ Deliver a pointer \a event containing newly pressed or released QEventPoints.
+ If \a handlersOnly is \c true, skip the items and just deliver to Pointer Handlers
+ (via QQuickItemPrivate::handlePointerEvent()).
+
+ For the sake of determinism, this function first builds the list
+ \c targetItems by calling pointerTargets() on the root item. That is, the
+ list of items to "visit" is determined at the beginning, and will not be
+ affected if items reparent, hide, or otherwise try to make themselves
+ eligible or ineligible during delivery. (Avoid bugs due to ugly
+ just-in-time tricks in JS event handlers, filters etc.)
+
+ Whenever a touch gesture is in progress, and another touchpoint is pressed,
+ or an existing touchpoint is released, we "start over" with delivery:
+ that's why this function is called whenever the event \e contains newly
+ pressed or released points. It's not necessary for a handler or an item to
+ greedily grab all touchpoints just in case a valid gesture might start.
+ QQuickMultiPointHandler::wantsPointerEvent() can calmly return \c false if
+ the number of points is less than QQuickMultiPointHandler::minimumPointCount(),
+ because it knows it will be asked again if the number of points increases.
+
+ When \a handlersOnly is \c false, \a event visits the items in \c targetItems
+ via QQuickItem::event(). We have to call sendFilteredPointerEvent()
+ before visiting each item, just in case a Flickable (or some other
+ parent-filter) will decide to intercept the event. But we also have to be
+ very careful never to let the same Flickable filter the same event twice,
+ because when Flickable decides to intercept, it lets the child item have
+ that event, and then grabs the next event. That allows you to drag a
+ Slider, DragHandler or whatever inside a ListView delegate: if you're
+ dragging in the correct direction for the draggable child, it can use
+ QQuickItem::setKeepMouseGrab(), QQuickItem::setKeepTouchGrab() or
+ QQuickPointerHandler::grabPermissions() to prevent Flickable from
+ intercepting during filtering, only if it actually \e has the exclusive
+ grab already when Flickable attempts to take it. Typically, both the
+ Flickable and the child are checking the same drag threshold, so the
+ child must have a chance to grab and \e keep the grab before Flickable
+ gets a chance to steal it, even though Flickable actually sees the
+ event first during filtering.
+*/
bool QQuickDeliveryAgentPrivate::deliverPressOrReleaseEvent(QPointerEvent *event, bool handlersOnly)
{
QVector<QQuickItem *> targetItems;
@@ -2062,7 +2232,11 @@ bool QQuickDeliveryAgentPrivate::deliverPressOrReleaseEvent(QPointerEvent *event
}
}
- for (QQuickItem *item : targetItems) {
+ QVector<QPointer<QQuickItem>> safeTargetItems(targetItems.begin(), targetItems.end());
+
+ for (auto &item : safeTargetItems) {
+ if (item.isNull())
+ continue;
// failsafe: when items get into a subscene somehow, ensure that QQuickItemPrivate::deliveryAgent() can find it
if (isSubsceneAgent)
QQuickItemPrivate::get(item)->maybeHasSubsceneDeliveryAgent = true;
@@ -2092,6 +2266,14 @@ bool QQuickDeliveryAgentPrivate::deliverPressOrReleaseEvent(QPointerEvent *event
return event->allPointsAccepted();
}
+/*! \internal
+ Deliver \a pointerEvent to \a item and its handlers, if any.
+ If \a handlersOnly is \c true, skip QQuickItem::event() and just visit its
+ handlers via QQuickItemPrivate::handlePointerEvent().
+
+ This function exists just to de-duplicate the common code between
+ deliverPressOrReleaseEvent() and deliverUpdatedPoints().
+*/
void QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem(QQuickItem *item, bool isGrabber, QPointerEvent *pointerEvent, bool handlersOnly)
{
QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
@@ -2125,6 +2307,7 @@ void QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem(QQuickItem *item, b
return;
// TODO: unite this mouse point delivery with the synthetic mouse event below
+ // TODO: remove isGrabber then?
if (isMouse) {
auto button = static_cast<QSinglePointEvent *>(pointerEvent)->button();
if ((isGrabber && button == Qt::NoButton) || item->acceptedMouseButtons().testFlag(button)) {
@@ -2170,11 +2353,6 @@ void QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem(QQuickItem *item, b
if (item->acceptTouchEvents()) {
qCDebug(lcTouch) << "considering delivering" << &touchEvent << " to " << item;
- // If any parent filters the event, we're done.
- hasFiltered.clear();
- if (sendFilteredPointerEvent(&touchEvent, item))
- return;
-
// Deliver the touch event to the given item
qCDebug(lcTouch) << "actually delivering" << &touchEvent << " to " << item;
QCoreApplication::sendEvent(item, &touchEvent);
@@ -2399,11 +2577,22 @@ bool QQuickDeliveryAgentPrivate::deliverDragEvent(
}
#endif // quick_draganddrop
+/*! \internal
+ Allow \a filteringParent to filter \a event on behalf of \a receiver, via
+ QQuickItem::childMouseEventFilter(). This happens right \e before we would
+ send \a event to \a receiver.
+
+ Returns \c true only if \a event has been intercepted (by \a filteringParent
+ or some other filtering ancestor) and should \e not be sent to \a receiver.
+*/
bool QQuickDeliveryAgentPrivate::sendFilteredPointerEvent(QPointerEvent *event, QQuickItem *receiver, QQuickItem *filteringParent)
{
return sendFilteredPointerEventImpl(event, receiver, filteringParent ? filteringParent : receiver->parentItem());
}
+/*! \internal
+ The recursive implementation of sendFilteredPointerEvent().
+*/
bool QQuickDeliveryAgentPrivate::sendFilteredPointerEventImpl(QPointerEvent *event, QQuickItem *receiver, QQuickItem *filteringParent)
{
if (!allowChildEventFiltering)
@@ -2452,15 +2641,18 @@ bool QQuickDeliveryAgentPrivate::sendFilteredPointerEventImpl(QPointerEvent *eve
QQuickItemPrivate::get(receiver)->localizedTouchEvent(static_cast<QTouchEvent *>(event), true, &filteringParentTouchEvent);
if (filteringParentTouchEvent.type() != QEvent::None) {
qCDebug(lcTouch) << "letting parent" << filteringParent << "filter for" << receiver << &filteringParentTouchEvent;
- if (filteringParent->childMouseEventFilter(receiver, &filteringParentTouchEvent)) {
+ filtered = filteringParent->childMouseEventFilter(receiver, &filteringParentTouchEvent);
+ if (filtered) {
qCDebug(lcTouch) << "touch event intercepted by childMouseEventFilter of " << filteringParent;
+ event->setAccepted(filteringParentTouchEvent.isAccepted());
skipDelivery.append(filteringParent);
- for (auto point : filteringParentTouchEvent.points()) {
- const QQuickItem *exclusiveGrabber = qobject_cast<const QQuickItem *>(event->exclusiveGrabber(point));
- if (!exclusiveGrabber || !exclusiveGrabber->keepTouchGrab())
- event->setExclusiveGrabber(point, filteringParent);
+ if (event->isAccepted()) {
+ for (auto point : filteringParentTouchEvent.points()) {
+ const QQuickItem *exclusiveGrabber = qobject_cast<const QQuickItem *>(event->exclusiveGrabber(point));
+ if (!exclusiveGrabber || !exclusiveGrabber->keepTouchGrab())
+ event->setExclusiveGrabber(point, filteringParent);
+ }
}
- return true;
} else if (Q_LIKELY(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents)) &&
!filteringParent->acceptTouchEvents()) {
qCDebug(lcTouch) << "touch event NOT intercepted by childMouseEventFilter of " << filteringParent
@@ -2496,17 +2688,17 @@ bool QQuickDeliveryAgentPrivate::sendFilteredPointerEventImpl(QPointerEvent *eve
// touchMouseId and touchMouseDevice must be set, even if it's only temporarily and isn't grabbed.
touchMouseId = tp.id();
touchMouseDevice = event->pointingDevice();
- if (filteringParent->childMouseEventFilter(receiver, &mouseEvent)) {
+ filtered = filteringParent->childMouseEventFilter(receiver, &mouseEvent);
+ if (filtered) {
qCDebug(lcTouch) << "touch event intercepted as synth mouse event by childMouseEventFilter of " << filteringParent;
+ event->setAccepted(mouseEvent.isAccepted());
skipDelivery.append(filteringParent);
- if (t != QEvent::MouseButtonRelease) {
+ if (event->isAccepted() && event->isBeginEvent()) {
qCDebug(lcTouchTarget) << "TP (mouse)" << Qt::hex << tp.id() << "->" << filteringParent;
filteringParentTouchEvent.setExclusiveGrabber(tp, filteringParent);
touchMouseUnset = false; // We want to leave touchMouseId and touchMouseDevice set
- if (mouseEvent.isAccepted())
- filteringParent->grabMouse();
+ filteringParent->grabMouse();
}
- filtered = true;
}
if (touchMouseUnset)
// Now that we're done sending a synth mouse event, and it wasn't grabbed,
@@ -2526,6 +2718,17 @@ bool QQuickDeliveryAgentPrivate::sendFilteredPointerEventImpl(QPointerEvent *eve
return sendFilteredPointerEventImpl(event, receiver, filteringParent->parentItem()) || filtered;
}
+/*! \internal
+ Allow \a filteringParent to filter \a event on behalf of \a receiver, via
+ QQuickItem::childMouseEventFilter(). This happens right \e before we would
+ send \a event to \a receiver.
+
+ Returns \c true only if \a event has been intercepted (by \a filteringParent
+ or some other filtering ancestor) and should \e not be sent to \a receiver.
+
+ Unlike sendFilteredPointerEvent(), this version does not synthesize a
+ mouse event from touch (presumably it's already an actual mouse event).
+*/
bool QQuickDeliveryAgentPrivate::sendFilteredMouseEvent(QEvent *event, QQuickItem *receiver, QQuickItem *filteringParent)
{
if (!filteringParent)
@@ -2548,6 +2751,13 @@ bool QQuickDeliveryAgentPrivate::sendFilteredMouseEvent(QEvent *event, QQuickIte
return sendFilteredMouseEvent(event, receiver, filteringParent->parentItem()) || filtered;
}
+/*! \internal
+ Returns \c true if the movement delta \a d in pixels along the \a axis
+ exceeds \a startDragThreshold if it is set, or QStyleHints::startDragDistance();
+ \e or, if QEventPoint::velocity() of \a event exceeds QStyleHints::startDragVelocity().
+
+ \sa QQuickPointerHandlerPrivate::dragOverThreshold()
+*/
bool QQuickDeliveryAgentPrivate::dragOverThreshold(qreal d, Qt::Axis axis, QMouseEvent *event, int startDragThreshold)
{
QStyleHints *styleHints = QGuiApplication::styleHints();
@@ -2562,6 +2772,13 @@ bool QQuickDeliveryAgentPrivate::dragOverThreshold(qreal d, Qt::Axis axis, QMous
return overThreshold;
}
+/*! \internal
+ Returns \c true if the movement delta \a d in pixels along the \a axis
+ exceeds \a startDragThreshold if it is set, or QStyleHints::startDragDistance();
+ \e or, if QEventPoint::velocity() of \a tp exceeds QStyleHints::startDragVelocity().
+
+ \sa QQuickPointerHandlerPrivate::dragOverThreshold()
+*/
bool QQuickDeliveryAgentPrivate::dragOverThreshold(qreal d, Qt::Axis axis, const QEventPoint &tp, int startDragThreshold)
{
QStyleHints *styleHints = qApp->styleHints();
@@ -2574,6 +2791,11 @@ bool QQuickDeliveryAgentPrivate::dragOverThreshold(qreal d, Qt::Axis axis, const
return overThreshold;
}
+/*! \internal
+ Returns \c true if the movement \a delta in pixels exceeds QStyleHints::startDragDistance().
+
+ \sa QQuickDeliveryAgentPrivate::dragOverThreshold()
+*/
bool QQuickDeliveryAgentPrivate::dragOverThreshold(QVector2D delta)
{
int threshold = qApp->styleHints()->startDragDistance();
diff --git a/src/quick/util/qquickdeliveryagent_p.h b/src/quick/util/qquickdeliveryagent_p.h
index 0f42f4fa77..df195f16d0 100644
--- a/src/quick/util/qquickdeliveryagent_p.h
+++ b/src/quick/util/qquickdeliveryagent_p.h
@@ -25,6 +25,10 @@
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcPtr)
+Q_DECLARE_LOGGING_CATEGORY(lcHoverTrace)
+Q_DECLARE_LOGGING_CATEGORY(lcTouchTarget)
+
class QQuickItem;
class QQuickDeliveryAgentPrivate;
diff --git a/src/quick/util/qquickdeliveryagent_p_p.h b/src/quick/util/qquickdeliveryagent_p_p.h
index 66db4bc88c..5678afb9f7 100644
--- a/src/quick/util/qquickdeliveryagent_p_p.h
+++ b/src/quick/util/qquickdeliveryagent_p_p.h
@@ -21,6 +21,8 @@
#include <private/qevent_p.h>
#include <private/qpointingdevice_p.h>
+
+#include <QtCore/qpointer.h>
#include <private/qobject_p.h>
#include <memory>
@@ -40,7 +42,7 @@ struct QQuickPointingDeviceExtra {
QVector<QObject *> deliveryTargets;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickDeliveryAgentPrivate : public QObjectPrivate
+class Q_QUICK_EXPORT QQuickDeliveryAgentPrivate : public QObjectPrivate
{
public:
Q_DECLARE_PUBLIC(QQuickDeliveryAgent)
@@ -65,6 +67,8 @@ public:
void clearFocusObject();
void updateFocusItemTransform();
+ QQuickItem *focusTargetItem() const;
+
// Keeps track of the item currently receiving mouse events
#if QT_CONFIG(quick_draganddrop)
QQuickDragGrabber *dragGrabber = nullptr;
@@ -131,6 +135,7 @@ public:
void flushFrameSynchronousEvents(QQuickWindow *win);
void deliverDelayedTouchEvent();
void handleWindowDeactivate(QQuickWindow *win);
+ void handleWindowHidden(QQuickWindow *win);
// utility functions that used to be in QQuickPointerEvent et al.
bool allUpdatedPointsAccepted(const QPointerEvent *ev);
@@ -161,9 +166,14 @@ public:
QVector<QQuickItem *> mergePointerTargets(const QVector<QQuickItem *> &list1, const QVector<QQuickItem *> &list2) const;
// hover delivery
+ enum class HoverChange : uint8_t {
+ Clear,
+ Set,
+ };
bool deliverHoverEvent(const QPointF &scenePos, const QPointF &lastScenePos, Qt::KeyboardModifiers modifiers, ulong timestamp);
bool deliverHoverEventRecursive(QQuickItem *, const QPointF &scenePos, const QPointF &lastScenePos, Qt::KeyboardModifiers modifiers, ulong timestamp);
- bool deliverHoverEventToItem(QQuickItem *item, const QPointF &scenePos, const QPointF &lastScenePos, Qt::KeyboardModifiers modifiers, ulong timestamp, bool clearHover);
+ bool deliverHoverEventToItem(QQuickItem *item, const QPointF &scenePos, const QPointF &lastScenePos, Qt::KeyboardModifiers modifiers, ulong timestamp,
+ HoverChange hoverChange);
bool sendHoverEvent(QEvent::Type, QQuickItem *, const QPointF &scenePos, const QPointF &lastScenePos,
Qt::KeyboardModifiers modifiers, ulong timestamp);
bool clearHover(ulong timestamp = 0);
diff --git a/src/quick/util/qquickfontloader.cpp b/src/quick/util/qquickfontloader.cpp
index 991ea7b295..9972071408 100644
--- a/src/quick/util/qquickfontloader.cpp
+++ b/src/quick/util/qquickfontloader.cpp
@@ -28,8 +28,6 @@
QT_BEGIN_NAMESPACE
-#define FONTLOADER_MAXIMUM_REDIRECT_RECURSION 16
-
class QQuickFontObject : public QObject
{
Q_OBJECT
@@ -44,7 +42,6 @@ Q_SIGNALS:
void fontDownloaded(int id);
private:
- int redirectCount = 0;
QNetworkReply *reply = nullptr;
private Q_SLOTS:
@@ -74,20 +71,6 @@ void QQuickFontObject::download(const QUrl &url, QNetworkAccessManager *manager)
void QQuickFontObject::replyFinished()
{
if (reply) {
- redirectCount++;
- if (redirectCount < FONTLOADER_MAXIMUM_REDIRECT_RECURSION) {
- QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
- if (redirect.isValid()) {
- QUrl url = reply->url().resolved(redirect.toUrl());
- QNetworkAccessManager *manager = reply->manager();
- reply->deleteLater();
- reply = nullptr;
- download(url, manager);
- return;
- }
- }
- redirectCount = 0;
-
if (!reply->error()) {
id = QFontDatabase::addApplicationFontFromData(reply->readAll());
emit fontDownloaded(id);
@@ -312,7 +295,7 @@ void QQuickFontLoader::updateFontInfo(int id)
text: "Fancy font"
font.family: webFont.font.family
font.weight: webFont.font.weight
- font.style: webFont.font.style
+ font.styleName: webFont.font.styleName
font.pixelSize: 24
}
}
@@ -381,12 +364,11 @@ QString QQuickFontLoader::name() const
\qmlproperty enumeration QtQuick::FontLoader::status
This property holds the status of font loading. It can be one of:
- \list
- \li FontLoader.Null - no font has been set
- \li FontLoader.Ready - the font has been loaded
- \li FontLoader.Loading - the font is currently being loaded
- \li FontLoader.Error - an error occurred while loading the font
- \endlist
+
+ \value FontLoader.Null no font has been set
+ \value FontLoader.Ready the font has been loaded
+ \value FontLoader.Loading the font is currently being loaded
+ \value FontLoader.Error an error occurred while loading the font
Use this status to provide an update or respond to the status change in some way.
For example, you could:
diff --git a/src/quick/util/qquickfontloader_p.h b/src/quick/util/qquickfontloader_p.h
index 5d13b574fe..533541a84a 100644
--- a/src/quick/util/qquickfontloader_p.h
+++ b/src/quick/util/qquickfontloader_p.h
@@ -25,7 +25,7 @@
QT_BEGIN_NAMESPACE
class QQuickFontLoaderPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickFontLoader : public QObject
+class Q_QUICK_EXPORT QQuickFontLoader : public QObject
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickFontLoader)
@@ -64,7 +64,5 @@ Q_SIGNALS:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickFontLoader)
-
#endif // QQUICKFONTLOADER_H
diff --git a/src/quick/util/qquickfontmetrics_p.h b/src/quick/util/qquickfontmetrics_p.h
index a91e5288b5..a4ebf5cbf7 100644
--- a/src/quick/util/qquickfontmetrics_p.h
+++ b/src/quick/util/qquickfontmetrics_p.h
@@ -27,7 +27,7 @@ QT_BEGIN_NAMESPACE
class QFont;
-class Q_QUICK_PRIVATE_EXPORT QQuickFontMetrics : public QObject
+class Q_QUICK_EXPORT QQuickFontMetrics : public QObject
{
Q_OBJECT
@@ -86,6 +86,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickFontMetrics)
-
#endif // QQUICKFONTMETRICS_H
diff --git a/src/quick/util/qquickforeignutils.cpp b/src/quick/util/qquickforeignutils.cpp
index e8e60bbc92..242d961cdc 100644
--- a/src/quick/util/qquickforeignutils.cpp
+++ b/src/quick/util/qquickforeignutils.cpp
@@ -10,6 +10,7 @@ QT_BEGIN_NAMESPACE
\instantiates QEventPoint
\inqmlmodule QtQuick
\brief Qml equivalent for \l QEventPoint.
+ \sa handlerPoint
eventPoint is the Qml value type representation
of \l QEventPoint.
@@ -19,7 +20,7 @@ QT_BEGIN_NAMESPACE
\list
\li \l bool \c eventPoint.accepted: see also \l QEventPoint::accepted
- \li \l pointingDevice \c eventPoint.device: see also \l QEventPoint::device
+ \li \l PointerDevice \c eventPoint.device: see also \l QEventPoint::device
\li \l size \c eventPoint.ellipseDiameters: see also \l QEventPoint::ellipseDiameters
\li \l point \c eventPoint.globalGrabPosition: see also \l QEventPoint::globalGrabPosition
\li \l point \c eventPoint.globalLastPosition: see also \l QEventPoint::globalLastPosition
@@ -49,27 +50,21 @@ QT_BEGIN_NAMESPACE
State supports the following values:
- \table
- \row
- \li \c Qt.TouchPointUnknownState
- \li Unknown state.
- \row
- \li \c Qt.TouchPointStationary
- \li The event point did not move.
- \row
- \li \c Qt.TouchPointPressed
- \li The touch point or button is pressed.
- \row
- \li \c Qt.TouchPointMoved
- \li The event point was updated.
- \row
- \li \c Qt.TouchPointReleased
- \li The touch point or button was released.
- \endtable
+ \value EventPoint.Unknown
+ Unknown state; same as Qt.TouchPointUnknownState
+ \value EventPoint.Stationary
+ The event point did not move; same as Qt.TouchPointStationary
+ \value EventPoint.Pressed
+ The touch point or button is pressed; same as Qt.TouchPointPressed
+ \value EventPoint.Updated
+ The event point was updated; same as Qt.TouchPointMoved
+ \value EventPoint.Released
+ The touch point or button was released; same as Qt.TouchPointReleased
The States type is a typedef for \l {QFlags} {QFlags<State>}. It stores an OR combination of
State values. See also \l QEventPoint::States
+ \snippet pointerHandlers/tapHandlerGrabChanged.qml 0
*/
QT_END_NAMESPACE
diff --git a/src/quick/util/qquickforeignutils_p.h b/src/quick/util/qquickforeignutils_p.h
index 1ce53ae7ef..5d257fb16d 100644
--- a/src/quick/util/qquickforeignutils_p.h
+++ b/src/quick/util/qquickforeignutils_p.h
@@ -99,6 +99,22 @@ struct QEventPointForeign
QML_ADDED_IN_VERSION(6, 5)
};
+// Prevent the same QEventPoint type from being exported into qmltypes
+// twice, as a value type and a namespace.
+// TODO: Remove once QTBUG-115855 is fixed.
+struct QEventPointDerived : public QEventPoint
+{
+ Q_GADGET
+};
+
+namespace QEventPointForeignNamespace
+{
+ Q_NAMESPACE
+ QML_FOREIGN_NAMESPACE(QEventPointDerived)
+ QML_NAMED_ELEMENT(EventPoint)
+ QML_ADDED_IN_VERSION(6, 6)
+};
+
QT_END_NAMESPACE
#endif // QTQUICKFOREIGN_P_H
diff --git a/src/quick/util/qquickframeanimation_p.h b/src/quick/util/qquickframeanimation_p.h
index 5011673bb6..39480d47c2 100644
--- a/src/quick/util/qquickframeanimation_p.h
+++ b/src/quick/util/qquickframeanimation_p.h
@@ -23,7 +23,7 @@
QT_BEGIN_NAMESPACE
class QQuickFrameAnimationPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickFrameAnimation : public QObject, public QQmlParserStatus
+class Q_QUICK_EXPORT QQuickFrameAnimation : public QObject, public QQmlParserStatus
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickFrameAnimation)
@@ -80,6 +80,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickFrameAnimation)
-
#endif
diff --git a/src/quick/util/qquickglobal.cpp b/src/quick/util/qquickglobal.cpp
index 3eae2ac28d..f1aae5c6fa 100644
--- a/src/quick/util/qquickglobal.cpp
+++ b/src/quick/util/qquickglobal.cpp
@@ -24,6 +24,7 @@
#include <QtQml/private/qqmlanybinding_p.h>
#include <QtCore/qiterable.h>
+#include <QtCore/qpointer.h>
#ifdef Q_CC_MSVC
// MSVC2010 warns about 'unused variable t', even if it's used in t->~T()
diff --git a/src/quick/util/qquickimageprovider.cpp b/src/quick/util/qquickimageprovider.cpp
index e3b96ddad4..27134c9deb 100644
--- a/src/quick/util/qquickimageprovider.cpp
+++ b/src/quick/util/qquickimageprovider.cpp
@@ -4,7 +4,7 @@
#include "qquickimageprovider.h"
#include "qquickimageprovider_p.h"
-#include "qquickpixmapcache_p.h"
+#include "qquickpixmap_p.h"
#include <QtQuick/private/qsgcontext_p.h>
#include <private/qqmlglobal_p.h>
#include <QtGui/qcolorspace.h>
@@ -476,6 +476,7 @@ public:
}
QColorSpace targetColorSpace;
+ QRectF sourceClipRect;
QQuickImageProviderOptions::AutoTransform autoTransform = QQuickImageProviderOptions::UsePluginDefaultTransform;
bool preserveAspectRatioCrop = false;
bool preserveAspectRatioFit = false;
@@ -582,6 +583,19 @@ void QQuickImageProviderOptions::setTargetColorSpace(const QColorSpace &colorSpa
d->targetColorSpace = colorSpace;
}
+/*!
+ Returns the requested source clip rect.
+*/
+QRectF QQuickImageProviderOptions::sourceClipRect() const
+{
+ return d->sourceClipRect;
+}
+
+void QQuickImageProviderOptions::setSourceClipRect(const QRectF &rect)
+{
+ d->sourceClipRect = rect;
+}
+
QQuickImageProviderWithOptions::QQuickImageProviderWithOptions(ImageType type, Flags flags)
: QQuickAsyncImageProvider()
{
diff --git a/src/quick/util/qquickinputmethod_p.h b/src/quick/util/qquickinputmethod_p.h
index 7e0573e867..927f009b2c 100644
--- a/src/quick/util/qquickinputmethod_p.h
+++ b/src/quick/util/qquickinputmethod_p.h
@@ -25,22 +25,22 @@
#include <private/qtquickglobal_p.h>
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QQuickInputMethod : public QObject
+class Q_QUICK_EXPORT QQuickInputMethod : public QObject
{
Q_OBJECT
QML_NAMED_ELEMENT(InputMethod)
QML_ADDED_IN_VERSION(6, 4)
QML_SINGLETON
- Q_PROPERTY(QRectF cursorRectangle READ cursorRectangle NOTIFY cursorRectangleChanged)
- Q_PROPERTY(QRectF anchorRectangle READ anchorRectangle NOTIFY anchorRectangleChanged)
- Q_PROPERTY(QRectF keyboardRectangle READ keyboardRectangle NOTIFY keyboardRectangleChanged)
+ Q_PROPERTY(QRectF cursorRectangle READ cursorRectangle NOTIFY cursorRectangleChanged FINAL)
+ Q_PROPERTY(QRectF anchorRectangle READ anchorRectangle NOTIFY anchorRectangleChanged FINAL)
+ Q_PROPERTY(QRectF keyboardRectangle READ keyboardRectangle NOTIFY keyboardRectangleChanged FINAL)
Q_PROPERTY(QRectF inputItemClipRectangle READ inputItemClipRectangle NOTIFY
- inputItemClipRectangleChanged)
- Q_PROPERTY(bool visible READ isVisible NOTIFY visibleChanged)
- Q_PROPERTY(bool animating READ isAnimating NOTIFY animatingChanged)
- Q_PROPERTY(QLocale locale READ locale NOTIFY localeChanged)
- Q_PROPERTY(Qt::LayoutDirection inputDirection READ inputDirection NOTIFY inputDirectionChanged)
+ inputItemClipRectangleChanged FINAL)
+ Q_PROPERTY(bool visible READ isVisible NOTIFY visibleChanged FINAL)
+ Q_PROPERTY(bool animating READ isAnimating NOTIFY animatingChanged FINAL)
+ Q_PROPERTY(QLocale locale READ locale NOTIFY localeChanged FINAL)
+ Q_PROPERTY(Qt::LayoutDirection inputDirection READ inputDirection NOTIFY inputDirectionChanged FINAL)
public:
explicit QQuickInputMethod(QObject *parent = nullptr);
diff --git a/src/quick/util/qquickpath.cpp b/src/quick/util/qquickpath.cpp
index a38de75753..c33111039f 100644
--- a/src/quick/util/qquickpath.cpp
+++ b/src/quick/util/qquickpath.cpp
@@ -25,7 +25,7 @@ QT_BEGIN_NAMESPACE
be instantiated.
\sa Path, PathAttribute, PathPercent, PathLine, PathPolyline, PathQuad, PathCubic, PathArc,
- PathAngleArc, PathCurve, PathSvg
+ PathAngleArc, PathCurve, PathSvg, PathRectangle
*/
/*!
@@ -70,7 +70,7 @@ QT_BEGIN_NAMESPACE
\li Yes
\li Yes
\row
- \li PathMultiLine
+ \li PathMultiline
\li Yes
\li Yes
\li Yes
@@ -100,6 +100,11 @@ QT_BEGIN_NAMESPACE
\li Yes
\li Yes
\row
+ \li PathRectangle
+ \li Yes
+ \li Yes
+ \li Yes
+ \row
\li PathAttribute
\li Yes
\li N/A
@@ -119,7 +124,7 @@ QT_BEGIN_NAMESPACE
\note Path is a non-visual type; it does not display anything on its own.
To draw a path, use \l Shape.
- \sa PathView, Shape, PathAttribute, PathPercent, PathLine, PathPolyline, PathMove, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg
+ \sa PathView, Shape, PathAttribute, PathPercent, PathLine, PathPolyline, PathMove, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg, PathRectangle
*/
QQuickPath::QQuickPath(QObject *parent)
: QObject(*(new QQuickPathPrivate), parent)
@@ -143,7 +148,7 @@ QQuickPath::~QQuickPath()
qreal QQuickPath::startX() const
{
Q_D(const QQuickPath);
- return d->startX.isNull ? 0 : d->startX.value;
+ return d->startX.isValid() ? d->startX.value() : 0;
}
void QQuickPath::setStartX(qreal x)
@@ -165,7 +170,7 @@ bool QQuickPath::hasStartX() const
qreal QQuickPath::startY() const
{
Q_D(const QQuickPath);
- return d->startY.isNull ? 0 : d->startY.value;
+ return d->startY.isValid() ? d->startY.value() : 0;
}
void QQuickPath::setStartY(qreal y)
@@ -210,6 +215,7 @@ bool QQuickPath::isClosed() const
\li \l PathArc - an arc to a given position with a radius.
\li \l PathAngleArc - an arc specified by center point, radii, and angles.
\li \l PathSvg - a path specified as an SVG path data string.
+ \li \l PathRectangle - a rectangle with a given position and size
\li \l PathCurve - a point on a Catmull-Rom curve.
\li \l PathAttribute - an attribute at a given position in the path.
\li \l PathPercent - a way to spread out items along various segments of the path.
@@ -372,6 +378,9 @@ void QQuickPath::processPath()
d->_path = createPath(QPointF(), QPointF(), d->_attributes, d->pathLength, d->_attributePoints, &d->closed);
}
+ if (d->simplify)
+ d->_path = d->_path.simplified();
+
emit changed();
}
@@ -405,8 +414,8 @@ QPainterPath QQuickPath::createPath(const QPointF &startPoint, const QPointF &en
first.values[attributes.at(ii)] = 0;
attributePoints << first;
- qreal startX = d->startX.isValid() ? d->startX.value : startPoint.x();
- qreal startY = d->startY.isValid() ? d->startY.value : startPoint.y();
+ qreal startX = d->startX.isValid() ? d->startX.value() : startPoint.x();
+ qreal startY = d->startY.isValid() ? d->startY.value() : startPoint.y();
path.moveTo(startX, startY);
const QString percentString = QStringLiteral("_qfx_percent");
@@ -490,8 +499,8 @@ QPainterPath QQuickPath::createShapePath(const QPointF &startPoint, const QPoint
QPainterPath path;
- qreal startX = d->startX.isValid() ? d->startX.value : startPoint.x();
- qreal startY = d->startY.isValid() ? d->startY.value : startPoint.y();
+ qreal startX = d->startX.isValid() ? d->startX.value() : startPoint.x();
+ qreal startY = d->startY.isValid() ? d->startY.value() : startPoint.y();
path.moveTo(startX, startY);
int index = 0;
@@ -712,6 +721,32 @@ void QQuickPath::invalidateSequentialHistory() const
d->prevBez.isValid = false;
}
+/*! \qmlproperty bool QtQuick::Path::simplify
+ \since 6.6
+
+ When set to true, the path will be simplified. This implies merging all subpaths that intersect,
+ creating a path where there are no self-intersections. Consecutive parallel lines will also be
+ merged. The simplified path is intended to be used with ShapePath.OddEvenFill. Bezier curves may
+ be flattened to line segments due to numerical instability of doing bezier curve intersections.
+*/
+void QQuickPath::setSimplify(bool s)
+{
+ Q_D(QQuickPath);
+ if (d->simplify == s)
+ return;
+
+ d->simplify = s;
+ processPath();
+
+ emit simplifyChanged();
+}
+
+bool QQuickPath::simplify() const
+{
+ Q_D(const QQuickPath);
+ return d->simplify;
+}
+
/*!
\qmlproperty size QtQuick::Path::scale
@@ -952,12 +987,12 @@ qreal QQuickPath::attributeAt(const QString &name, qreal percent) const
qreal QQuickCurve::x() const
{
- return _x.isNull ? 0 : _x.value;
+ return _x.isValid() ? _x.value() : 0;
}
void QQuickCurve::setX(qreal x)
{
- if (_x.isNull || _x != x) {
+ if (!_x.isValid() || _x != x) {
_x = x;
emit xChanged();
emit changed();
@@ -971,12 +1006,12 @@ bool QQuickCurve::hasX()
qreal QQuickCurve::y() const
{
- return _y.isNull ? 0 : _y.value;
+ return _y.isValid() ? _y.value() : 0;
}
void QQuickCurve::setY(qreal y)
{
- if (_y.isNull || _y != y) {
+ if (!_y.isValid() || _y != y) {
_y = y;
emit yChanged();
emit changed();
@@ -995,7 +1030,7 @@ qreal QQuickCurve::relativeX() const
void QQuickCurve::setRelativeX(qreal x)
{
- if (_relativeX.isNull || _relativeX != x) {
+ if (!_relativeX.isValid() || _relativeX != x) {
_relativeX = x;
emit relativeXChanged();
emit changed();
@@ -1014,7 +1049,7 @@ qreal QQuickCurve::relativeY() const
void QQuickCurve::setRelativeY(qreal y)
{
- if (_relativeY.isNull || _relativeY != y) {
+ if (!_relativeY.isValid() || _relativeY != y) {
_relativeY = y;
emit relativeYChanged();
emit changed();
@@ -1162,7 +1197,7 @@ void QQuickPathAttribute::setValue(qreal value)
}
\endqml
- \sa Path, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg, PathMove, PathPolyline
+ \sa Path, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg, PathMove, PathPolyline, PathRectangle
*/
/*!
@@ -1373,7 +1408,7 @@ qreal QQuickPathQuad::relativeControlX() const
void QQuickPathQuad::setRelativeControlX(qreal x)
{
- if (_relativeControlX.isNull || _relativeControlX != x) {
+ if (!_relativeControlX.isValid() || _relativeControlX != x) {
_relativeControlX = x;
emit relativeControlXChanged();
emit changed();
@@ -1392,7 +1427,7 @@ qreal QQuickPathQuad::relativeControlY() const
void QQuickPathQuad::setRelativeControlY(qreal y)
{
- if (_relativeControlY.isNull || _relativeControlY != y) {
+ if (!_relativeControlY.isValid() || _relativeControlY != y) {
_relativeControlY = y;
emit relativeControlYChanged();
emit changed();
@@ -1438,7 +1473,7 @@ void QQuickPathQuad::addToPath(QPainterPath &path, const QQuickPathData &data)
\endqml
\endtable
- \sa Path, PathQuad, PathLine, PathArc, PathAngleArc, PathCurve, PathSvg
+ \sa Path, PathQuad, PathLine, PathArc, PathAngleArc, PathCurve, PathSvg, PathRectangle
*/
/*!
@@ -1557,7 +1592,7 @@ qreal QQuickPathCubic::relativeControl1X() const
void QQuickPathCubic::setRelativeControl1X(qreal x)
{
- if (_relativeControl1X.isNull || _relativeControl1X != x) {
+ if (!_relativeControl1X.isValid() || _relativeControl1X != x) {
_relativeControl1X = x;
emit relativeControl1XChanged();
emit changed();
@@ -1576,7 +1611,7 @@ qreal QQuickPathCubic::relativeControl1Y() const
void QQuickPathCubic::setRelativeControl1Y(qreal y)
{
- if (_relativeControl1Y.isNull || _relativeControl1Y != y) {
+ if (!_relativeControl1Y.isValid() || _relativeControl1Y != y) {
_relativeControl1Y = y;
emit relativeControl1YChanged();
emit changed();
@@ -1595,7 +1630,7 @@ qreal QQuickPathCubic::relativeControl2X() const
void QQuickPathCubic::setRelativeControl2X(qreal x)
{
- if (_relativeControl2X.isNull || _relativeControl2X != x) {
+ if (!_relativeControl2X.isValid() || _relativeControl2X != x) {
_relativeControl2X = x;
emit relativeControl2XChanged();
emit changed();
@@ -1614,7 +1649,7 @@ qreal QQuickPathCubic::relativeControl2Y() const
void QQuickPathCubic::setRelativeControl2Y(qreal y)
{
- if (_relativeControl2Y.isNull || _relativeControl2Y != y) {
+ if (!_relativeControl2Y.isValid() || _relativeControl2Y != y) {
_relativeControl2Y = y;
emit relativeControl2YChanged();
emit changed();
@@ -2006,7 +2041,7 @@ void QQuickPathArc::addToPath(QPainterPath &path, const QQuickPathData &data)
to work as part of a larger path (specifying start and end), PathAngleArc is designed
to make a path where the arc is primary (such as a circular progress indicator) more intuitive.
- \sa Path, PathLine, PathQuad, PathCubic, PathCurve, PathSvg, PathArc
+ \sa Path, PathLine, PathQuad, PathCubic, PathCurve, PathSvg, PathArc, PathRectangle
*/
/*!
@@ -2223,6 +2258,268 @@ void QQuickPathSvg::addToPath(QPainterPath &path, const QQuickPathData &)
/****************************************************************************/
/*!
+ \qmltype PathRectangle
+ \instantiates QQuickPathRectangle
+ \inqmlmodule QtQuick
+ \ingroup qtquick-animation-paths
+ \brief Defines a rectangle with optionally rounded corners.
+ \since QtQuick 6.8
+
+ PathRectangle provides an easy way to specify a rectangle, optionally with rounded corners. The
+ API corresponds to that of the \l Rectangle item.
+
+ \sa Path, PathLine, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg
+*/
+
+/*!
+ \qmlproperty real QtQuick::PathRectangle::x
+ \qmlproperty real QtQuick::PathRectangle::y
+
+ Defines the top left corner of the rectangle.
+
+ Unless that corner is rounded, this will also be the start and end point of the path.
+
+ \sa relativeX, relativeY
+*/
+
+/*!
+ \qmlproperty real QtQuick::PathRectangle::relativeX
+ \qmlproperty real QtQuick::PathRectangle::relativeY
+
+ Defines the top left corner of the rectangle relative to the path's start point.
+
+ If both a relative and absolute end position are specified for a single axis, the relative
+ position will be used.
+
+ Relative and absolute positions can be mixed, for example it is valid to set a relative x
+ and an absolute y.
+
+ \sa x, y
+*/
+
+/*!
+ \qmlproperty real QtQuick::PathRectangle::width
+ \qmlproperty real QtQuick::PathRectangle::height
+
+ Defines the width and height of the rectangle.
+
+ \sa x, y
+*/
+
+qreal QQuickPathRectangle::width() const
+{
+ return _width;
+}
+
+void QQuickPathRectangle::setWidth(qreal width)
+{
+ if (_width == width)
+ return;
+
+ _width = width;
+ emit widthChanged();
+ emit changed();
+}
+
+qreal QQuickPathRectangle::height() const
+{
+ return _height;
+}
+
+void QQuickPathRectangle::setHeight(qreal height)
+{
+ if (_height == height)
+ return;
+
+ _height = height;
+ emit heightChanged();
+ emit changed();
+}
+
+/*!
+ \qmlproperty real QtQuick::PathRectangle::strokeAdjustment
+
+ This property defines the stroke width adjustment to the rectangle coordinates.
+
+ When used in a \l ShapePath with stroking enabled, the actual stroked rectangle will by default
+ extend beyond the defined rectangle by half the stroke width on all sides. This is the expected
+ behavior since the path defines the midpoint line of the stroking, and corresponds to QPainter
+ and SVG rendering.
+
+ If one instead wants the defined rectangle to be the outer edge of the stroked rectangle, like
+ a \l Rectangle item with a border, one can set strokeAdjustment to the stroke width. This will
+ effectively shift all edges inwards by half the stroke width. Like in the following example:
+
+ \qml
+ ShapePath {
+ id: myRec
+ fillColor: "white"
+ strokeColor: "black"
+ strokeWidth: 16
+ joinStyle: ShapePath.MiterJoin
+
+ PathRectangle { x: 10; y: 10; width: 200; height: 100; strokeAdjustment: myRec.strokeWidth }
+ }
+ \endqml
+*/
+
+qreal QQuickPathRectangle::strokeAdjustment() const
+{
+ return _strokeAdjustment;
+}
+
+void QQuickPathRectangle::setStrokeAdjustment(qreal newStrokeAdjustment)
+{
+ if (_strokeAdjustment == newStrokeAdjustment)
+ return;
+ _strokeAdjustment = newStrokeAdjustment;
+ emit strokeAdjustmentChanged();
+ emit changed();
+}
+
+/*!
+ \qmlproperty real QtQuick::PathRectangle::radius
+
+ This property defines the corner radius used to define a rounded rectangle.
+
+ If radius is a positive value, the rectangle path will be defined as a rounded rectangle,
+ otherwise it will be defined as a normal rectangle.
+
+ This property may be overridden by the individual corner radius properties.
+
+ \sa topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius
+*/
+
+qreal QQuickPathRectangle::radius() const
+{
+ return _extra.isAllocated() ? _extra->radius : 0;
+}
+
+void QQuickPathRectangle::setRadius(qreal newRadius)
+{
+ if (_extra.value().radius == newRadius)
+ return;
+ _extra->radius = newRadius;
+ emit radiusChanged();
+ if (_extra->cornerRadii[Qt::TopLeftCorner] < 0)
+ emit topLeftRadiusChanged();
+ if (_extra->cornerRadii[Qt::TopRightCorner] < 0)
+ emit topRightRadiusChanged();
+ if (_extra->cornerRadii[Qt::BottomLeftCorner] < 0)
+ emit bottomLeftRadiusChanged();
+ if (_extra->cornerRadii[Qt::BottomRightCorner] < 0)
+ emit bottomRightRadiusChanged();
+ emit changed();
+}
+
+/*!
+ \qmlproperty real QtQuick::PathRectangle::topLeftRadius
+ \qmlproperty real QtQuick::PathRectangle::topRightRadius
+ \qmlproperty real QtQuick::PathRectangle::bottomLeftRadius
+ \qmlproperty real QtQuick::PathRectangle::bottomRightRadius
+
+ If set, these properties define the individual corner radii. A zero value defines that corner
+ to be sharp, while a positive value defines it to be rounded. When unset, the value of \l
+ radius is used instead.
+
+ These properties are unset by default. Assign \c undefined to them to return them to the unset
+ state.
+
+ \sa radius
+*/
+
+qreal QQuickPathRectangle::cornerRadius(Qt::Corner corner) const
+{
+ if (_extra.isAllocated())
+ return _extra->cornerRadii[corner] < 0 ? _extra->radius : _extra->cornerRadii[corner];
+ else
+ return 0;
+}
+
+void QQuickPathRectangle::setCornerRadius(Qt::Corner corner, qreal newCornerRadius)
+{
+ if (newCornerRadius < 0 || _extra.value().cornerRadii[corner] == newCornerRadius)
+ return;
+ _extra->cornerRadii[corner] = newCornerRadius;
+ emitCornerRadiusChanged(corner);
+}
+
+void QQuickPathRectangle::resetCornerRadius(Qt::Corner corner)
+{
+ if (!_extra.isAllocated() || _extra->cornerRadii[corner] < 0)
+ return;
+ _extra->cornerRadii[corner] = -1;
+ emitCornerRadiusChanged(corner);
+}
+
+void QQuickPathRectangle::emitCornerRadiusChanged(Qt::Corner corner)
+{
+ switch (corner) {
+ case Qt::TopLeftCorner:
+ emit topLeftRadiusChanged();
+ break;
+ case Qt::TopRightCorner:
+ emit topRightRadiusChanged();
+ break;
+ case Qt::BottomLeftCorner:
+ emit bottomLeftRadiusChanged();
+ break;
+ case Qt::BottomRightCorner:
+ emit bottomRightRadiusChanged();
+ break;
+ }
+ emit changed();
+}
+
+void QQuickPathRectangle::addToPath(QPainterPath &path, const QQuickPathData &data)
+{
+ QRectF rect(positionForCurve(data, path.currentPosition()), QSizeF(_width, _height));
+
+ qreal halfStroke = _strokeAdjustment * 0.5;
+ rect.adjust(halfStroke, halfStroke, -halfStroke, -halfStroke);
+ if (rect.isEmpty())
+ return;
+
+ if (!_extra.isAllocated()) {
+ // No rounded corners
+ path.addRect(rect);
+ } else {
+ // Radii must not exceed half of the width or half of the height
+ const qreal maxDiameter = qMin(rect.width(), rect.height());
+ const qreal generalDiameter = qMax(qreal(0), qMin(maxDiameter, 2 * _extra->radius));
+ auto effectiveDiameter = [&](Qt::Corner corner) {
+ qreal radius = _extra->cornerRadii[corner];
+ return radius < 0 ? generalDiameter : qMin(maxDiameter, 2 * radius);
+ };
+ const qreal diamTL = effectiveDiameter(Qt::TopLeftCorner);
+ const qreal diamTR = effectiveDiameter(Qt::TopRightCorner);
+ const qreal diamBL = effectiveDiameter(Qt::BottomLeftCorner);
+ const qreal diamBR = effectiveDiameter(Qt::BottomRightCorner);
+
+ path.moveTo(rect.left() + diamTL * 0.5, rect.top());
+ if (diamTR)
+ path.arcTo(QRectF(QPointF(rect.right() - diamTR, rect.top()), QSizeF(diamTR, diamTR)), 90, -90);
+ else
+ path.lineTo(rect.topRight());
+ if (diamBR)
+ path.arcTo(QRectF(QPointF(rect.right() - diamBR, rect.bottom() - diamBR), QSizeF(diamBR, diamBR)), 0, -90);
+ else
+ path.lineTo(rect.bottomRight());
+ if (diamBL)
+ path.arcTo(QRectF(QPointF(rect.left(), rect.bottom() - diamBL), QSizeF(diamBL, diamBL)), 270, -90);
+ else
+ path.lineTo(rect.bottomLeft());
+ if (diamTL)
+ path.arcTo(QRectF(rect.topLeft(), QSizeF(diamTL, diamTL)), 180, -90);
+ else
+ path.lineTo(rect.topLeft());
+ path.closeSubpath();
+ }
+}
+
+/****************************************************************************/
+
+/*!
\qmltype PathPercent
\instantiates QQuickPathPercent
\inqmlmodule QtQuick
@@ -2649,22 +2946,21 @@ void QQuickPathMultiline::addToPath(QPainterPath &path, const QQuickPathData &)
*/
/*!
- \qmlproperty enumeration QtQuick::PathText::font.weight
+ \qmlproperty int QtQuick::PathText::font.weight
Sets the font's weight.
The weight can be one of:
- \list
- \li Font.Thin
- \li Font.Light
- \li Font.ExtraLight
- \li Font.Normal - the default
- \li Font.Medium
- \li Font.DemiBold
- \li Font.Bold
- \li Font.ExtraBold
- \li Font.Black
- \endlist
+
+ \value Font.Thin 100
+ \value Font.ExtraLight 200
+ \value Font.Light 300
+ \value Font.Normal 400 (default)
+ \value Font.Medium 500
+ \value Font.DemiBold 600
+ \value Font.Bold 700
+ \value Font.ExtraBold 800
+ \value Font.Black 900
\qml
PathText { text: "Hello"; font.weight: Font.DemiBold }
@@ -2728,13 +3024,12 @@ void QQuickPathMultiline::addToPath(QPainterPath &path, const QQuickPathData &)
Sets the capitalization for the text.
- \list
- \li Font.MixedCase - This is the normal text rendering option where no capitalization change is applied.
- \li Font.AllUppercase - This alters the text to be rendered in all uppercase type.
- \li Font.AllLowercase - This alters the text to be rendered in all lowercase type.
- \li Font.SmallCaps - This alters the text to be rendered in small-caps type.
- \li Font.Capitalize - This alters the text to be rendered with the first character of each word as an uppercase character.
- \endlist
+ \value Font.MixedCase no capitalization change is applied
+ \value Font.AllUppercase alters the text to be rendered in all uppercase type
+ \value Font.AllLowercase alters the text to be rendered in all lowercase type
+ \value Font.SmallCaps alters the text to be rendered in small-caps type
+ \value Font.Capitalize alters the text to be rendered with the first character of
+ each word as an uppercase character
\qml
PathText { text: "Hello"; font.capitalization: Font.AllLowercase }
@@ -2769,6 +3064,33 @@ void QQuickPathMultiline::addToPath(QPainterPath &path, const QQuickPathData &)
\endqml
*/
+/*!
+ \qmlproperty object QtQuick::PathText::font.variableAxes
+ \since 6.7
+
+ \include qquicktext.cpp qml-font-variable-axes
+*/
+
+/*!
+ \qmlproperty object QtQuick::PathText::font.features
+ \since 6.6
+
+ \include qquicktext.cpp qml-font-features
+*/
+
+/*!
+ \qmlproperty bool QtQuick::PathText::font.contextFontMerging
+ \since 6.8
+
+ \include qquicktext.cpp qml-font-context-font-merging
+*/
+
+/*!
+ \qmlproperty bool QtQuick::PathText::font.preferTypoLineMetrics
+ \since 6.8
+
+ \include qquicktext.cpp qml-font-prefer-typo-line-metrics
+*/
void QQuickPathText::updatePath() const
{
if (!_path.isEmpty())
diff --git a/src/quick/util/qquickpath_p.h b/src/quick/util/qquickpath_p.h
index 9333805389..173bdd2fea 100644
--- a/src/quick/util/qquickpath_p.h
+++ b/src/quick/util/qquickpath_p.h
@@ -22,6 +22,7 @@ QT_REQUIRE_CONFIG(quick_path);
#include <qqml.h>
#include <private/qqmlnullablevalue_p.h>
+#include <private/qlazilyallocated_p.h>
#include <private/qbezier_p.h>
#include <private/qtquickglobal_p.h>
@@ -40,7 +41,7 @@ struct QQuickPathData
QList<QQuickCurve*> curves;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickPathElement : public QObject
+class Q_QUICK_EXPORT QQuickPathElement : public QObject
{
Q_OBJECT
QML_ANONYMOUS
@@ -51,7 +52,7 @@ Q_SIGNALS:
void changed();
};
-class Q_QUICK_PRIVATE_EXPORT QQuickPathAttribute : public QQuickPathElement
+class Q_QUICK_EXPORT QQuickPathAttribute : public QQuickPathElement
{
Q_OBJECT
@@ -78,7 +79,7 @@ private:
qreal _value = 0;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickCurve : public QQuickPathElement
+class Q_QUICK_EXPORT QQuickCurve : public QQuickPathElement
{
Q_OBJECT
@@ -122,7 +123,7 @@ private:
QQmlNullableValue<qreal> _relativeY;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickPathLine : public QQuickCurve
+class Q_QUICK_EXPORT QQuickPathLine : public QQuickCurve
{
Q_OBJECT
QML_NAMED_ELEMENT(PathLine)
@@ -133,7 +134,7 @@ public:
void addToPath(QPainterPath &path, const QQuickPathData &) override;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickPathMove : public QQuickCurve
+class Q_QUICK_EXPORT QQuickPathMove : public QQuickCurve
{
Q_OBJECT
QML_NAMED_ELEMENT(PathMove)
@@ -144,7 +145,7 @@ public:
void addToPath(QPainterPath &path, const QQuickPathData &) override;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickPathQuad : public QQuickCurve
+class Q_QUICK_EXPORT QQuickPathQuad : public QQuickCurve
{
Q_OBJECT
@@ -187,7 +188,7 @@ private:
QQmlNullableValue<qreal> _relativeControlY;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickPathCubic : public QQuickCurve
+class Q_QUICK_EXPORT QQuickPathCubic : public QQuickCurve
{
Q_OBJECT
@@ -255,7 +256,7 @@ private:
QQmlNullableValue<qreal> _relativeControl2Y;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickPathCatmullRomCurve : public QQuickCurve
+class Q_QUICK_EXPORT QQuickPathCatmullRomCurve : public QQuickCurve
{
Q_OBJECT
QML_NAMED_ELEMENT(PathCurve)
@@ -266,7 +267,7 @@ public:
void addToPath(QPainterPath &path, const QQuickPathData &) override;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickPathArc : public QQuickCurve
+class Q_QUICK_EXPORT QQuickPathArc : public QQuickCurve
{
Q_OBJECT
Q_PROPERTY(qreal radiusX READ radiusX WRITE setRadiusX NOTIFY radiusXChanged)
@@ -316,7 +317,7 @@ private:
qreal _xAxisRotation = 0;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickPathAngleArc : public QQuickCurve
+class Q_QUICK_EXPORT QQuickPathAngleArc : public QQuickCurve
{
Q_OBJECT
Q_PROPERTY(qreal centerX READ centerX WRITE setCenterX NOTIFY centerXChanged)
@@ -376,7 +377,7 @@ private:
bool _moveToStart = true;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickPathSvg : public QQuickCurve
+class Q_QUICK_EXPORT QQuickPathSvg : public QQuickCurve
{
Q_OBJECT
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
@@ -397,7 +398,84 @@ private:
QString _path;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickPathPercent : public QQuickPathElement
+class Q_QUICK_EXPORT QQuickPathRectangle : public QQuickCurve
+{
+ Q_OBJECT
+
+ Q_PROPERTY(qreal width READ width WRITE setWidth NOTIFY widthChanged FINAL)
+ Q_PROPERTY(qreal height READ height WRITE setHeight NOTIFY heightChanged FINAL)
+ Q_PROPERTY(qreal strokeAdjustment READ strokeAdjustment WRITE setStrokeAdjustment NOTIFY strokeAdjustmentChanged FINAL)
+ Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged FINAL)
+ Q_PROPERTY(qreal topLeftRadius READ topLeftRadius WRITE setTopLeftRadius RESET resetTopLeftRadius NOTIFY topLeftRadiusChanged FINAL)
+ Q_PROPERTY(qreal topRightRadius READ topRightRadius WRITE setTopRightRadius NOTIFY topRightRadiusChanged RESET resetTopRightRadius FINAL)
+ Q_PROPERTY(qreal bottomLeftRadius READ bottomLeftRadius WRITE setBottomLeftRadius NOTIFY bottomLeftRadiusChanged RESET resetBottomLeftRadius FINAL)
+ Q_PROPERTY(qreal bottomRightRadius READ bottomRightRadius WRITE setBottomRightRadius NOTIFY bottomRightRadiusChanged RESET resetBottomRightRadius FINAL)
+
+ QML_NAMED_ELEMENT(PathRectangle)
+ QML_ADDED_IN_VERSION(6, 8)
+public:
+ QQuickPathRectangle(QObject *parent = nullptr) : QQuickCurve(parent) {}
+
+ qreal width() const;
+ void setWidth(qreal width);
+
+ qreal height() const;
+ void setHeight(qreal height);
+
+ qreal strokeAdjustment() const;
+ void setStrokeAdjustment(qreal newStrokeAdjustment);
+
+ qreal radius() const;
+ void setRadius(qreal newRadius);
+
+ qreal topLeftRadius() const { return cornerRadius(Qt::TopLeftCorner); }
+ void setTopLeftRadius(qreal radius) { setCornerRadius(Qt::TopLeftCorner, radius); }
+ void resetTopLeftRadius() { resetCornerRadius(Qt::TopLeftCorner); }
+
+ qreal topRightRadius() const { return cornerRadius(Qt::TopRightCorner); }
+ void setTopRightRadius(qreal radius) { setCornerRadius(Qt::TopRightCorner, radius); }
+ void resetTopRightRadius() { resetCornerRadius(Qt::TopRightCorner); }
+
+ qreal bottomLeftRadius() const { return cornerRadius(Qt::BottomLeftCorner); }
+ void setBottomLeftRadius(qreal radius) { setCornerRadius(Qt::BottomLeftCorner, radius); }
+ void resetBottomLeftRadius() { resetCornerRadius(Qt::BottomLeftCorner); }
+
+ qreal bottomRightRadius() const { return cornerRadius(Qt::BottomRightCorner); }
+ void setBottomRightRadius(qreal radius) { setCornerRadius(Qt::BottomRightCorner, radius); }
+ void resetBottomRightRadius() { resetCornerRadius(Qt::BottomRightCorner); }
+
+ qreal cornerRadius(Qt::Corner corner) const;
+ void setCornerRadius(Qt::Corner corner, qreal newCornerRadius);
+ void resetCornerRadius(Qt::Corner corner);
+
+ void addToPath(QPainterPath &path, const QQuickPathData &) override;
+
+Q_SIGNALS:
+ void widthChanged();
+ void heightChanged();
+ void strokeAdjustmentChanged();
+ void radiusChanged();
+ void topLeftRadiusChanged();
+ void topRightRadiusChanged();
+ void bottomLeftRadiusChanged();
+ void bottomRightRadiusChanged();
+
+private:
+ void emitCornerRadiusChanged(Qt::Corner corner);
+
+ qreal _width = 0;
+ qreal _height = 0;
+ qreal _strokeAdjustment = 0;
+ struct ExtraData
+ {
+ ExtraData() { std::fill_n(cornerRadii, 4, -1); }
+ qreal radius = 0;
+ qreal cornerRadii[4];
+ };
+ QLazilyAllocated<ExtraData> _extra;
+};
+
+class Q_QUICK_EXPORT QQuickPathPercent : public QQuickPathElement
{
Q_OBJECT
Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY valueChanged)
@@ -416,7 +494,7 @@ private:
qreal _value = 0;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickPathPolyline : public QQuickCurve
+class Q_QUICK_EXPORT QQuickPathPolyline : public QQuickCurve
{
Q_OBJECT
Q_PROPERTY(QPointF start READ start NOTIFY startChanged)
@@ -440,7 +518,7 @@ private:
QVector<QPointF> m_path;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickPathMultiline : public QQuickCurve
+class Q_QUICK_EXPORT QQuickPathMultiline : public QQuickCurve
{
Q_OBJECT
Q_PROPERTY(QPointF start READ start NOTIFY startChanged)
@@ -478,7 +556,7 @@ struct QQuickCachedBezier
};
class QQuickPathPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickPath : public QObject, public QQmlParserStatus
+class Q_QUICK_EXPORT QQuickPath : public QObject, public QQmlParserStatus
{
Q_OBJECT
@@ -487,6 +565,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickPath : public QObject, public QQmlParserStatu
Q_PROPERTY(qreal startX READ startX WRITE setStartX NOTIFY startXChanged)
Q_PROPERTY(qreal startY READ startY WRITE setStartY NOTIFY startYChanged)
Q_PROPERTY(bool closed READ isClosed NOTIFY changed)
+ Q_PROPERTY(bool simplify READ simplify WRITE setSimplify NOTIFY simplifyChanged REVISION(6, 6) FINAL)
Q_PROPERTY(QSizeF scale READ scale WRITE setScale NOTIFY scaleChanged REVISION(2, 14))
Q_CLASSINFO("DefaultProperty", "pathElements")
QML_NAMED_ELEMENT(Path)
@@ -517,10 +596,14 @@ public:
QSizeF scale() const;
void setScale(const QSizeF &scale);
+ bool simplify() const;
+ void setSimplify(bool s);
+
Q_SIGNALS:
void changed();
void startXChanged();
void startYChanged();
+ Q_REVISION(6, 6) void simplifyChanged();
Q_REVISION(2, 14) void scaleChanged();
protected:
@@ -574,7 +657,7 @@ public:
static QPointF sequentialPointAt(const QPainterPath &path, const qreal &pathLength, const QList<AttributePoint> &attributePoints, QQuickCachedBezier &prevBez, qreal p, qreal *angle = nullptr);
};
-class Q_QUICK_PRIVATE_EXPORT QQuickPathText : public QQuickPathElement
+class Q_QUICK_EXPORT QQuickPathText : public QQuickPathElement
{
Q_OBJECT
Q_PROPERTY(qreal x READ x WRITE setX NOTIFY xChanged)
@@ -676,20 +759,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickPathElement)
-QML_DECLARE_TYPE(QQuickPathAttribute)
-QML_DECLARE_TYPE(QQuickCurve)
-QML_DECLARE_TYPE(QQuickPathLine)
-QML_DECLARE_TYPE(QQuickPathMove)
-QML_DECLARE_TYPE(QQuickPathQuad)
-QML_DECLARE_TYPE(QQuickPathCubic)
-QML_DECLARE_TYPE(QQuickPathCatmullRomCurve)
-QML_DECLARE_TYPE(QQuickPathArc)
-QML_DECLARE_TYPE(QQuickPathAngleArc)
-QML_DECLARE_TYPE(QQuickPathSvg)
-QML_DECLARE_TYPE(QQuickPathPercent)
-QML_DECLARE_TYPE(QQuickPathPolyline)
-QML_DECLARE_TYPE(QQuickPath)
-QML_DECLARE_TYPE(QQuickPathText)
-
#endif // QQUICKPATH_H
diff --git a/src/quick/util/qquickpath_p_p.h b/src/quick/util/qquickpath_p_p.h
index 36067df5dd..ae57a56ccc 100644
--- a/src/quick/util/qquickpath_p_p.h
+++ b/src/quick/util/qquickpath_p_p.h
@@ -28,7 +28,7 @@ QT_REQUIRE_CONFIG(quick_path);
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QQuickPathPrivate : public QObjectPrivate
+class Q_QUICK_EXPORT QQuickPathPrivate : public QObjectPrivate
{
Q_DECLARE_PUBLIC(QQuickPath)
@@ -53,6 +53,7 @@ public:
bool closed = false;
bool componentComplete = true;
bool isShapePath = false;
+ bool simplify = false;
};
QT_END_NAMESPACE
diff --git a/src/quick/util/qquickpathinterpolator_p.h b/src/quick/util/qquickpathinterpolator_p.h
index 2d235f2064..c8910173f9 100644
--- a/src/quick/util/qquickpathinterpolator_p.h
+++ b/src/quick/util/qquickpathinterpolator_p.h
@@ -25,7 +25,7 @@ QT_REQUIRE_CONFIG(quick_path);
QT_BEGIN_NAMESPACE
class QQuickPath;
-class Q_QUICK_PRIVATE_EXPORT QQuickPathInterpolator : public QObject
+class Q_QUICK_EXPORT QQuickPathInterpolator : public QObject
{
Q_OBJECT
Q_PROPERTY(QQuickPath *path READ path WRITE setPath NOTIFY pathChanged)
@@ -68,6 +68,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickPathInterpolator)
-
#endif // QQUICKPATHINTERPOLATOR_P_H
diff --git a/src/quick/util/qquickpixmap_p.h b/src/quick/util/qquickpixmap_p.h
new file mode 100644
index 0000000000..f352da8d16
--- /dev/null
+++ b/src/quick/util/qquickpixmap_p.h
@@ -0,0 +1,205 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQUICKPIXMAP_H
+#define QQUICKPIXMAP_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qstring.h>
+#include <QtGui/qpixmap.h>
+#include <QtCore/qmutex.h>
+#include <QtCore/qurl.h>
+#include <private/qtquickglobal_p.h>
+#include <QtQuick/qquickimageprovider.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQmlEngine;
+class QQuickPixmapData;
+class QQuickTextureFactory;
+class QQuickImageProviderOptionsPrivate;
+
+class QQuickDefaultTextureFactory : public QQuickTextureFactory
+{
+ Q_OBJECT
+public:
+ QQuickDefaultTextureFactory(const QImage &i);
+ QSGTexture *createTexture(QQuickWindow *window) const override;
+ QSize textureSize() const override { return size; }
+ int textureByteCount() const override { return size.width() * size.height() * 4; }
+ QImage image() const override { return im; }
+
+private:
+ QImage im;
+ QSize size;
+};
+
+class QQuickImageProviderPrivate
+{
+public:
+ QQuickImageProvider::ImageType type;
+ QQuickImageProvider::Flags flags;
+ bool isProviderWithOptions;
+};
+
+// ### Qt 6: Make public moving to qquickimageprovider.h
+class Q_QUICK_EXPORT QQuickImageProviderOptions
+{
+public:
+ enum AutoTransform {
+ UsePluginDefaultTransform = -1,
+ ApplyTransform = 0,
+ DoNotApplyTransform = 1
+ };
+
+ QQuickImageProviderOptions();
+ ~QQuickImageProviderOptions();
+
+ QQuickImageProviderOptions(const QQuickImageProviderOptions&);
+ QQuickImageProviderOptions& operator=(const QQuickImageProviderOptions&);
+
+ bool operator==(const QQuickImageProviderOptions&) const;
+
+ AutoTransform autoTransform() const;
+ void setAutoTransform(AutoTransform autoTransform);
+
+ bool preserveAspectRatioCrop() const;
+ void setPreserveAspectRatioCrop(bool preserveAspectRatioCrop);
+
+ bool preserveAspectRatioFit() const;
+ void setPreserveAspectRatioFit(bool preserveAspectRatioFit);
+
+ QColorSpace targetColorSpace() const;
+ void setTargetColorSpace(const QColorSpace &colorSpace);
+
+ QRectF sourceClipRect() const;
+ void setSourceClipRect(const QRectF &rect);
+
+private:
+ QSharedDataPointer<QQuickImageProviderOptionsPrivate> d;
+};
+
+/*! \internal
+ A class that encapsulates the action of fetching a pixmap, as well as the
+ pixmap itself (indirectly via QQuickPixmapData::textureFactory) and the
+ responsibility of canceling outstanding requests. Rather than relying on
+ QPixmapCache which doesn't cache all the information Qt Quick needs,
+ QQuickPixmap implements its own cache, that correctly degrades over time.
+ (QQuickPixmapData::release() marks it as being not-currently-used, and
+ QQuickPixmapCache::shrinkCache() sweeps away the least-recently-released
+ instances until the remaining bytes are less than cache_limit.)
+*/
+class Q_QUICK_EXPORT QQuickPixmap
+{
+ Q_DECLARE_TR_FUNCTIONS(QQuickPixmap)
+public:
+ enum Status { Null, Ready, Error, Loading };
+
+ enum Option {
+ Asynchronous = 0x00000001,
+ Cache = 0x00000002
+ };
+ Q_DECLARE_FLAGS(Options, Option)
+
+ QQuickPixmap();
+ QQuickPixmap(QQmlEngine *, const QUrl &);
+ QQuickPixmap(QQmlEngine *, const QUrl &, Options options);
+ QQuickPixmap(QQmlEngine *, const QUrl &, const QRect &region, const QSize &);
+ QQuickPixmap(const QUrl &, const QImage &image);
+ ~QQuickPixmap();
+
+ bool isNull() const;
+ bool isReady() const;
+ bool isError() const;
+ bool isLoading() const;
+
+ Status status() const;
+ QString error() const;
+ const QUrl &url() const;
+ const QSize &implicitSize() const;
+ const QRect &requestRegion() const;
+ const QSize &requestSize() const;
+ QQuickImageProviderOptions::AutoTransform autoTransform() const;
+ int frameCount() const;
+ QImage image() const;
+ void setImage(const QImage &);
+ void setPixmap(const QQuickPixmap &other);
+
+ QColorSpace colorSpace() const;
+
+ QQuickTextureFactory *textureFactory() const;
+
+ QRect rect() const;
+ int width() const;
+ int height() const;
+
+ void load(QQmlEngine *, const QUrl &);
+ void load(QQmlEngine *, const QUrl &, QQuickPixmap::Options options);
+ void load(QQmlEngine *, const QUrl &, const QRect &requestRegion, const QSize &requestSize);
+ void load(QQmlEngine *, const QUrl &, const QRect &requestRegion, const QSize &requestSize, QQuickPixmap::Options options);
+ void load(QQmlEngine *, const QUrl &, const QRect &requestRegion, const QSize &requestSize,
+ QQuickPixmap::Options options, const QQuickImageProviderOptions &providerOptions, int frame = 0, int frameCount = 1,
+ qreal devicePixelRatio = 1.0);
+ void loadImageFromDevice(QQmlEngine *engine, QIODevice *device, const QUrl &url,
+ const QRect &requestRegion, const QSize &requestSize,
+ const QQuickImageProviderOptions &providerOptions, int frame = 0, int frameCount = 1);
+
+ void clear();
+ void clear(QObject *);
+
+ bool connectFinished(QObject *, const char *);
+ bool connectFinished(QObject *, int);
+ bool connectDownloadProgress(QObject *, const char *);
+ bool connectDownloadProgress(QObject *, int);
+
+ static void purgeCache();
+ static bool isCached(const QUrl &url, const QRect &requestRegion, const QSize &requestSize,
+ const int frame, const QQuickImageProviderOptions &options);
+ static bool isScalableImageFormat(const QUrl &url);
+
+ static const QLatin1String itemGrabberScheme;
+
+private:
+ Q_DISABLE_COPY(QQuickPixmap)
+ QQuickPixmapData *d;
+ friend class QQuickPixmapData;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickPixmap::Options)
+
+// ### Qt 6: This should be made public in Qt 6. It's functionality can't be merged into
+// QQuickImageProvider without breaking source compatibility.
+class Q_QUICK_EXPORT QQuickImageProviderWithOptions : public QQuickAsyncImageProvider
+{
+public:
+ QQuickImageProviderWithOptions(ImageType type, Flags flags = Flags());
+
+ QImage requestImage(const QString &id, QSize *size, const QSize& requestedSize) override;
+ QPixmap requestPixmap(const QString &id, QSize *size, const QSize& requestedSize) override;
+ QQuickTextureFactory *requestTexture(const QString &id, QSize *size, const QSize &requestedSize) override;
+ QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override;
+
+ virtual QImage requestImage(const QString &id, QSize *size, const QSize& requestedSize, const QQuickImageProviderOptions &options);
+ virtual QPixmap requestPixmap(const QString &id, QSize *size, const QSize& requestedSize, const QQuickImageProviderOptions &options);
+ virtual QQuickTextureFactory *requestTexture(const QString &id, QSize *size, const QSize &requestedSize, const QQuickImageProviderOptions &options);
+ virtual QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize, const QQuickImageProviderOptions &options);
+
+ static QSize loadSize(const QSize &originalSize, const QSize &requestedSize, const QByteArray &format, const QQuickImageProviderOptions &options,
+ qreal devicePixelRatio = 1.0);
+ static QQuickImageProviderWithOptions *checkedCast(QQuickImageProvider *provider);
+};
+
+QT_END_NAMESPACE
+
+#endif // QQUICKPIXMAP_H
diff --git a/src/quick/util/qquickpixmapcache.cpp b/src/quick/util/qquickpixmapcache.cpp
index e5c19edab0..af9584ea43 100644
--- a/src/quick/util/qquickpixmapcache.cpp
+++ b/src/quick/util/qquickpixmapcache.cpp
@@ -5,6 +5,7 @@
#include <QtQuick/private/qquickimageprovider_p.h>
#include <QtQuick/private/qquickprofiler_p.h>
#include <QtQuick/private/qsgcontext_p.h>
+#include <QtQuick/private/qsgrenderer_p.h>
#include <QtQuick/private/qsgtexturereader_p.h>
#include <QtQuick/qquickwindow.h>
@@ -27,6 +28,7 @@
#include <QtCore/qbuffer.h>
#include <QtCore/qdebug.h>
#include <QtCore/qmetaobject.h>
+#include <QtCore/qscopeguard.h>
#if QT_CONFIG(qml_network)
#include <QtQml/qqmlnetworkaccessmanagerfactory.h>
@@ -34,9 +36,14 @@
#include <QtNetwork/qsslerror.h>
#endif
+#include <private/qdebug_p.h>
+
#define IMAGEREQUEST_MAX_NETWORK_REQUEST_COUNT 8
-#define IMAGEREQUEST_MAX_REDIRECT_RECURSION 16
+
+// After QQuickPixmapCache::unreferencePixmap() it may get deleted via a timer in 30 seconds
#define CACHE_EXPIRE_TIME 30
+
+// How many (1/4) of the unreferenced pixmaps to delete in QQuickPixmapCache::timerEvent()
#define CACHE_REMOVAL_FRACTION 4
#define PIXMAP_PROFILE(Code) Q_QUICK_PROFILE(QQuickProfiler::ProfilePixmapCache, Code)
@@ -49,16 +56,53 @@
QT_BEGIN_NAMESPACE
-const QLatin1String QQuickPixmap::itemGrabberScheme = QLatin1String("itemgrabber");
+using namespace Qt::Literals::StringLiterals;
+
+#if defined(QT_DEBUG) && QT_CONFIG(thread)
+class ThreadAffinityMarker
+{
+public:
+ ThreadAffinityMarker() { attachToCurrentThread(); }
+
+ void assertOnAssignedThread()
+ {
+ QMutexLocker locker(&m_mutex);
+ if (!m_assignedThread)
+ attachToCurrentThread();
+ Q_ASSERT_X(m_assignedThread == QThread::currentThreadId(), Q_FUNC_INFO,
+ "Running on a wrong thread!");
+ }
-Q_LOGGING_CATEGORY(lcImg, "qt.quick.image")
+ void detachFromCurrentThread()
+ {
+ QMutexLocker locker(&m_mutex);
+ m_assignedThread = nullptr;
+ }
+
+ void attachToCurrentThread() { m_assignedThread = QThread::currentThreadId(); }
-#ifndef QT_NO_DEBUG
-static const bool qsg_leak_check = !qEnvironmentVariableIsEmpty("QML_LEAK_CHECK");
+private:
+ Qt::HANDLE m_assignedThread;
+ QMutex m_mutex;
+};
+# define Q_THREAD_AFFINITY_MARKER(x) ThreadAffinityMarker x
+# define Q_ASSERT_CALLED_ON_VALID_THREAD(x) x.assertOnAssignedThread()
+# define Q_DETACH_THREAD_AFFINITY_MARKER(x) x.detachFromCurrentThread()
+#else
+# define Q_THREAD_AFFINITY_MARKER(x)
+# define Q_ASSERT_CALLED_ON_VALID_THREAD(x)
+# define Q_DETACH_THREAD_AFFINITY_MARKER(x)
#endif
-// The cache limit describes the maximum "junk" in the cache.
-static int cache_limit = 2048 * 1024; // 2048 KB cache limit for embedded in qpixmapcache.cpp
+const QLatin1String QQuickPixmap::itemGrabberScheme = QLatin1String("itemgrabber");
+
+Q_STATIC_LOGGING_CATEGORY(lcImg, "qt.quick.image")
+
+/*! \internal
+ The maximum currently-unused image data that can be stored for potential
+ later reuse, in bytes. See QQuickPixmapCache::shrinkCache()
+*/
+static int cache_limit = 2048 * 1024;
static inline QString imageProviderId(const QUrl &url)
{
@@ -110,9 +154,9 @@ public:
bool loading;
QQuickImageProviderOptions providerOptions;
- int redirectCount;
class Event : public QEvent {
+ Q_EVENT_DISABLE_COPY(Event);
public:
Event(ReadError, const QString &, const QSize &, QQuickTextureFactory *factory);
~Event();
@@ -136,22 +180,37 @@ private:
Q_DISABLE_COPY(QQuickPixmapReply)
public:
- static int finishedIndex;
- static int downloadProgressIndex;
+ static int finishedMethodIndex;
+ static int downloadProgressMethodIndex;
};
-class QQuickPixmapReaderThreadObject : public QObject {
+/*! \internal
+ Serves as an endpoint for notifications on the connected reader's thread, thus enforcing
+ execution of their continuation on the thread. */
+class ReaderThreadExecutionEnforcer : public QObject
+{
Q_OBJECT
public:
- QQuickPixmapReaderThreadObject(QQuickPixmapReader *);
- void processJobs();
- bool event(QEvent *e) override;
+ enum Event {
+ ProcessJobs = QEvent::User,
+ };
+
+ ReaderThreadExecutionEnforcer(QQuickPixmapReader *reader);
+
+ /*! \internal
+ Forces the execution of processJobs() on the original reader on the thread it's running on.
+ */
+ void processJobsOnReaderThreadLater();
+
public slots:
void asyncResponseFinished(QQuickImageResponse *response);
+ void asyncResponseFinished();
private slots:
void networkRequestDone();
- void asyncResponseFinished();
private:
+ Q_DISABLE_COPY(ReaderThreadExecutionEnforcer)
+ bool event(QEvent *e) override;
+
QQuickPixmapReader *reader;
};
@@ -168,12 +227,14 @@ public:
static QQuickPixmapReader *instance(QQmlEngine *engine);
static QQuickPixmapReader *existingInstance(QQmlEngine *engine);
+ void startJob(QQuickPixmapReply *job);
protected:
void run() override;
private:
- friend class QQuickPixmapReaderThreadObject;
+ Q_DISABLE_COPY(QQuickPixmapReader)
+ friend class ReaderThreadExecutionEnforcer;
void processJobs();
void processJob(QQuickPixmapReply *, const QUrl &, const QString &, QQuickImageProvider::ImageType, const QSharedPointer<QQuickImageProvider> &);
#if QT_CONFIG(qml_network)
@@ -182,14 +243,30 @@ private:
void asyncResponseFinished(QQuickImageResponse *);
QList<QQuickPixmapReply*> jobs;
- QList<QQuickPixmapReply*> cancelled;
+ QList<QQuickPixmapReply *> cancelledJobs;
QQmlEngine *engine;
- QObject *eventLoopQuitHack;
+#if QT_CONFIG(quick_pixmap_cache_threaded_download)
+ /*! \internal
+ Returns a pointer to the thread object owned by the run loop in QQuickPixmapReader::run.
+ */
+ ReaderThreadExecutionEnforcer *readerThreadExecutionEnforcer()
+ {
+ return runLoopReaderThreadExecutionEnforcer;
+ }
+ QObject *eventLoopQuitHack;
QMutex mutex;
-
- // Owned not by the QQuickPixmapReader, but by the run() function.
- QQuickPixmapReaderThreadObject *threadObject = nullptr;
+ ReaderThreadExecutionEnforcer *runLoopReaderThreadExecutionEnforcer = nullptr;
+#else
+ /*! \internal
+ Returns a pointer to the thread object owned by this instance.
+ */
+ ReaderThreadExecutionEnforcer *readerThreadExecutionEnforcer()
+ {
+ return ownedReaderThreadExecutionEnforcer.get();
+ }
+ std::unique_ptr<ReaderThreadExecutionEnforcer> ownedReaderThreadExecutionEnforcer;
+#endif
#if QT_CONFIG(qml_network)
QNetworkAccessManager *networkAccessManager();
@@ -198,20 +275,33 @@ private:
#endif
QHash<QQuickImageResponse*,QQuickPixmapReply*> asyncResponses;
- static int replyDownloadProgress;
- static int replyFinished;
- static int downloadProgress;
- static int threadNetworkRequestDone;
+ Q_THREAD_AFFINITY_MARKER(m_creatorThreadAffinityMarker);
+ Q_THREAD_AFFINITY_MARKER(m_readerThreadAffinityMarker);
+
+ static int replyDownloadProgressMethodIndex;
+ static int replyFinishedMethodIndex;
+ static int downloadProgressMethodIndex;
+ static int threadNetworkRequestDoneMethodIndex;
static QHash<QQmlEngine *,QQuickPixmapReader*> readers;
public:
static QMutex readerMutex;
};
-class QQuickPixmapStore;
+#if QT_CONFIG(quick_pixmap_cache_threaded_download)
+# define PIXMAP_READER_LOCK() QMutexLocker locker(&mutex)
+#else
+# define PIXMAP_READER_LOCK()
+#endif
+
+class QQuickPixmapCache;
+
+/*! \internal
+ The private storage for QQuickPixmap.
+*/
class QQuickPixmapData
{
public:
- QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QRect &r, const QSize &rs,
+ QQuickPixmapData(const QUrl &u, const QRect &r, const QSize &rs,
const QQuickImageProviderOptions &po, const QString &e)
: refCount(1), frameCount(1), frame(0), inCache(false), pixmapStatus(QQuickPixmap::Error),
url(u), errorString(e), requestRegion(r), requestSize(rs),
@@ -222,10 +312,9 @@ public:
, storeToCache(true)
#endif
{
- declarativePixmaps.insert(pixmap);
}
- QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QRect &r, const QSize &s, const QQuickImageProviderOptions &po,
+ QQuickPixmapData(const QUrl &u, const QRect &r, const QSize &s, const QQuickImageProviderOptions &po,
QQuickImageProviderOptions::AutoTransform aTransform, int frame=0, int frameCount=1)
: refCount(1), frameCount(frameCount), frame(frame), inCache(false), pixmapStatus(QQuickPixmap::Loading),
url(u), requestRegion(r), requestSize(s),
@@ -236,10 +325,9 @@ public:
, storeToCache(true)
#endif
{
- declarativePixmaps.insert(pixmap);
}
- QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, QQuickTextureFactory *texture,
+ QQuickPixmapData(const QUrl &u, QQuickTextureFactory *texture,
const QSize &s, const QRect &r, const QSize &rs, const QQuickImageProviderOptions &po,
QQuickImageProviderOptions::AutoTransform aTransform, int frame=0, int frameCount=1)
: refCount(1), frameCount(frameCount), frame(frame), inCache(false), pixmapStatus(QQuickPixmap::Ready),
@@ -251,10 +339,9 @@ public:
, storeToCache(true)
#endif
{
- declarativePixmaps.insert(pixmap);
}
- QQuickPixmapData(QQuickPixmap *pixmap, QQuickTextureFactory *texture)
+ QQuickPixmapData(QQuickTextureFactory *texture)
: refCount(1), frameCount(1), frame(0), inCache(false), pixmapStatus(QQuickPixmap::Ready),
appliedTransform(QQuickImageProviderOptions::UsePluginDefaultTransform),
textureFactory(texture), reply(nullptr), prevUnreferenced(nullptr),
@@ -265,24 +352,18 @@ public:
{
if (texture)
requestSize = implicitSize = texture->textureSize();
- declarativePixmaps.insert(pixmap);
}
~QQuickPixmapData()
{
- while (!declarativePixmaps.isEmpty()) {
- QQuickPixmap *referencer = declarativePixmaps.first();
- declarativePixmaps.remove(referencer);
- referencer->d = nullptr;
- }
delete textureFactory;
}
int cost() const;
void addref();
- void release(QQuickPixmapStore *store = nullptr);
+ void release(QQuickPixmapCache *store = nullptr);
void addToCache();
- void removeFromCache(QQuickPixmapStore *store = nullptr);
+ void removeFromCache(QQuickPixmapCache *store = nullptr);
uint refCount;
int frameCount;
@@ -301,11 +382,14 @@ public:
QColorSpace targetColorSpace;
QIODevice *specialDevice = nullptr;
+
+ // actual image data, after loading
QQuickTextureFactory *textureFactory;
- QIntrusiveList<QQuickPixmap, &QQuickPixmap::dataListNode> declarativePixmaps;
QQuickPixmapReply *reply;
+ // prev/next pointers to form a linked list for dereferencing pixmaps that are currently unused
+ // (those get lazily deleted in QQuickPixmapCache::shrinkCache())
QQuickPixmapData *prevUnreferenced;
QQuickPixmapData**prevUnreferencedPtr;
QQuickPixmapData *nextUnreferenced;
@@ -313,20 +397,22 @@ public:
#ifdef Q_OS_WEBOS
bool storeToCache;
#endif
+
+private:
+ Q_DISABLE_COPY(QQuickPixmapData)
};
-int QQuickPixmapReply::finishedIndex = -1;
-int QQuickPixmapReply::downloadProgressIndex = -1;
+int QQuickPixmapReply::finishedMethodIndex = -1;
+int QQuickPixmapReply::downloadProgressMethodIndex = -1;
// XXX
QHash<QQmlEngine *,QQuickPixmapReader*> QQuickPixmapReader::readers;
QMutex QQuickPixmapReader::readerMutex;
-int QQuickPixmapReader::replyDownloadProgress = -1;
-int QQuickPixmapReader::replyFinished = -1;
-int QQuickPixmapReader::downloadProgress = -1;
-int QQuickPixmapReader::threadNetworkRequestDone = -1;
-
+int QQuickPixmapReader::replyDownloadProgressMethodIndex = -1;
+int QQuickPixmapReader::replyFinishedMethodIndex = -1;
+int QQuickPixmapReader::downloadProgressMethodIndex = -1;
+int QQuickPixmapReader::threadNetworkRequestDoneMethodIndex = -1;
void QQuickPixmapReply::postReply(ReadError error, const QString &errorString,
const QSize &implicitSize, QQuickTextureFactory *factory)
@@ -349,8 +435,9 @@ QQuickPixmapReply::Event::~Event()
QNetworkAccessManager *QQuickPixmapReader::networkAccessManager()
{
if (!accessManager) {
- Q_ASSERT(threadObject);
- accessManager = QQmlEnginePrivate::get(engine)->createNetworkAccessManager(threadObject);
+ Q_ASSERT(readerThreadExecutionEnforcer());
+ accessManager = QQmlEnginePrivate::get(engine)->createNetworkAccessManager(
+ readerThreadExecutionEnforcer());
}
return accessManager;
}
@@ -463,6 +550,8 @@ public:
}
bool hasOpenGL;
QStringList fileSuffixes;
+private:
+ Q_DISABLE_COPY(BackendSupport)
};
Q_GLOBAL_STATIC(BackendSupport, backendSupport);
@@ -489,10 +578,11 @@ QQuickPixmapReader::QQuickPixmapReader(QQmlEngine *eng)
, accessManager(nullptr)
#endif
{
+ Q_DETACH_THREAD_AFFINITY_MARKER(m_readerThreadAffinityMarker);
+#if QT_CONFIG(quick_pixmap_cache_threaded_download)
eventLoopQuitHack = new QObject;
eventLoopQuitHack->moveToThread(this);
- connect(eventLoopQuitHack, SIGNAL(destroyed(QObject *)), SLOT(quit()), Qt::DirectConnection);
-#if USE_THREADED_DOWNLOAD
+ QObject::connect(eventLoopQuitHack, &QObject::destroyed, this, &QThread::quit, Qt::DirectConnection);
start(QThread::LowestPriority);
#else
run(); // Call nonblocking run for ourselves.
@@ -501,38 +591,54 @@ QQuickPixmapReader::QQuickPixmapReader(QQmlEngine *eng)
QQuickPixmapReader::~QQuickPixmapReader()
{
+ Q_ASSERT_CALLED_ON_VALID_THREAD(m_creatorThreadAffinityMarker);
+
readerMutex.lock();
readers.remove(engine);
readerMutex.unlock();
- mutex.lock();
- // manually cancel all outstanding jobs.
- for (QQuickPixmapReply *reply : std::as_const(jobs)) {
- if (reply->data && reply->data->reply == reply)
- reply->data->reply = nullptr;
- delete reply;
- }
- jobs.clear();
-#if QT_CONFIG(qml_network)
-
- const auto cancelJob = [this](QQuickPixmapReply *reply) {
- if (reply->loading) {
- cancelled.append(reply);
- reply->data = nullptr;
+ {
+ PIXMAP_READER_LOCK();
+ // manually cancel all outstanding jobs.
+ for (QQuickPixmapReply *reply : std::as_const(jobs)) {
+ if (reply->data && reply->data->reply == reply)
+ reply->data->reply = nullptr;
+ delete reply;
}
- };
+ jobs.clear();
+#if QT_CONFIG(qml_network)
+ const auto cancelJob = [this](QQuickPixmapReply *reply) {
+ if (reply->loading) {
+ cancelledJobs.append(reply);
+ reply->data = nullptr;
+ }
+ };
- for (auto *reply : std::as_const(networkJobs))
- cancelJob(reply);
+ for (auto *reply : std::as_const(networkJobs))
+ cancelJob(reply);
- for (auto *reply : std::as_const(asyncResponses))
- cancelJob(reply);
+ for (auto *reply : std::as_const(asyncResponses))
+ cancelJob(reply);
#endif
- if (threadObject) threadObject->processJobs();
- mutex.unlock();
+#if !QT_CONFIG(quick_pixmap_cache_threaded_download)
+ // In this case we won't be waiting, but we are on the correct thread already, so we can
+ // perform housekeeping synchronously now.
+ processJobs();
+#else // QT_CONFIG(quick_pixmap_cache_threaded_download) is true
+ // Perform housekeeping on all the jobs cancelled above soon...
+ if (readerThreadExecutionEnforcer())
+ readerThreadExecutionEnforcer()->processJobsOnReaderThreadLater();
+#endif
+ }
+#if QT_CONFIG(quick_pixmap_cache_threaded_download)
+ // ... schedule stopping of this thread via the eventLoopQuitHack (processJobs scheduled above
+ // will run first) ...
eventLoopQuitHack->deleteLater();
+ // ... and wait() for it all to finish, as the thread will only quit after eventLoopQuitHack
+ // has been deleted.
wait();
+#endif
#if QT_CONFIG(qml_network)
// While we've been waiting, the other thread may have added
@@ -558,28 +664,11 @@ QQuickPixmapReader::~QQuickPixmapReader()
#if QT_CONFIG(qml_network)
void QQuickPixmapReader::networkRequestDone(QNetworkReply *reply)
{
+ Q_ASSERT_CALLED_ON_VALID_THREAD(m_readerThreadAffinityMarker);
+
QQuickPixmapReply *job = networkJobs.take(reply);
if (job) {
- job->redirectCount++;
- if (job->redirectCount < IMAGEREQUEST_MAX_REDIRECT_RECURSION) {
- QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
- if (redirect.isValid()) {
- QUrl url = reply->url().resolved(redirect.toUrl());
- QNetworkRequest req(url);
- req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
-
- reply->deleteLater();
- reply = networkAccessManager()->get(req);
-
- QMetaObject::connect(reply, replyDownloadProgress, job, downloadProgress);
- QMetaObject::connect(reply, replyFinished, threadObject, threadNetworkRequestDone);
-
- networkJobs.insert(reply, job);
- return;
- }
- }
-
QImage image;
QQuickPixmapReply::ReadError error = QQuickPixmapReply::NoError;
QString errorString;
@@ -614,20 +703,22 @@ void QQuickPixmapReader::networkRequestDone(QNetworkReply *reply)
// send completion event to the QQuickPixmapReply
if (!factory)
factory = QQuickTextureFactory::textureFactoryForImage(image);
- mutex.lock();
- if (!cancelled.contains(job))
+
+ PIXMAP_READER_LOCK();
+ if (!cancelledJobs.contains(job))
job->postReply(error, errorString, readSize, factory);
- mutex.unlock();
}
reply->deleteLater();
- // kick off event loop again incase we have dropped below max request count
- threadObject->processJobs();
+ // kick off event loop again in case we have dropped below max request count
+ readerThreadExecutionEnforcer()->processJobsOnReaderThreadLater();
}
#endif // qml_network
void QQuickPixmapReader::asyncResponseFinished(QQuickImageResponse *response)
{
+ Q_ASSERT_CALLED_ON_VALID_THREAD(m_readerThreadAffinityMarker);
+
QQuickPixmapReply *job = asyncResponses.take(response);
if (job) {
@@ -639,41 +730,40 @@ void QQuickPixmapReader::asyncResponseFinished(QQuickImageResponse *response)
errorString = response->errorString();
} else {
t = response->textureFactory();
- }
- mutex.lock();
- if (!cancelled.contains(job))
+ }
+
+ PIXMAP_READER_LOCK();
+ if (!cancelledJobs.contains(job))
job->postReply(error, errorString, t ? t->textureSize() : QSize(), t);
else
delete t;
- mutex.unlock();
}
response->deleteLater();
- // kick off event loop again incase we have dropped below max request count
- threadObject->processJobs();
+ // kick off event loop again in case we have dropped below max request count
+ readerThreadExecutionEnforcer()->processJobsOnReaderThreadLater();
}
-QQuickPixmapReaderThreadObject::QQuickPixmapReaderThreadObject(QQuickPixmapReader *i)
-: reader(i)
-{
-}
+ReaderThreadExecutionEnforcer::ReaderThreadExecutionEnforcer(QQuickPixmapReader *i) : reader(i) { }
-void QQuickPixmapReaderThreadObject::processJobs()
+void ReaderThreadExecutionEnforcer::processJobsOnReaderThreadLater()
{
- QCoreApplication::postEvent(this, new QEvent(QEvent::User));
+ QCoreApplication::postEvent(
+ this, new QEvent(QEvent::Type(ReaderThreadExecutionEnforcer::ProcessJobs)));
}
-bool QQuickPixmapReaderThreadObject::event(QEvent *e)
+bool ReaderThreadExecutionEnforcer::event(QEvent *e)
{
- if (e->type() == QEvent::User) {
+ switch (e->type()) {
+ case QEvent::Type(ReaderThreadExecutionEnforcer::ProcessJobs):
reader->processJobs();
return true;
- } else {
+ default:
return QObject::event(e);
}
}
-void QQuickPixmapReaderThreadObject::networkRequestDone()
+void ReaderThreadExecutionEnforcer::networkRequestDone()
{
#if QT_CONFIG(qml_network)
QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
@@ -681,12 +771,12 @@ void QQuickPixmapReaderThreadObject::networkRequestDone()
#endif
}
-void QQuickPixmapReaderThreadObject::asyncResponseFinished(QQuickImageResponse *response)
+void ReaderThreadExecutionEnforcer::asyncResponseFinished(QQuickImageResponse *response)
{
reader->asyncResponseFinished(response);
}
-void QQuickPixmapReaderThreadObject::asyncResponseFinished()
+void ReaderThreadExecutionEnforcer::asyncResponseFinished()
{
QQuickImageResponse *response = static_cast<QQuickImageResponse *>(sender());
asyncResponseFinished(response);
@@ -694,16 +784,17 @@ void QQuickPixmapReaderThreadObject::asyncResponseFinished()
void QQuickPixmapReader::processJobs()
{
- QMutexLocker locker(&mutex);
+ Q_ASSERT_CALLED_ON_VALID_THREAD(m_readerThreadAffinityMarker);
+ PIXMAP_READER_LOCK();
while (true) {
- if (cancelled.isEmpty() && jobs.isEmpty())
+ if (cancelledJobs.isEmpty() && jobs.isEmpty())
return; // Nothing else to do
// Clean cancelled jobs
- if (!cancelled.isEmpty()) {
- for (int i = 0; i < cancelled.size(); ++i) {
- QQuickPixmapReply *job = cancelled.at(i);
+ if (!cancelledJobs.isEmpty()) {
+ for (int i = 0; i < cancelledJobs.size(); ++i) {
+ QQuickPixmapReply *job = cancelledJobs.at(i);
#if QT_CONFIG(qml_network)
QNetworkReply *reply = networkJobs.key(job, 0);
if (reply) {
@@ -724,7 +815,7 @@ void QQuickPixmapReader::processJobs()
// deleteLater, since not owned by this thread
job->deleteLater();
}
- cancelled.clear();
+ cancelledJobs.clear();
}
if (!jobs.isEmpty()) {
@@ -753,7 +844,6 @@ void QQuickPixmapReader::processJobs()
;
}
-
if (usableJob) {
jobs.removeAt(i);
@@ -761,9 +851,13 @@ void QQuickPixmapReader::processJobs()
PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingStarted>(url));
+#if QT_CONFIG(quick_pixmap_cache_threaded_download)
locker.unlock();
+ auto relockMutexGuard = qScopeGuard(([&locker]() {
+ locker.relock();
+ }));
+#endif
processJob(job, url, localFile, imageType, provider);
- locker.relock();
}
}
@@ -776,6 +870,8 @@ void QQuickPixmapReader::processJobs()
void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &url, const QString &localFile,
QQuickImageProvider::ImageType imageType, const QSharedPointer<QQuickImageProvider> &provider)
{
+ Q_ASSERT_CALLED_ON_VALID_THREAD(m_readerThreadAffinityMarker);
+
// fetch
if (url.scheme() == QLatin1String("image")) {
// Use QQuickImageProvider
@@ -783,10 +879,9 @@ void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &u
if (imageType == QQuickImageProvider::Invalid) {
QString errorStr = QQuickPixmap::tr("Invalid image provider: %1").arg(url.toString());
- mutex.lock();
- if (!cancelled.contains(runningJob))
+ PIXMAP_READER_LOCK();
+ if (!cancelledJobs.contains(runningJob))
runningJob->postReply(QQuickPixmapReply::Loading, errorStr, readSize, nullptr);
- mutex.unlock();
return;
}
@@ -814,10 +909,11 @@ void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &u
errorCode = QQuickPixmapReply::Loading;
errorStr = QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString());
}
- mutex.lock();
- if (!cancelled.contains(runningJob))
- runningJob->postReply(errorCode, errorStr, readSize, QQuickTextureFactory::textureFactoryForImage(image));
- mutex.unlock();
+ PIXMAP_READER_LOCK();
+ if (!cancelledJobs.contains(runningJob)) {
+ runningJob->postReply(errorCode, errorStr, readSize,
+ QQuickTextureFactory::textureFactoryForImage(image));
+ }
break;
}
@@ -835,10 +931,13 @@ void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &u
errorCode = QQuickPixmapReply::Loading;
errorStr = QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString());
}
- mutex.lock();
- if (!cancelled.contains(runningJob))
- runningJob->postReply(errorCode, errorStr, readSize, QQuickTextureFactory::textureFactoryForImage(pixmap.toImage()));
- mutex.unlock();
+
+ PIXMAP_READER_LOCK();
+ if (!cancelledJobs.contains(runningJob)) {
+ runningJob->postReply(
+ errorCode, errorStr, readSize,
+ QQuickTextureFactory::textureFactoryForImage(pixmap.toImage()));
+ }
break;
}
@@ -856,12 +955,11 @@ void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &u
errorCode = QQuickPixmapReply::Loading;
errorStr = QQuickPixmap::tr("Failed to get texture from provider: %1").arg(url.toString());
}
- mutex.lock();
- if (!cancelled.contains(runningJob))
+ PIXMAP_READER_LOCK();
+ if (!cancelledJobs.contains(runningJob))
runningJob->postReply(errorCode, errorStr, readSize, t);
else
delete t;
- mutex.unlock();
break;
}
@@ -876,7 +974,8 @@ void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &u
}
{
- QObject::connect(response, SIGNAL(finished()), threadObject, SLOT(asyncResponseFinished()));
+ QObject::connect(response, &QQuickImageResponse::finished, readerThreadExecutionEnforcer(),
+ qOverload<>(&ReaderThreadExecutionEnforcer::asyncResponseFinished));
// as the response object can outlive the provider QSharedPointer, we have to extend the pointee's lifetime by that of the response
// we do this by capturing a copy of the QSharedPointer in a lambda, and dropping it once the lambda has been called
auto provider_copy = provider; // capturing provider would capture it as a const reference, and copy capture with initializer is only available in C++14
@@ -889,8 +988,9 @@ void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &u
//
// loadAcquire() synchronizes-with storeRelease() in QQuickImageResponsePrivate::_q_finished():
if (static_cast<QQuickImageResponsePrivate*>(QObjectPrivate::get(response))->finished.loadAcquire()) {
- QMetaObject::invokeMethod(threadObject, "asyncResponseFinished",
- Qt::QueuedConnection, Q_ARG(QQuickImageResponse*, response));
+ QMetaObject::invokeMethod(readerThreadExecutionEnforcer(), "asyncResponseFinished",
+ Qt::QueuedConnection,
+ Q_ARG(QQuickImageResponse *, response));
}
asyncResponses.insert(response, runningJob);
@@ -929,10 +1029,9 @@ void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &u
errorStr += QString::fromLatin1(" (%1)").arg(f.fileName());
errorCode = QQuickPixmapReply::Decoding;
}
- mutex.lock();
- if (!cancelled.contains(runningJob))
+ PIXMAP_READER_LOCK();
+ if (!cancelledJobs.contains(runningJob))
runningJob->postReply(errorCode, errorStr, readSize, factory);
- mutex.unlock();
return;
} else {
int frameCount;
@@ -952,10 +1051,11 @@ void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &u
errorCode = QQuickPixmapReply::Loading;
}
}
- mutex.lock();
- if (!cancelled.contains(runningJob))
- runningJob->postReply(errorCode, errorStr, readSize, QQuickTextureFactory::textureFactoryForImage(image));
- mutex.unlock();
+ PIXMAP_READER_LOCK();
+ if (!cancelledJobs.contains(runningJob)) {
+ runningJob->postReply(errorCode, errorStr, readSize,
+ QQuickTextureFactory::textureFactoryForImage(image));
+ }
} else {
#if QT_CONFIG(qml_network)
// Network resource
@@ -963,8 +1063,10 @@ void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &u
req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
QNetworkReply *reply = networkAccessManager()->get(req);
- QMetaObject::connect(reply, replyDownloadProgress, runningJob, downloadProgress);
- QMetaObject::connect(reply, replyFinished, threadObject, threadNetworkRequestDone);
+ QMetaObject::connect(reply, replyDownloadProgressMethodIndex, runningJob,
+ downloadProgressMethodIndex);
+ QMetaObject::connect(reply, replyFinishedMethodIndex, readerThreadExecutionEnforcer(),
+ threadNetworkRequestDoneMethodIndex);
networkJobs.insert(reply, runningJob);
#else
@@ -994,24 +1096,28 @@ QQuickPixmapReader *QQuickPixmapReader::existingInstance(QQmlEngine *engine)
QQuickPixmapReply *QQuickPixmapReader::getImage(QQuickPixmapData *data)
{
- mutex.lock();
QQuickPixmapReply *reply = new QQuickPixmapReply(data);
reply->engineForReader = engine;
- jobs.append(reply);
- // XXX
- if (threadObject) threadObject->processJobs();
- mutex.unlock();
return reply;
}
+void QQuickPixmapReader::startJob(QQuickPixmapReply *job)
+{
+ PIXMAP_READER_LOCK();
+ jobs.append(job);
+ if (readerThreadExecutionEnforcer())
+ readerThreadExecutionEnforcer()->processJobsOnReaderThreadLater();
+}
+
void QQuickPixmapReader::cancel(QQuickPixmapReply *reply)
{
- mutex.lock();
+ PIXMAP_READER_LOCK();
if (reply->loading) {
- cancelled.append(reply);
+ cancelledJobs.append(reply);
reply->data = nullptr;
// XXX
- if (threadObject) threadObject->processJobs();
+ if (readerThreadExecutionEnforcer())
+ readerThreadExecutionEnforcer()->processJobsOnReaderThreadLater();
} else {
// If loading was started (reply removed from jobs) but the reply was never processed
// (otherwise it would have deleted itself) we need to profile an error.
@@ -1020,50 +1126,46 @@ void QQuickPixmapReader::cancel(QQuickPixmapReply *reply)
}
delete reply;
}
- mutex.unlock();
}
void QQuickPixmapReader::run()
{
- if (replyDownloadProgress == -1) {
+ Q_ASSERT_CALLED_ON_VALID_THREAD(m_readerThreadAffinityMarker);
+
+ if (replyDownloadProgressMethodIndex == -1) {
#if QT_CONFIG(qml_network)
- replyDownloadProgress = QMetaMethod::fromSignal(&QNetworkReply::downloadProgress).methodIndex();
- replyFinished = QMetaMethod::fromSignal(&QNetworkReply::finished).methodIndex();
- const QMetaObject *ir = &QQuickPixmapReaderThreadObject::staticMetaObject;
- threadNetworkRequestDone = ir->indexOfSlot("networkRequestDone()");
+ replyDownloadProgressMethodIndex =
+ QMetaMethod::fromSignal(&QNetworkReply::downloadProgress).methodIndex();
+ replyFinishedMethodIndex = QMetaMethod::fromSignal(&QNetworkReply::finished).methodIndex();
+ const QMetaObject *ir = &ReaderThreadExecutionEnforcer::staticMetaObject;
+ threadNetworkRequestDoneMethodIndex = ir->indexOfSlot("networkRequestDone()");
#endif
- downloadProgress = QMetaMethod::fromSignal(&QQuickPixmapReply::downloadProgress).methodIndex();
+ downloadProgressMethodIndex =
+ QMetaMethod::fromSignal(&QQuickPixmapReply::downloadProgress).methodIndex();
}
+#if QT_CONFIG(quick_pixmap_cache_threaded_download)
const auto guard = qScopeGuard([this]() {
- // We need to delete the threadObject from the same thread.
- QMutexLocker lock(&mutex);
- delete threadObject;
- threadObject = nullptr;
+ // We need to delete the runLoopReaderThreadExecutionEnforcer from the same thread.
+ PIXMAP_READER_LOCK();
+ delete runLoopReaderThreadExecutionEnforcer;
+ runLoopReaderThreadExecutionEnforcer = nullptr;
});
{
- QMutexLocker lock(&mutex);
- Q_ASSERT(!threadObject);
- threadObject = new QQuickPixmapReaderThreadObject(this);
+ PIXMAP_READER_LOCK();
+ Q_ASSERT(!runLoopReaderThreadExecutionEnforcer);
+ runLoopReaderThreadExecutionEnforcer = new ReaderThreadExecutionEnforcer(this);
}
processJobs();
-#if USE_THREADED_DOWNLOAD
exec();
+#else
+ ownedReaderThreadExecutionEnforcer = std::make_unique<ReaderThreadExecutionEnforcer>(this);
+ processJobs();
#endif
}
-class QQuickPixmapKey
-{
-public:
- const QUrl *url;
- const QRect *region;
- const QSize *size;
- int frame;
- QQuickImageProviderOptions options;
-};
-
inline bool operator==(const QQuickPixmapKey &lhs, const QQuickPixmapKey &rhs)
{
return *lhs.url == *rhs.url &&
@@ -1078,62 +1180,70 @@ inline size_t qHash(const QQuickPixmapKey &key, size_t seed) noexcept
return qHashMulti(seed, *key.url, *key.region, *key.size, key.frame, key.options.autoTransform());
}
-class QQuickPixmapStore : public QObject
+#ifndef QT_NO_DEBUG_STREAM
+inline QDebug operator<<(QDebug debug, const QQuickPixmapKey &key)
{
- Q_OBJECT
-public:
- QQuickPixmapStore();
- ~QQuickPixmapStore();
-
- void unreferencePixmap(QQuickPixmapData *);
- void referencePixmap(QQuickPixmapData *);
-
- void purgeCache();
-
-protected:
- void timerEvent(QTimerEvent *) override;
-
-public:
- QHash<QQuickPixmapKey, QQuickPixmapData *> m_cache;
-
-private:
- void shrinkCache(int remove);
-
- QQuickPixmapData *m_unreferencedPixmaps;
- QQuickPixmapData *m_lastUnreferencedPixmap;
+ QDebugStateSaver saver(debug);
+ debug.nospace();
+ if (!key.url) {
+ debug << "QQuickPixmapKey(0)";
+ return debug;
+ }
- int m_unreferencedCost;
- int m_timerId;
- bool m_destroying;
-};
-Q_GLOBAL_STATIC(QQuickPixmapStore, pixmapStore);
+ debug << "QQuickPixmapKey(" << key.url->toString() << " frame=" << key.frame;
+ if (!key.region->isEmpty()) {
+ debug << " region=";
+ QtDebugUtils::formatQRect(debug, *key.region);
+ }
+ if (!key.size->isEmpty()) {
+ debug << " size=";
+ QtDebugUtils::formatQSize(debug, *key.size);
+ }
+ debug << ')';
+ return debug;
+}
+#endif
+QQuickPixmapCache *QQuickPixmapCache::instance()
+{
+ static QQuickPixmapCache self;
+ return &self;
+}
-QQuickPixmapStore::QQuickPixmapStore()
- : m_unreferencedPixmaps(nullptr), m_lastUnreferencedPixmap(nullptr), m_unreferencedCost(0), m_timerId(-1), m_destroying(false)
+QQuickPixmapCache::~QQuickPixmapCache()
{
+ destroyCache();
}
-QQuickPixmapStore::~QQuickPixmapStore()
+/*! \internal
+ Empty the cache completely, to prevent leaks. Returns the number of
+ leaked pixmaps (should always be \c 0).
+
+ This is work the destructor needs to do, but we put it into a function
+ only to make it testable in autotests, because the static instance()
+ cannot be destroyed before shutdown.
+*/
+int QQuickPixmapCache::destroyCache()
{
+ if (m_destroying)
+ return -1;
+
m_destroying = true;
-#ifndef QT_NO_DEBUG
- int leakedPixmaps = 0;
-#endif
// Prevent unreferencePixmap() from assuming it needs to kick
// off the cache expiry timer, as we're shrinking the cache
// manually below after releasing all the pixmaps.
m_timerId = -2;
// unreference all (leaked) pixmaps
+ int leakedPixmaps = 0;
const auto cache = m_cache; // NOTE: intentional copy (QTBUG-65077); releasing items from the cache modifies m_cache.
for (auto *pixmap : cache) {
- int currRefCount = pixmap->refCount;
+ auto currRefCount = pixmap->refCount;
if (currRefCount) {
-#ifndef QT_NO_DEBUG
leakedPixmaps++;
-#endif
+ qCDebug(lcQsgLeak) << "leaked pixmap: refCount" << pixmap->refCount << pixmap->url << "frame" << pixmap->frame
+ << "size" << pixmap->requestSize << "region" << pixmap->requestRegion;
while (currRefCount > 0) {
pixmap->release(this);
currRefCount--;
@@ -1142,17 +1252,29 @@ QQuickPixmapStore::~QQuickPixmapStore()
}
// free all unreferenced pixmaps
- while (m_lastUnreferencedPixmap) {
+ while (m_lastUnreferencedPixmap)
shrinkCache(20);
- }
-#ifndef QT_NO_DEBUG
- if (leakedPixmaps && qsg_leak_check)
- qDebug("Number of leaked pixmaps: %i", leakedPixmaps);
-#endif
+ qCDebug(lcQsgLeak, "Number of leaked pixmaps: %i", leakedPixmaps);
+ return leakedPixmaps;
+}
+
+qsizetype QQuickPixmapCache::referencedCost() const
+{
+ qsizetype ret = 0;
+ QMutexLocker locker(&m_cacheMutex);
+ for (const auto *pixmap : std::as_const(m_cache)) {
+ if (pixmap->refCount)
+ ret += pixmap->cost();
+ }
+ return ret;
}
-void QQuickPixmapStore::unreferencePixmap(QQuickPixmapData *data)
+/*! \internal
+ Declare that \a data is currently unused so that shrinkCache() can lazily
+ delete it later.
+*/
+void QQuickPixmapCache::unreferencePixmap(QQuickPixmapData *data)
{
Q_ASSERT(data->prevUnreferenced == nullptr);
Q_ASSERT(data->prevUnreferencedPtr == nullptr);
@@ -1160,8 +1282,10 @@ void QQuickPixmapStore::unreferencePixmap(QQuickPixmapData *data)
data->nextUnreferenced = m_unreferencedPixmaps;
data->prevUnreferencedPtr = &m_unreferencedPixmaps;
- if (!m_destroying) // the texture factories may have been cleaned up already.
+ if (!m_destroying) { // the texture factories may have been cleaned up already.
m_unreferencedCost += data->cost();
+ qCDebug(lcImg) << data->url << "had cost" << data->cost() << "of total unreferenced" << m_unreferencedCost;
+ }
m_unreferencedPixmaps = data;
if (m_unreferencedPixmaps->nextUnreferenced) {
@@ -1180,7 +1304,11 @@ void QQuickPixmapStore::unreferencePixmap(QQuickPixmapData *data)
}
}
-void QQuickPixmapStore::referencePixmap(QQuickPixmapData *data)
+/*! \internal
+ Declare that \a data is being used (by a QQuickPixmap) so that
+ shrinkCache() won't delete it. (This is not reference counting though.)
+*/
+void QQuickPixmapCache::referencePixmap(QQuickPixmapData *data)
{
Q_ASSERT(data->prevUnreferencedPtr);
@@ -1197,10 +1325,16 @@ void QQuickPixmapStore::referencePixmap(QQuickPixmapData *data)
data->prevUnreferenced = nullptr;
m_unreferencedCost -= data->cost();
+ qCDebug(lcImg) << data->url << "subtracts cost" << data->cost() << "of total" << m_unreferencedCost;
}
-void QQuickPixmapStore::shrinkCache(int remove)
+/*! \internal
+ Delete the least-recently-released QQuickPixmapData instances
+ until the remaining bytes are less than cache_limit.
+*/
+void QQuickPixmapCache::shrinkCache(int remove)
{
+ qCDebug(lcImg) << "reduce unreferenced cost" << m_unreferencedCost << "to less than limit" << cache_limit;
while ((remove > 0 || m_unreferencedCost > cache_limit) && m_lastUnreferencedPixmap) {
QQuickPixmapData *data = m_lastUnreferencedPixmap;
Q_ASSERT(data->nextUnreferenced == nullptr);
@@ -1219,7 +1353,7 @@ void QQuickPixmapStore::shrinkCache(int remove)
}
}
-void QQuickPixmapStore::timerEvent(QTimerEvent *)
+void QQuickPixmapCache::timerEvent(QTimerEvent *)
{
int removalCost = m_unreferencedCost / CACHE_REMOVAL_FRACTION;
@@ -1231,23 +1365,24 @@ void QQuickPixmapStore::timerEvent(QTimerEvent *)
}
}
-void QQuickPixmapStore::purgeCache()
+void QQuickPixmapCache::purgeCache()
{
shrinkCache(m_unreferencedCost);
}
void QQuickPixmap::purgeCache()
{
- pixmapStore()->purgeCache();
+ QQuickPixmapCache::instance()->purgeCache();
}
QQuickPixmapReply::QQuickPixmapReply(QQuickPixmapData *d)
: data(d), engineForReader(nullptr), requestRegion(d->requestRegion), requestSize(d->requestSize),
- url(d->url), loading(false), providerOptions(d->providerOptions), redirectCount(0)
+ url(d->url), loading(false), providerOptions(d->providerOptions)
{
- if (finishedIndex == -1) {
- finishedIndex = QMetaMethod::fromSignal(&QQuickPixmapReply::finished).methodIndex();
- downloadProgressIndex = QMetaMethod::fromSignal(&QQuickPixmapReply::downloadProgress).methodIndex();
+ if (finishedMethodIndex == -1) {
+ finishedMethodIndex = QMetaMethod::fromSignal(&QQuickPixmapReply::finished).methodIndex();
+ downloadProgressMethodIndex =
+ QMetaMethod::fromSignal(&QQuickPixmapReply::downloadProgress).methodIndex();
}
}
@@ -1303,10 +1438,10 @@ void QQuickPixmapData::addref()
++refCount;
PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapReferenceCountChanged>(url, refCount));
if (prevUnreferencedPtr)
- pixmapStore()->referencePixmap(this);
+ QQuickPixmapCache::instance()->referencePixmap(this);
}
-void QQuickPixmapData::release(QQuickPixmapStore *store)
+void QQuickPixmapData::release(QQuickPixmapCache *store)
{
Q_ASSERT(refCount > 0);
--refCount;
@@ -1323,7 +1458,7 @@ void QQuickPixmapData::release(QQuickPixmapStore *store)
QQuickPixmapReader::readerMutex.unlock();
}
- store = store ? store : pixmapStore();
+ store = store ? store : QQuickPixmapCache::instance();
if (pixmapStatus == QQuickPixmap::Ready
#ifdef Q_OS_WEBOS
&& storeToCache
@@ -1340,32 +1475,52 @@ void QQuickPixmapData::release(QQuickPixmapStore *store)
}
}
+/*! \internal
+ Add this to the QQuickPixmapCache singleton.
+
+ \note The actual image will end up in QQuickPixmapData::textureFactory.
+ At the time addToCache() is called, it's generally not yet loaded; so the
+ qCDebug() below cannot say how much data we're committing to storing.
+ (On the other hand, removeFromCache() can tell.) QQuickTextureFactory is an
+ abstraction for image data. See QQuickDefaultTextureFactory for example:
+ it stores a QImage directly. Other QQuickTextureFactory subclasses store data
+ in other ways.
+*/
void QQuickPixmapData::addToCache()
{
if (!inCache) {
QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, providerOptions };
- pixmapStore()->m_cache.insert(key, this);
+ QMutexLocker locker(&QQuickPixmapCache::instance()->m_cacheMutex);
+ if (lcImg().isDebugEnabled()) {
+ qCDebug(lcImg) << "adding" << key << "to total" << QQuickPixmapCache::instance()->m_cache.size();
+ for (auto it = QQuickPixmapCache::instance()->m_cache.keyBegin(); it != QQuickPixmapCache::instance()->m_cache.keyEnd(); ++it) {
+ if (*(it->url) == url && it->frame == frame)
+ qDebug(lcImg) << " similar pre-existing:" << *it;
+ }
+ }
+ QQuickPixmapCache::instance()->m_cache.insert(key, this);
inCache = true;
PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapCacheCountChanged>(
- url, pixmapStore()->m_cache.size()));
+ url, QQuickPixmapCache::instance()->m_cache.size()));
}
}
-void QQuickPixmapData::removeFromCache(QQuickPixmapStore *store)
+void QQuickPixmapData::removeFromCache(QQuickPixmapCache *store)
{
-
if (inCache) {
if (!store)
- store = pixmapStore();
+ store = QQuickPixmapCache::instance();
QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, providerOptions };
+ QMutexLocker locker(&QQuickPixmapCache::instance()->m_cacheMutex);
store->m_cache.remove(key);
+ qCDebug(lcImg) << "removed" << key << implicitSize << "; total remaining" << QQuickPixmapCache::instance()->m_cache.size();
inCache = false;
PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapCacheCountChanged>(
url, store->m_cache.size()));
}
}
-static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, QQmlEngine *engine, const QUrl &url,
+static QQuickPixmapData* createPixmapDataSync(QQmlEngine *engine, const QUrl &url,
const QRect &requestRegion, const QSize &requestSize,
const QQuickImageProviderOptions &providerOptions, int frame, bool *ok,
qreal devicePixelRatio)
@@ -1383,7 +1538,7 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q
switch (imageType) {
case QQuickImageProvider::Invalid:
- return new QQuickPixmapData(declarativePixmap, url, requestRegion, requestSize, providerOptions,
+ return new QQuickPixmapData(url, requestRegion, requestSize, providerOptions,
QQuickPixmap::tr("Invalid image provider: %1").arg(url.toString()));
case QQuickImageProvider::Texture:
{
@@ -1391,7 +1546,7 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q
: provider->requestTexture(imageId(url), &readSize, requestSize);
if (texture) {
*ok = true;
- return new QQuickPixmapData(declarativePixmap, url, texture, readSize, requestRegion, requestSize,
+ return new QQuickPixmapData(url, texture, readSize, requestRegion, requestSize,
providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
}
break;
@@ -1403,7 +1558,7 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q
: provider->requestImage(imageId(url), &readSize, requestSize);
if (!image.isNull()) {
*ok = true;
- return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(image),
+ return new QQuickPixmapData(url, QQuickTextureFactory::textureFactoryForImage(image),
readSize, requestRegion, requestSize, providerOptions,
QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
}
@@ -1415,7 +1570,7 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q
: provider->requestPixmap(imageId(url), &readSize, requestSize);
if (!pixmap.isNull()) {
*ok = true;
- return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(pixmap.toImage()),
+ return new QQuickPixmapData(url, QQuickTextureFactory::textureFactoryForImage(pixmap.toImage()),
readSize, requestRegion, requestSize, providerOptions,
QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
}
@@ -1429,7 +1584,7 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q
}
// provider has bad image type, or provider returned null image
- return new QQuickPixmapData(declarativePixmap, url, requestRegion, requestSize, providerOptions,
+ return new QQuickPixmapData(url, requestRegion, requestSize, providerOptions,
QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString()));
}
@@ -1447,7 +1602,7 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q
QQuickTextureFactory *factory = texReader.read();
if (factory) {
*ok = true;
- return new QQuickPixmapData(declarativePixmap, url, factory, factory->textureSize(), requestRegion, requestSize,
+ return new QQuickPixmapData(url, factory, factory->textureSize(), requestRegion, requestSize,
providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
} else {
errorString = QQuickPixmap::tr("Error decoding: %1").arg(url.toString());
@@ -1461,7 +1616,7 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q
if (readImage(url, &f, &image, &errorString, &readSize, &frameCount, requestRegion, requestSize,
providerOptions, &appliedTransform, frame, devicePixelRatio)) {
*ok = true;
- return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(image), readSize, requestRegion, requestSize,
+ return new QQuickPixmapData(url, QQuickTextureFactory::textureFactoryForImage(image), readSize, requestRegion, requestSize,
providerOptions, appliedTransform, frame, frameCount);
} else if (f.fileName() != localFile) {
errorString += QString::fromLatin1(" (%1)").arg(f.fileName());
@@ -1470,7 +1625,7 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q
} else {
errorString = QQuickPixmap::tr("Cannot open: %1").arg(url.toString());
}
- return new QQuickPixmapData(declarativePixmap, url, requestRegion, requestSize, providerOptions, errorString);
+ return new QQuickPixmapData(url, requestRegion, requestSize, providerOptions, errorString);
}
@@ -1492,6 +1647,12 @@ QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url)
load(engine, url);
}
+QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url, Options options)
+: d(nullptr)
+{
+ load(engine, url, options);
+}
+
QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url, const QRect &region, const QSize &size)
: d(nullptr)
{
@@ -1500,7 +1661,7 @@ QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url, const QRect &reg
QQuickPixmap::QQuickPixmap(const QUrl &url, const QImage &image)
{
- d = new QQuickPixmapData(this, url, new QQuickDefaultTextureFactory(image), image.size(), QRect(), QSize(),
+ d = new QQuickPixmapData(url, new QQuickDefaultTextureFactory(image), image.size(), QRect(), QSize(),
QQuickImageProviderOptions(), QQuickImageProviderOptions::UsePluginDefaultTransform);
d->addToCache();
}
@@ -1508,7 +1669,6 @@ QQuickPixmap::QQuickPixmap(const QUrl &url, const QImage &image)
QQuickPixmap::~QQuickPixmap()
{
if (d) {
- d->declarativePixmaps.remove(this);
d->release();
d = nullptr;
}
@@ -1616,18 +1776,24 @@ void QQuickPixmap::setImage(const QImage &p)
{
clear();
- if (!p.isNull())
- d = new QQuickPixmapData(this, QQuickTextureFactory::textureFactoryForImage(p));
+ if (!p.isNull()) {
+ if (d)
+ d->release();
+ d = new QQuickPixmapData(QQuickTextureFactory::textureFactoryForImage(p));
+ }
}
void QQuickPixmap::setPixmap(const QQuickPixmap &other)
{
+ if (d == other.d)
+ return;
clear();
if (other.d) {
+ if (d)
+ d->release();
d = other.d;
d->addref();
- d->declarativePixmaps.insert(this);
}
}
@@ -1680,14 +1846,14 @@ void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &reques
qreal devicePixelRatio)
{
if (d) {
- d->declarativePixmaps.remove(this);
d->release();
d = nullptr;
}
QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, providerOptions };
- QQuickPixmapStore *store = pixmapStore();
+ QQuickPixmapCache *store = QQuickPixmapCache::instance();
+ QMutexLocker locker(&QQuickPixmapCache::instance()->m_cacheMutex);
QHash<QQuickPixmapKey, QQuickPixmapData *>::Iterator iter = store->m_cache.end();
#ifdef Q_OS_WEBOS
@@ -1711,6 +1877,7 @@ void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &reques
iter = store->m_cache.find(key);
if (iter == store->m_cache.end()) {
+ locker.unlock();
if (url.scheme() == QLatin1String("image")) {
QQmlEnginePrivate *enginePrivate = QQmlEnginePrivate::get(engine);
if (auto provider = enginePrivate->imageProvider(imageProviderId(url)).staticCast<QQuickImageProvider>()) {
@@ -1727,7 +1894,7 @@ void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &reques
if (!(options & QQuickPixmap::Asynchronous)) {
bool ok = false;
PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingStarted>(url));
- d = createPixmapDataSync(this, engine, url, requestRegion, requestSize, providerOptions, frame, &ok, devicePixelRatio);
+ d = createPixmapDataSync(engine, url, requestRegion, requestSize, providerOptions, frame, &ok, devicePixelRatio);
if (ok) {
PIXMAP_PROFILE(pixmapLoadingFinished(url, QSize(width(), height())));
if (options & QQuickPixmap::Cache)
@@ -1747,7 +1914,7 @@ void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &reques
return;
- d = new QQuickPixmapData(this, url, requestRegion, requestSize, providerOptions,
+ d = new QQuickPixmapData(url, requestRegion, requestSize, providerOptions,
QQuickImageProviderOptions::UsePluginDefaultTransform, frame, frameCount);
if (options & QQuickPixmap::Cache)
d->addToCache();
@@ -1756,12 +1923,14 @@ void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &reques
#endif
QQuickPixmapReader::readerMutex.lock();
- d->reply = QQuickPixmapReader::instance(engine)->getImage(d);
+ QQuickPixmapReader *reader = QQuickPixmapReader::instance(engine);
+ d->reply = reader->getImage(d);
+ reader->startJob(d->reply);
QQuickPixmapReader::readerMutex.unlock();
} else {
d = *iter;
d->addref();
- d->declarativePixmaps.insert(this);
+ qCDebug(lcImg) << "loaded from cache" << url << "frame" << frame;
}
}
@@ -1778,37 +1947,43 @@ void QQuickPixmap::loadImageFromDevice(QQmlEngine *engine, QIODevice *device, co
{
auto oldD = d;
QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, providerOptions };
- QQuickPixmapStore *store = pixmapStore();
+ QQuickPixmapCache *store = QQuickPixmapCache::instance();
QHash<QQuickPixmapKey, QQuickPixmapData *>::Iterator iter = store->m_cache.end();
+ QMutexLocker locker(&store->m_cacheMutex);
iter = store->m_cache.find(key);
if (iter == store->m_cache.end()) {
if (!engine)
return;
- d = new QQuickPixmapData(this, url, requestRegion, requestSize, providerOptions,
+ locker.unlock();
+ d = new QQuickPixmapData(url, requestRegion, requestSize, providerOptions,
QQuickImageProviderOptions::UsePluginDefaultTransform, frame, frameCount);
d->specialDevice = device;
d->addToCache();
QQuickPixmapReader::readerMutex.lock();
- d->reply = QQuickPixmapReader::instance(engine)->getImage(d);
+ QQuickPixmapReader *reader = QQuickPixmapReader::instance(engine);
+ d->reply = reader->getImage(d);
if (oldD) {
- QObject::connect(d->reply, &QQuickPixmapReply::finished, [oldD]() {
+ QObject::connect(d->reply, &QQuickPixmapReply::destroyed, store, [oldD]() {
oldD->release();
- });
+ }, Qt::QueuedConnection);
}
+ reader->startJob(d->reply);
QQuickPixmapReader::readerMutex.unlock();
} else {
d = *iter;
d->addref();
- d->declarativePixmaps.insert(this);
+ qCDebug(lcImg) << "loaded from cache" << url << "frame" << frame << "refCount" << d->refCount;
+ locker.unlock();
+ if (oldD)
+ oldD->release();
}
}
void QQuickPixmap::clear()
{
if (d) {
- d->declarativePixmaps.remove(this);
d->release();
d = nullptr;
}
@@ -1819,7 +1994,6 @@ void QQuickPixmap::clear(QObject *obj)
if (d) {
if (d->reply)
QObject::disconnect(d->reply, nullptr, obj, nullptr);
- d->declarativePixmaps.remove(this);
d->release();
d = nullptr;
}
@@ -1829,11 +2003,22 @@ bool QQuickPixmap::isCached(const QUrl &url, const QRect &requestRegion, const Q
const int frame, const QQuickImageProviderOptions &options)
{
QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, options };
- QQuickPixmapStore *store = pixmapStore();
+ QQuickPixmapCache *store = QQuickPixmapCache::instance();
return store->m_cache.contains(key);
}
+bool QQuickPixmap::isScalableImageFormat(const QUrl &url)
+{
+ if (url.scheme() == "image"_L1)
+ return true;
+
+ const QString stringUrl = url.path(QUrl::PrettyDecoded);
+ return stringUrl.endsWith("svg"_L1)
+ || stringUrl.endsWith("svgz"_L1)
+ || stringUrl.endsWith("pdf"_L1);
+}
+
bool QQuickPixmap::connectFinished(QObject *object, const char *method)
{
if (!d || !d->reply) {
@@ -1851,7 +2036,7 @@ bool QQuickPixmap::connectFinished(QObject *object, int method)
return false;
}
- return QMetaObject::connect(d->reply, QQuickPixmapReply::finishedIndex, object, method);
+ return QMetaObject::connect(d->reply, QQuickPixmapReply::finishedMethodIndex, object, method);
}
bool QQuickPixmap::connectDownloadProgress(QObject *object, const char *method)
@@ -1861,7 +2046,8 @@ bool QQuickPixmap::connectDownloadProgress(QObject *object, const char *method)
return false;
}
- return QObject::connect(d->reply, SIGNAL(downloadProgress(qint64,qint64)), object, method);
+ return QObject::connect(d->reply, SIGNAL(downloadProgress(qint64,qint64)), object,
+ method);
}
bool QQuickPixmap::connectDownloadProgress(QObject *object, int method)
@@ -1871,7 +2057,8 @@ bool QQuickPixmap::connectDownloadProgress(QObject *object, int method)
return false;
}
- return QMetaObject::connect(d->reply, QQuickPixmapReply::downloadProgressIndex, object, method);
+ return QMetaObject::connect(d->reply, QQuickPixmapReply::downloadProgressMethodIndex, object,
+ method);
}
QColorSpace QQuickPixmap::colorSpace() const
diff --git a/src/quick/util/qquickpixmapcache_p.h b/src/quick/util/qquickpixmapcache_p.h
index ed6422a2bf..8c0c085b50 100644
--- a/src/quick/util/qquickpixmapcache_p.h
+++ b/src/quick/util/qquickpixmapcache_p.h
@@ -15,176 +15,72 @@
// We mean it.
//
-#include <QtCore/qcoreapplication.h>
-#include <QtCore/qstring.h>
-#include <QtGui/qpixmap.h>
+#include <QtCore/qmutex.h>
#include <QtCore/qurl.h>
-#include <private/qtquickglobal_p.h>
#include <QtQuick/qquickimageprovider.h>
-
-#include <private/qintrusivelist_p.h>
+#include <private/qquickpixmap_p.h>
QT_BEGIN_NAMESPACE
-class QQmlEngine;
class QQuickPixmapData;
-class QQuickTextureFactory;
-class QQuickImageProviderOptionsPrivate;
-class QQuickDefaultTextureFactory : public QQuickTextureFactory
+/*! \internal
+ A composite key to identify a QQuickPixmapData instance in a QHash.
+*/
+struct Q_AUTOTEST_EXPORT QQuickPixmapKey
{
- Q_OBJECT
-public:
- QQuickDefaultTextureFactory(const QImage &i);
- QSGTexture *createTexture(QQuickWindow *window) const override;
- QSize textureSize() const override { return size; }
- int textureByteCount() const override { return size.width() * size.height() * 4; }
- QImage image() const override { return im; }
-
-private:
- QImage im;
- QSize size;
+ const QUrl *url;
+ const QRect *region;
+ const QSize *size;
+ int frame;
+ QQuickImageProviderOptions options;
};
-class QQuickImageProviderPrivate
+/*! \internal
+ A singleton managing the storage and garbage collection of QQuickPixmapData
+ instances. It's exported only for the sake of autotests, and should not be
+ manipulated outside qquickpixmap.cpp and qquickpixmapcache.cpp.
+*/
+class Q_AUTOTEST_EXPORT QQuickPixmapCache : public QObject
{
-public:
- QQuickImageProvider::ImageType type;
- QQuickImageProvider::Flags flags;
- bool isProviderWithOptions;
-};
+ Q_OBJECT
-// ### Qt 6: Make public moving to qquickimageprovider.h
-class Q_QUICK_PRIVATE_EXPORT QQuickImageProviderOptions
-{
public:
- enum AutoTransform {
- UsePluginDefaultTransform = -1,
- ApplyTransform = 0,
- DoNotApplyTransform = 1
- };
-
- QQuickImageProviderOptions();
- ~QQuickImageProviderOptions();
-
- QQuickImageProviderOptions(const QQuickImageProviderOptions&);
- QQuickImageProviderOptions& operator=(const QQuickImageProviderOptions&);
-
- bool operator==(const QQuickImageProviderOptions&) const;
+ static QQuickPixmapCache *instance();
+ ~QQuickPixmapCache() override;
- AutoTransform autoTransform() const;
- void setAutoTransform(AutoTransform autoTransform);
+ void unreferencePixmap(QQuickPixmapData *);
+ void referencePixmap(QQuickPixmapData *);
- bool preserveAspectRatioCrop() const;
- void setPreserveAspectRatioCrop(bool preserveAspectRatioCrop);
+ void purgeCache();
- bool preserveAspectRatioFit() const;
- void setPreserveAspectRatioFit(bool preserveAspectRatioFit);
-
- QColorSpace targetColorSpace() const;
- void setTargetColorSpace(const QColorSpace &colorSpace);
+protected:
+ void timerEvent(QTimerEvent *) override;
private:
- QSharedDataPointer<QQuickImageProviderOptionsPrivate> d;
-};
+ QQuickPixmapCache() = default;
+ Q_DISABLE_COPY(QQuickPixmapCache)
-class Q_QUICK_PRIVATE_EXPORT QQuickPixmap
-{
- Q_DECLARE_TR_FUNCTIONS(QQuickPixmap)
-public:
- QQuickPixmap();
- QQuickPixmap(QQmlEngine *, const QUrl &);
- QQuickPixmap(QQmlEngine *, const QUrl &, const QRect &region, const QSize &);
- QQuickPixmap(const QUrl &, const QImage &image);
- ~QQuickPixmap();
-
- enum Status { Null, Ready, Error, Loading };
-
- enum Option {
- Asynchronous = 0x00000001,
- Cache = 0x00000002
- };
- Q_DECLARE_FLAGS(Options, Option)
-
- bool isNull() const;
- bool isReady() const;
- bool isError() const;
- bool isLoading() const;
-
- Status status() const;
- QString error() const;
- const QUrl &url() const;
- const QSize &implicitSize() const;
- const QRect &requestRegion() const;
- const QSize &requestSize() const;
- QQuickImageProviderOptions::AutoTransform autoTransform() const;
- int frameCount() const;
- QImage image() const;
- void setImage(const QImage &);
- void setPixmap(const QQuickPixmap &other);
-
- QColorSpace colorSpace() const;
-
- QQuickTextureFactory *textureFactory() const;
-
- QRect rect() const;
- int width() const;
- int height() const;
-
- void load(QQmlEngine *, const QUrl &);
- void load(QQmlEngine *, const QUrl &, QQuickPixmap::Options options);
- void load(QQmlEngine *, const QUrl &, const QRect &requestRegion, const QSize &requestSize);
- void load(QQmlEngine *, const QUrl &, const QRect &requestRegion, const QSize &requestSize, QQuickPixmap::Options options);
- void load(QQmlEngine *, const QUrl &, const QRect &requestRegion, const QSize &requestSize,
- QQuickPixmap::Options options, const QQuickImageProviderOptions &providerOptions, int frame = 0, int frameCount = 1,
- qreal devicePixelRatio = 1.0);
- void loadImageFromDevice(QQmlEngine *engine, QIODevice *device, const QUrl &url,
- const QRect &requestRegion, const QSize &requestSize,
- const QQuickImageProviderOptions &providerOptions, int frame = 0, int frameCount = 1);
-
- void clear();
- void clear(QObject *);
-
- bool connectFinished(QObject *, const char *);
- bool connectFinished(QObject *, int);
- bool connectDownloadProgress(QObject *, const char *);
- bool connectDownloadProgress(QObject *, int);
-
- static void purgeCache();
- static bool isCached(const QUrl &url, const QRect &requestRegion, const QSize &requestSize,
- const int frame, const QQuickImageProviderOptions &options);
-
- static const QLatin1String itemGrabberScheme;
+ void shrinkCache(int remove);
+ int destroyCache();
+ qsizetype referencedCost() const;
private:
- Q_DISABLE_COPY(QQuickPixmap)
- QQuickPixmapData *d;
- QIntrusiveListNode dataListNode;
- friend class QQuickPixmapData;
-};
+ QHash<QQuickPixmapKey, QQuickPixmapData *> m_cache;
+ mutable QMutex m_cacheMutex; // avoid simultaneous iteration and modification
-Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickPixmap::Options)
+ QQuickPixmapData *m_unreferencedPixmaps = nullptr;
+ QQuickPixmapData *m_lastUnreferencedPixmap = nullptr;
-// ### Qt 6: This should be made public in Qt 6. It's functionality can't be merged into
-// QQuickImageProvider without breaking source compatibility.
-class Q_QUICK_PRIVATE_EXPORT QQuickImageProviderWithOptions : public QQuickAsyncImageProvider
-{
-public:
- QQuickImageProviderWithOptions(ImageType type, Flags flags = Flags());
-
- QImage requestImage(const QString &id, QSize *size, const QSize& requestedSize) override;
- QPixmap requestPixmap(const QString &id, QSize *size, const QSize& requestedSize) override;
- QQuickTextureFactory *requestTexture(const QString &id, QSize *size, const QSize &requestedSize) override;
- QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override;
+ int m_unreferencedCost = 0;
+ int m_timerId = -1;
+ bool m_destroying = false;
- virtual QImage requestImage(const QString &id, QSize *size, const QSize& requestedSize, const QQuickImageProviderOptions &options);
- virtual QPixmap requestPixmap(const QString &id, QSize *size, const QSize& requestedSize, const QQuickImageProviderOptions &options);
- virtual QQuickTextureFactory *requestTexture(const QString &id, QSize *size, const QSize &requestedSize, const QQuickImageProviderOptions &options);
- virtual QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize, const QQuickImageProviderOptions &options);
-
- static QSize loadSize(const QSize &originalSize, const QSize &requestedSize, const QByteArray &format, const QQuickImageProviderOptions &options,
- qreal devicePixelRatio = 1.0);
- static QQuickImageProviderWithOptions *checkedCast(QQuickImageProvider *provider);
+ friend class QQuickPixmap;
+ friend class QQuickPixmapData;
+ friend class tst_qquickpixmapcache;
+ friend class tst_qquicktext;
+ friend class tst_qquicktextedit;
};
QT_END_NAMESPACE
diff --git a/src/quick/util/qquickprofiler_p.h b/src/quick/util/qquickprofiler_p.h
index 276172a2da..377c9831d7 100644
--- a/src/quick/util/qquickprofiler_p.h
+++ b/src/quick/util/qquickprofiler_p.h
@@ -22,6 +22,7 @@
#include <QtQml/private/qqmlprofilerdefinitions_p.h>
#endif
+#include <QtCore/private/qnumeric_p.h>
#include <QtCore/qurl.h>
#include <QtCore/qsize.h>
#include <QtCore/qmutex.h>
@@ -128,14 +129,14 @@ public:
template<SceneGraphFrameType type>
qint64 *timings()
{
- if (type < NumRenderThreadFrameTypes)
+ if constexpr (type < NumRenderThreadFrameTypes)
return renderThreadTimings.localData().values[type];
else
return guiThreadTimings.values[type - NumRenderThreadFrameTypes];
}
};
-class Q_QUICK_PRIVATE_EXPORT QQuickProfiler : public QObject, public QQmlProfilerDefinitions {
+class Q_QUICK_EXPORT QQuickProfiler : public QObject, public QQmlProfilerDefinitions {
Q_OBJECT
public:
@@ -206,11 +207,12 @@ public:
static void animationFrame(qint64 delta, AnimationThread threadId)
{
- int animCount = QUnifiedTimer::instance()->runningAnimationCount();
+ const qsizetype animCount = QUnifiedTimer::instance()->runningAnimationCount();
if (animCount > 0 && delta > 0) {
s_instance->processMessage(QQuickProfilerData(s_instance->timestamp(), 1 << Event,
- 1 << AnimationFrame, 1000 / (int)delta /* trim fps to integer */, animCount,
+ 1 << AnimationFrame, 1000 / (int)delta /* trim fps to integer */,
+ qt_saturate<int>(animCount),
threadId));
}
}
@@ -316,7 +318,17 @@ protected:
void processMessage(const QQuickProfilerData &message)
{
QMutexLocker lock(&m_dataMutex);
- m_data.append(message);
+ if (Q_LIKELY(m_data.isEmpty() || m_data.last().time <= message.time)) {
+ m_data.append(message);
+ return;
+ }
+
+ // Since the scenegraph data is recorded from different threads, contention for the lock
+ // can cause it to be processed out of order here. Insert the message at the right place.
+ const auto it = std::find_if(
+ m_data.rbegin(), m_data.rend(),
+ [t = message.time](const QQuickProfilerData &i) { return i.time <= t; });
+ m_data.insert(it.base(), message);
}
void startProfilingImpl(quint64 features);
diff --git a/src/quick/util/qquickpropertychanges.cpp b/src/quick/util/qquickpropertychanges.cpp
index fcb6e71e17..ab08a725bb 100644
--- a/src/quick/util/qquickpropertychanges.cpp
+++ b/src/quick/util/qquickpropertychanges.cpp
@@ -20,8 +20,10 @@
#include <private/qqmlirbuilder_p.h>
#include <QtCore/qdebug.h>
+#include <QtQml/private/qqmlsignalnames_p.h>
#include <private/qobject_p.h>
+#include <QtCore/qpointer.h>
QT_BEGIN_NAMESPACE
@@ -101,7 +103,7 @@ QT_BEGIN_NAMESPACE
edited with \l{Qt Design Studio}. Mind that \l{Qt Design Studio} also imposes a number
of further restrictions on the files it can work with.
- \sa {Qt Quick Examples - Animation#States}{States example}, {Qt Quick States}, {Qt QML}
+ \sa {Qt Quick Examples - Animation#States}{States example}, {Qt Quick States}, {Qt Qml}
*/
/*!
@@ -267,7 +269,7 @@ void QQuickPropertyChangesPrivate::decodeBinding(const QString &propertyPrefix,
break;
}
- if (binding->isSignalHandler() || QmlIR::IRBuilder::isSignalPropertyName(propertyName)) {
+ if (binding->isSignalHandler() || QQmlSignalNames::isHandlerName(propertyName)) {
QQmlProperty prop = property(propertyName);
if (prop.isSignalProperty()) {
QQuickReplaceSignalHandler *handler = new QQuickReplaceSignalHandler;
diff --git a/src/quick/util/qquickpropertychanges_p.h b/src/quick/util/qquickpropertychanges_p.h
index 44a66bc6e8..b7413c1142 100644
--- a/src/quick/util/qquickpropertychanges_p.h
+++ b/src/quick/util/qquickpropertychanges_p.h
@@ -21,7 +21,7 @@
QT_BEGIN_NAMESPACE
class QQuickPropertyChangesPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickPropertyChanges : public QQuickStateOperation
+class Q_QUICK_EXPORT QQuickPropertyChanges : public QQuickStateOperation
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickPropertyChanges)
@@ -89,6 +89,4 @@ inline QQmlCustomParser *qmlCreateCustomParser<QQuickPropertyChanges>()
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickPropertyChanges)
-
#endif // QQUICKPROPERTYCHANGES_H
diff --git a/src/quick/util/qquickshortcut.cpp b/src/quick/util/qquickshortcut.cpp
index 51de063090..a6a0972d87 100644
--- a/src/quick/util/qquickshortcut.cpp
+++ b/src/quick/util/qquickshortcut.cpp
@@ -18,8 +18,9 @@
\ingroup qtquick-input
\brief Provides keyboard shortcuts.
- The Shortcut type provides a way of handling keyboard shortcuts. The shortcut can
- be set to one of the \l{QKeySequence::StandardKey}{standard keyboard shortcuts},
+ The Shortcut type lets you handle keyboard shortcuts. The shortcut can
+ be set to one of the
+ \l{QKeySequence::StandardKey}{standard keyboard shortcuts},
or it can be described with a string containing a sequence of up to four key
presses that are needed to \l{Shortcut::activated}{activate} the shortcut.
@@ -30,7 +31,7 @@
property int currentIndex
Shortcut {
- sequence: StandardKey.NextChild
+ sequences: [StandardKey.NextChild]
onActivated: view.currentIndex++
}
}
@@ -76,12 +77,12 @@ typedef bool (*ContextMatcher)(QObject *, Qt::ShortcutContext);
Q_GLOBAL_STATIC_WITH_ARGS(ContextMatcher, ctxMatcher, (qQuickShortcutContextMatcher))
-Q_QUICK_PRIVATE_EXPORT ContextMatcher qt_quick_shortcut_context_matcher()
+Q_QUICK_EXPORT ContextMatcher qt_quick_shortcut_context_matcher()
{
return *ctxMatcher();
}
-Q_QUICK_PRIVATE_EXPORT void qt_quick_set_shortcut_context_matcher(ContextMatcher matcher)
+Q_QUICK_EXPORT void qt_quick_set_shortcut_context_matcher(ContextMatcher matcher)
{
if (!ctxMatcher.isDestroyed())
*ctxMatcher() = matcher;
@@ -147,6 +148,10 @@ QQuickShortcut::~QQuickShortcut()
}
\endqml
+ \note Given that standard keys can resolve to one shortcut on some
+ platforms, but multiple shortcuts on other platforms, we recommend always
+ using \l{Shortcut::}{sequences} for standard keys.
+
\sa sequences
*/
QVariant QQuickShortcut::sequence() const
@@ -319,10 +324,11 @@ void QQuickShortcut::setAutoRepeat(bool repeat)
This property holds the \l{Qt::ShortcutContext}{shortcut context}.
Supported values are:
- \list
- \li \c Qt.WindowShortcut (default) - The shortcut is active when its parent item is in an active top-level window.
- \li \c Qt.ApplicationShortcut - The shortcut is active when one of the application's windows are active.
- \endlist
+
+ \value Qt.WindowShortcut
+ (default) The shortcut is active when its parent item is in an active top-level window.
+ \value Qt.ApplicationShortcut
+ The shortcut is active when one of the application's windows are active.
\qml
Shortcut {
diff --git a/src/quick/util/qquickshortcut_p.h b/src/quick/util/qquickshortcut_p.h
index baa884b6f0..a0e0c996ed 100644
--- a/src/quick/util/qquickshortcut_p.h
+++ b/src/quick/util/qquickshortcut_p.h
@@ -28,7 +28,7 @@ QT_BEGIN_NAMESPACE
class QShortcutEvent;
-class Q_QUICK_PRIVATE_EXPORT QQuickShortcut : public QObject, public QQmlParserStatus
+class Q_QUICK_EXPORT QQuickShortcut : public QObject, public QQmlParserStatus
{
Q_OBJECT
Q_INTERFACES(QQmlParserStatus)
diff --git a/src/quick/util/qquicksmoothedanimation.cpp b/src/quick/util/qquicksmoothedanimation.cpp
index 4681d5919e..b19e6f03d9 100644
--- a/src/quick/util/qquicksmoothedanimation.cpp
+++ b/src/quick/util/qquicksmoothedanimation.cpp
@@ -52,14 +52,12 @@ QSmoothedAnimation::~QSmoothedAnimation()
delete delayedStopTimer;
if (animationTemplate) {
if (target.object()) {
- QHash<QQmlProperty, QSmoothedAnimation* >::iterator it =
- animationTemplate->activeAnimations.find(target);
- if (it != animationTemplate->activeAnimations.end() && it.value() == this)
+ auto it = animationTemplate->activeAnimations.constFind(target);
+ if (it != animationTemplate->activeAnimations.cend() && it.value() == this)
animationTemplate->activeAnimations.erase(it);
} else {
//target is no longer valid, need to search linearly
- QHash<QQmlProperty, QSmoothedAnimation* >::iterator it;
- for (it = animationTemplate->activeAnimations.begin(); it != animationTemplate->activeAnimations.end(); ++it) {
+ for (auto it = animationTemplate->activeAnimations.cbegin(); it != animationTemplate->activeAnimations.cend(); ++it) {
if (it.value() == this) {
animationTemplate->activeAnimations.erase(it);
break;
@@ -421,11 +419,12 @@ QAbstractAnimationJob* QQuickSmoothedAnimation::transition(QQuickStateActions &a
Possible values are:
- \list
- \li SmoothedAnimation.Eased (default) - the animation will smoothly decelerate, and then reverse direction
- \li SmoothedAnimation.Immediate - the animation will immediately begin accelerating in the reverse direction, beginning with a velocity of 0
- \li SmoothedAnimation.Sync - the property is immediately set to the target value
- \endlist
+ \value SmoothedAnimation.Eased
+ (default) the animation will smoothly decelerate, and then reverse direction
+ \value SmoothedAnimation.Immediate
+ the animation will immediately begin accelerating in the reverse direction, beginning with a velocity of 0
+ \value SmoothedAnimation.Sync
+ the property is immediately set to the target value
*/
QQuickSmoothedAnimation::ReversingMode QQuickSmoothedAnimation::reversingMode() const
{
diff --git a/src/quick/util/qquicksmoothedanimation_p.h b/src/quick/util/qquicksmoothedanimation_p.h
index bf8bf46aed..9a996b0ed2 100644
--- a/src/quick/util/qquicksmoothedanimation_p.h
+++ b/src/quick/util/qquicksmoothedanimation_p.h
@@ -24,7 +24,7 @@ QT_BEGIN_NAMESPACE
class QQmlProperty;
class QQuickSmoothedAnimationPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickSmoothedAnimation : public QQuickNumberAnimation
+class Q_QUICK_EXPORT QQuickSmoothedAnimation : public QQuickNumberAnimation
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickSmoothedAnimation)
@@ -65,6 +65,4 @@ Q_SIGNALS:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickSmoothedAnimation)
-
#endif // QQUICKSMOOTHEDANIMATION_H
diff --git a/src/quick/util/qquicksmoothedanimation_p_p.h b/src/quick/util/qquicksmoothedanimation_p_p.h
index 2e0fe5be15..e57b472b01 100644
--- a/src/quick/util/qquicksmoothedanimation_p_p.h
+++ b/src/quick/util/qquicksmoothedanimation_p_p.h
@@ -21,7 +21,7 @@
#include "qquickanimation_p_p.h"
#include <private/qobject_p.h>
-#include <QBasicTimer>
+#include <QTimer>
QT_BEGIN_NAMESPACE
class QSmoothedAnimation;
diff --git a/src/quick/util/qquickspringanimation.cpp b/src/quick/util/qquickspringanimation.cpp
index 678f1c4238..4b615d69b7 100644
--- a/src/quick/util/qquickspringanimation.cpp
+++ b/src/quick/util/qquickspringanimation.cpp
@@ -132,12 +132,12 @@ QSpringAnimation::~QSpringAnimation()
{
if (animationTemplate) {
if (target.object()) {
- ActiveAnimationHashIt it = animationTemplate->activeAnimations.find(target);
- if (it != animationTemplate->activeAnimations.end() && it.value() == this)
+ auto it = animationTemplate->activeAnimations.constFind(target);
+ if (it != animationTemplate->activeAnimations.cend() && it.value() == this)
animationTemplate->activeAnimations.erase(it);
} else {
//target is no longer valid, need to search linearly
- for (ActiveAnimationHashIt it = animationTemplate->activeAnimations.begin(); it != animationTemplate->activeAnimations.end(); ++it) {
+ for (auto it = animationTemplate->activeAnimations.cbegin(); it != animationTemplate->activeAnimations.cend(); ++it) {
if (it.value() == this) {
animationTemplate->activeAnimations.erase(it);
break;
diff --git a/src/quick/util/qquickspringanimation_p.h b/src/quick/util/qquickspringanimation_p.h
index 552f67f640..ba7f83fa44 100644
--- a/src/quick/util/qquickspringanimation_p.h
+++ b/src/quick/util/qquickspringanimation_p.h
@@ -23,7 +23,7 @@
QT_BEGIN_NAMESPACE
class QQuickSpringAnimationPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickSpringAnimation : public QQuickNumberAnimation
+class Q_QUICK_EXPORT QQuickSpringAnimation : public QQuickNumberAnimation
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(QQuickSpringAnimation)
@@ -74,6 +74,4 @@ Q_SIGNALS:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickSpringAnimation)
-
#endif // QQUICKSPRINGANIMATION_H
diff --git a/src/quick/util/qquickstate.cpp b/src/quick/util/qquickstate.cpp
index 51bc9852f4..1f066270c6 100644
--- a/src/quick/util/qquickstate.cpp
+++ b/src/quick/util/qquickstate.cpp
@@ -112,7 +112,7 @@ QQuickStateOperation::QQuickStateOperation(QObjectPrivate &dd, QObject *parent)
not allowed.
\sa {Qt Quick Examples - Animation#States}{States example}, {Qt Quick States},
- {Animation and Transitions in Qt Quick}{Transitions}, {Qt QML}
+ {Animation and Transitions in Qt Quick}{Transitions}, {Qt Qml}
*/
QQuickState::QQuickState(QObject *parent)
: QObject(*(new QQuickStatePrivate), parent)
@@ -419,27 +419,25 @@ void QQuickState::addEntryToRevertList(const QQuickStateAction &action)
void QQuickState::removeAllEntriesFromRevertList(QObject *target)
{
- Q_D(QQuickState);
-
- if (isStateActive()) {
- const auto actionMatchesTarget = [target](QQuickSimpleAction &simpleAction) {
- if (simpleAction.property().object() == target) {
- QQmlPropertyPrivate::removeBinding(simpleAction.property());
- simpleAction.property().write(simpleAction.value());
- if (auto binding = simpleAction.binding()) {
- QQmlProperty prop = simpleAction.property();
- binding.installOn(prop);
- }
-
- return true;
- }
- return false;
- };
-
- d->revertList.erase(std::remove_if(d->revertList.begin(), d->revertList.end(),
- actionMatchesTarget),
- d->revertList.end());
- }
+ Q_D(QQuickState);
+
+ if (isStateActive()) {
+ const auto actionMatchesTarget = [target](const QQuickSimpleAction &simpleAction) {
+ if (simpleAction.property().object() == target) {
+ QQmlPropertyPrivate::removeBinding(simpleAction.property());
+ simpleAction.property().write(simpleAction.value());
+ if (auto binding = simpleAction.binding()) {
+ QQmlProperty prop = simpleAction.property();
+ binding.installOn(prop);
+ }
+
+ return true;
+ }
+ return false;
+ };
+
+ d->revertList.removeIf(actionMatchesTarget);
+ }
}
void QQuickState::addEntriesToRevertList(const QList<QQuickStateAction> &actionList)
diff --git a/src/quick/util/qquickstate_p.h b/src/quick/util/qquickstate_p.h
index 41b0a10835..66d0303162 100644
--- a/src/quick/util/qquickstate_p.h
+++ b/src/quick/util/qquickstate_p.h
@@ -27,11 +27,13 @@
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcStates)
+
class QQuickStateActionEvent;
class QQmlBinding;
class QQmlExpression;
-class Q_QUICK_PRIVATE_EXPORT QQuickStateAction
+class Q_QUICK_EXPORT QQuickStateAction
{
public:
QQuickStateAction();
@@ -89,7 +91,7 @@ public:
class QQuickStateGroup;
class QQuickState;
class QQuickStateOperationPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickStateOperation : public QObject
+class Q_QUICK_EXPORT QQuickStateOperation : public QObject
{
Q_OBJECT
QML_ANONYMOUS
@@ -116,7 +118,7 @@ typedef QQuickStateOperation::ActionList QQuickStateActions;
class QQuickTransition;
class QQuickStatePrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickState : public QObject
+class Q_QUICK_EXPORT QQuickState : public QObject
{
Q_OBJECT
@@ -178,7 +180,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickStateOperation)
-QML_DECLARE_TYPE(QQuickState)
-
#endif // QQUICKSTATE_H
diff --git a/src/quick/util/qquickstatechangescript_p.h b/src/quick/util/qquickstatechangescript_p.h
index 3489a33095..66772c7e00 100644
--- a/src/quick/util/qquickstatechangescript_p.h
+++ b/src/quick/util/qquickstatechangescript_p.h
@@ -21,7 +21,7 @@
QT_BEGIN_NAMESPACE
class QQuickStateChangeScriptPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickStateChangeScript : public QQuickStateOperation, public QQuickStateActionEvent
+class Q_QUICK_EXPORT QQuickStateChangeScript : public QQuickStateOperation, public QQuickStateActionEvent
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickStateChangeScript)
@@ -50,6 +50,4 @@ public:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickStateChangeScript)
-
#endif // QQUICKSTATEOPERATIONS_H
diff --git a/src/quick/util/qquickstategroup.cpp b/src/quick/util/qquickstategroup.cpp
index b4be946dc3..8fb3130e35 100644
--- a/src/quick/util/qquickstategroup.cpp
+++ b/src/quick/util/qquickstategroup.cpp
@@ -19,8 +19,6 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-Q_DECLARE_LOGGING_CATEGORY(lcStates)
-
class QQuickStateGroupPrivate : public QObjectPrivate
{
Q_DECLARE_PUBLIC(QQuickStateGroup)
@@ -85,7 +83,7 @@ public:
}
\endqml
- \sa {Qt Quick States}{Qt Quick States}, {Animation and Transitions in Qt Quick}{Transitions}, {Qt QML}
+ \sa {Qt Quick States}{Qt Quick States}, {Animation and Transitions in Qt Quick}{Transitions}, {Qt Qml}
*/
QQuickStateGroup::QQuickStateGroup(QObject *parent)
@@ -96,8 +94,10 @@ QQuickStateGroup::QQuickStateGroup(QObject *parent)
QQuickStateGroup::~QQuickStateGroup()
{
Q_D(const QQuickStateGroup);
- for (int i = 0; i < d->states.size(); ++i)
- d->states.at(i)->setStateGroup(nullptr);
+ for (QQuickState *state : std::as_const(d->states)) {
+ if (state)
+ state->setStateGroup(nullptr);
+ }
if (d->nullState)
d->nullState->setStateGroup(nullptr);
}
@@ -143,11 +143,9 @@ QQmlListProperty<QQuickState> QQuickStateGroup::statesProperty()
void QQuickStateGroupPrivate::append_state(QQmlListProperty<QQuickState> *list, QQuickState *state)
{
QQuickStateGroup *_this = static_cast<QQuickStateGroup *>(list->object);
- if (state) {
- _this->d_func()->states.append(state);
+ _this->d_func()->states.append(state);
+ if (state)
state->setStateGroup(_this);
- }
-
}
qsizetype QQuickStateGroupPrivate::count_state(QQmlListProperty<QQuickState> *list)
@@ -164,34 +162,42 @@ QQuickState *QQuickStateGroupPrivate::at_state(QQmlListProperty<QQuickState> *li
void QQuickStateGroupPrivate::clear_states(QQmlListProperty<QQuickState> *list)
{
- QQuickStateGroup *_this = static_cast<QQuickStateGroup *>(list->object);
- _this->d_func()->setCurrentStateInternal(QString(), true);
- for (qsizetype i = 0; i < _this->d_func()->states.size(); ++i) {
- _this->d_func()->states.at(i)->setStateGroup(nullptr);
+ QQuickStateGroupPrivate *d = static_cast<QQuickStateGroup *>(list->object)->d_func();
+ d->setCurrentStateInternal(QString(), true);
+ for (QQuickState *state : std::as_const(d->states)) {
+ if (state)
+ state->setStateGroup(nullptr);
}
- _this->d_func()->states.clear();
+ d->states.clear();
}
void QQuickStateGroupPrivate::replace_states(QQmlListProperty<QQuickState> *list, qsizetype index, QQuickState *state)
{
- auto *self = qobject_cast<QQuickStateGroup *>(list->object);
+ auto *self = static_cast<QQuickStateGroup *>(list->object);
auto *d = self->d_func();
auto *oldState = d->states.at(index);
if (oldState != state) {
- oldState->setStateGroup(nullptr);
- state->setStateGroup(self);
+ if (oldState)
+ oldState->setStateGroup(nullptr);
+
+ if (state)
+ state->setStateGroup(self);
d->states.replace(index, state);
- if (d->currentState == oldState->name())
- d->setCurrentStateInternal(state->name(), true);
+ if (!oldState || d->currentState == oldState->name())
+ d->setCurrentStateInternal(state ? state->name() : QString(), true);
}
}
void QQuickStateGroupPrivate::removeLast_states(QQmlListProperty<QQuickState> *list)
{
- auto *d = qobject_cast<QQuickStateGroup *>(list->object)->d_func();
- if (d->currentState == d->states.last()->name())
- d->setCurrentStateInternal(d->states.size() > 1 ? d->states.first()->name() : QString(), true);
- d->states.last()->setStateGroup(nullptr);
+ auto *d = static_cast<QQuickStateGroup *>(list->object)->d_func();
+ if (QQuickState *last = d->states.last()) {
+ if (d->currentState == last->name()) {
+ QQuickState *first = d->states.size() > 1 ? d->states.first() : nullptr;
+ d->setCurrentStateInternal(first ? first->name() : QString(), true);
+ }
+ last->setStateGroup(nullptr);
+ }
d->states.removeLast();
}
@@ -300,17 +306,18 @@ void QQuickStateGroup::componentComplete()
QVarLengthArray<QString, 4> names;
names.reserve(d->states.size());
- for (int ii = 0; ii < d->states.size(); ++ii) {
- QQuickState *state = d->states.at(ii);
+ for (QQuickState *state : std::as_const(d->states)) {
+ if (!state)
+ continue;
+
if (!state->isNamed())
state->setName(QLatin1String("anonymousState") + QString::number(++d->unnamedCount));
QString stateName = state->name();
- if (names.contains(stateName)) {
+ if (names.contains(stateName))
qmlWarning(state->parent()) << "Found duplicate state name: " << stateName;
- } else {
+ else
names.append(std::move(stateName));
- }
}
if (d->updateAutoState()) {
@@ -338,40 +345,38 @@ bool QQuickStateGroupPrivate::updateAutoState()
return false;
bool revert = false;
- for (int ii = 0; ii < states.size(); ++ii) {
- QQuickState *state = states.at(ii);
- if (state->isWhenKnown()) {
- if (state->isNamed()) {
- bool whenValue = state->when();
- const QQmlProperty whenProp(state, u"when"_s);
- const auto potentialWhenBinding = QQmlAnyBinding::ofProperty(whenProp);
- Q_ASSERT(!potentialWhenBinding.isUntypedPropertyBinding());
-
- // if there is a binding, the value in when might not be up-to-date at this point
- // so we manually re-evaluate the binding
- QQmlAbstractBinding *abstractBinding = potentialWhenBinding.asAbstractBinding();
- if (abstractBinding && abstractBinding->kind() == QQmlAbstractBinding::QmlBinding) {
- QQmlBinding *binding = static_cast<QQmlBinding *>(abstractBinding);
- if (binding->hasValidContext()) {
- const auto boolType = QMetaType::fromType<bool>();
- const bool isUndefined = !binding->evaluate(&whenValue, boolType);
- if (isUndefined)
- whenValue = false;
- }
- }
-
- if (whenValue) {
- qCDebug(lcStates) << "Setting auto state due to expression";
- if (currentState != state->name()) {
- q->setState(state->name());
- return true;
- } else {
- return false;
- }
- } else if (state->name() == currentState) {
- revert = true;
- }
+ for (QQuickState *state : std::as_const(states)) {
+ if (!state || !state->isWhenKnown() || !state->isNamed())
+ continue;
+
+ bool whenValue = state->when();
+ const QQmlPropertyIndex whenIndex(state->metaObject()->indexOfProperty("when"));
+ const auto potentialWhenBinding = QQmlAnyBinding::ofProperty(state, whenIndex);
+ Q_ASSERT(!potentialWhenBinding.isUntypedPropertyBinding());
+
+ // if there is a binding, the value in when might not be up-to-date at this point
+ // so we manually re-evaluate the binding
+ QQmlAbstractBinding *abstractBinding = potentialWhenBinding.asAbstractBinding();
+ if (abstractBinding && abstractBinding->kind() == QQmlAbstractBinding::QmlBinding) {
+ QQmlBinding *binding = static_cast<QQmlBinding *>(abstractBinding);
+ if (binding->hasValidContext()) {
+ const auto boolType = QMetaType::fromType<bool>();
+ const bool isUndefined = !binding->evaluate(&whenValue, boolType);
+ if (isUndefined)
+ whenValue = false;
+ }
+ }
+
+ if (whenValue) {
+ qCDebug(lcStates) << "Setting auto state due to expression";
+ if (currentState != state->name()) {
+ q->setState(state->name());
+ return true;
+ } else {
+ return false;
}
+ } else if (state->name() == currentState) {
+ revert = true;
}
}
if (revert) {
@@ -475,9 +480,9 @@ void QQuickStateGroupPrivate::setCurrentStateInternal(const QString &state,
QQuickState *oldState = nullptr;
if (!currentState.isEmpty()) {
- for (int ii = 0; ii < states.size(); ++ii) {
- if (states.at(ii)->name() == currentState) {
- oldState = states.at(ii);
+ for (QQuickState *state : std::as_const(states)) {
+ if (state && state->name() == currentState) {
+ oldState = state;
break;
}
}
@@ -487,9 +492,9 @@ void QQuickStateGroupPrivate::setCurrentStateInternal(const QString &state,
emit q->stateChanged(currentState);
QQuickState *newState = nullptr;
- for (int ii = 0; ii < states.size(); ++ii) {
- if (states.at(ii)->name() == currentState) {
- newState = states.at(ii);
+ for (QQuickState *state : std::as_const(states)) {
+ if (state && state->name() == currentState) {
+ newState = state;
break;
}
}
@@ -511,9 +516,8 @@ void QQuickStateGroupPrivate::setCurrentStateInternal(const QString &state,
QQuickState *QQuickStateGroup::findState(const QString &name) const
{
Q_D(const QQuickStateGroup);
- for (int i = 0; i < d->states.size(); ++i) {
- QQuickState *state = d->states.at(i);
- if (state->name() == name)
+ for (QQuickState *state : std::as_const(d->states)) {
+ if (state && state->name() == name)
return state;
}
diff --git a/src/quick/util/qquickstategroup_p.h b/src/quick/util/qquickstategroup_p.h
index 37f0ec981f..0a962b7fce 100644
--- a/src/quick/util/qquickstategroup_p.h
+++ b/src/quick/util/qquickstategroup_p.h
@@ -20,7 +20,7 @@
QT_BEGIN_NAMESPACE
class QQuickStateGroupPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickStateGroup : public QObject, public QQmlParserStatus
+class Q_QUICK_EXPORT QQuickStateGroup : public QObject, public QQmlParserStatus
{
Q_OBJECT
Q_INTERFACES(QQmlParserStatus)
@@ -61,6 +61,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickStateGroup)
-
#endif // QQUICKSTATEGROUP_H
diff --git a/src/quick/util/qquickstyledtext.cpp b/src/quick/util/qquickstyledtext.cpp
index 1bee09a421..a595dbcbc2 100644
--- a/src/quick/util/qquickstyledtext.cpp
+++ b/src/quick/util/qquickstyledtext.cpp
@@ -530,10 +530,12 @@ void QQuickStyledTextPrivate::parseEntity(const QChar *&ch, const QString &textI
while (!ch->isNull()) {
if (*ch == QLatin1Char(';')) {
auto entity = QStringView(textIn).mid(entityStart, entityLength);
+#if QT_CONFIG(texthtmlparser)
const QString parsedEntity = QTextHtmlParser::parseEntity(entity);
if (!parsedEntity.isNull())
textOut += parsedEntity;
else
+#endif
qCWarning(lcStyledText) << "StyledText doesn't support entity" << entity;
return;
} else if (*ch == QLatin1Char(' ')) {
@@ -655,7 +657,7 @@ void QQuickStyledTextPrivate::parseImageAttributes(const QChar *&ch, const QStri
do {
attr = parseAttribute(ch, textIn);
if (is_equal_ignoring_case(attr.first, QLatin1String("src"))) {
- image->url = QUrl(attr.second.toString());
+ image->url = QUrl(attr.second.toString());
} else if (is_equal_ignoring_case(attr.first, QLatin1String("width"))) {
image->size.setWidth(attr.second.toString().toInt());
} else if (is_equal_ignoring_case(attr.first, QLatin1String("height"))) {
@@ -675,20 +677,24 @@ void QQuickStyledTextPrivate::parseImageAttributes(const QChar *&ch, const QStri
// to avoid a relayout later on.
QUrl url = baseUrl.resolved(image->url);
if (url.isLocalFile()) {
- image->pix = new QQuickPixmap(context->engine(), url, QRect(), image->size);
+ image->pix.reset(new QQuickPixmap(context->engine(), url, QRect(), image->size));
if (image->pix && image->pix->isReady()) {
image->size = image->pix->implicitSize();
} else {
- delete image->pix;
- image->pix = nullptr;
+ image->pix.reset();
}
}
}
- imgWidth = image->size.width();
- image->offset = -std::fmod(imgWidth, spaceWidth) / 2.0;
- imgTags->append(image);
-
+ // Return immediately if img tag has invalid url
+ if (!image->url.isValid()) {
+ delete image;
+ qCWarning(lcStyledText) << "StyledText - Invalid base url in img tag";
+ } else {
+ imgWidth = image->size.width();
+ image->offset = -std::fmod(imgWidth, spaceWidth) / 2.0;
+ imgTags->append(image);
+ }
} else {
// if we already have a list of img tags for this text
// we only want to update the positions of these tags.
diff --git a/src/quick/util/qquickstyledtext_p.h b/src/quick/util/qquickstyledtext_p.h
index b7d51871a3..9f8ccf31e4 100644
--- a/src/quick/util/qquickstyledtext_p.h
+++ b/src/quick/util/qquickstyledtext_p.h
@@ -19,7 +19,8 @@
#include <QPointF>
#include <QList>
#include <QUrl>
-#include <QtQuick/private/qquickpixmapcache_p.h>
+#include <QScopedPointer>
+#include <QtQuick/private/qquickpixmap_p.h>
QT_BEGIN_NAMESPACE
@@ -31,9 +32,8 @@ class QQmlContext;
class Q_AUTOTEST_EXPORT QQuickStyledTextImgTag
{
public:
- QQuickStyledTextImgTag() { }
-
- ~QQuickStyledTextImgTag() { delete pix; }
+ QQuickStyledTextImgTag() = default;
+ ~QQuickStyledTextImgTag() = default;
enum Align {
Bottom,
@@ -47,7 +47,7 @@ public:
int position = 0;
qreal offset = 0.0; // this offset allows us to compensate for flooring reserved space
Align align = QQuickStyledTextImgTag::Bottom;
- QQuickPixmap *pix = nullptr;
+ QScopedPointer<QQuickPixmap> pix;
};
class Q_AUTOTEST_EXPORT QQuickStyledText
diff --git a/src/quick/util/qquicksvgparser_p.h b/src/quick/util/qquicksvgparser_p.h
index 6cbaca9f6a..5fc4d54d2d 100644
--- a/src/quick/util/qquicksvgparser_p.h
+++ b/src/quick/util/qquicksvgparser_p.h
@@ -24,7 +24,7 @@ QT_BEGIN_NAMESPACE
namespace QQuickSvgParser
{
bool parsePathDataFast(const QString &dataStr, QPainterPath &path);
- Q_QUICK_PRIVATE_EXPORT void pathArc(QPainterPath &path, qreal rx, qreal ry, qreal x_axis_rotation,
+ Q_QUICK_EXPORT void pathArc(QPainterPath &path, qreal rx, qreal ry, qreal x_axis_rotation,
int large_arc_flag, int sweep_flag, qreal x, qreal y, qreal curx,
qreal cury);
}
diff --git a/src/quick/util/qquicksystempalette.cpp b/src/quick/util/qquicksystempalette.cpp
index 7271537ec7..46b2fc00ba 100644
--- a/src/quick/util/qquicksystempalette.cpp
+++ b/src/quick/util/qquicksystempalette.cpp
@@ -27,8 +27,8 @@ public:
The SystemPalette type provides access to the Qt application
palettes. This provides information about the standard colors used
for application windows, buttons and other features. These colors
- are grouped into three \e {color groups}: \c Active, \c Inactive,
- and \c Disabled. See the QPalette documentation for details about
+ are grouped into three \e {color groups}: \c active, \c inactive,
+ and \c disabled. See the QPalette documentation for details about
color groups and the properties provided by SystemPalette.
This can be used to color items in a way that provides a more
@@ -230,16 +230,28 @@ QColor QQuickSystemPalette::placeholderText() const
Q_D(const QQuickSystemPalette);
return QGuiApplication::palette().color(d->group, QPalette::PlaceholderText);
}
+
+/*!
+ \qmlproperty color QtQuick::SystemPalette::accent
+ The accent color of the current color group.
+
+ \since 6.7
+ \sa QPalette::ColorRole
+*/
+QColor QQuickSystemPalette::accent() const
+{
+ Q_D(const QQuickSystemPalette);
+ return QGuiApplication::palette().color(d->group, QPalette::Accent);
+}
+
/*!
\qmlproperty enumeration QtQuick::SystemPalette::colorGroup
The color group of the palette. This can be one of:
- \list
- \li SystemPalette.Active (default)
- \li SystemPalette.Inactive
- \li SystemPalette.Disabled
- \endlist
+ \value SystemPalette.Active (default) QPalette::Active
+ \value SystemPalette.Inactive QPalette::Inactive
+ \value SystemPalette.Disabled QPalette::Disabled
\sa QPalette::ColorGroup
*/
diff --git a/src/quick/util/qquicksystempalette_p.h b/src/quick/util/qquicksystempalette_p.h
index c850dc56c9..a28ed33be5 100644
--- a/src/quick/util/qquicksystempalette_p.h
+++ b/src/quick/util/qquicksystempalette_p.h
@@ -26,7 +26,7 @@
QT_BEGIN_NAMESPACE
class QQuickSystemPalettePrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickSystemPalette : public QObject
+class Q_QUICK_EXPORT QQuickSystemPalette : public QObject
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickSystemPalette)
@@ -47,6 +47,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickSystemPalette : public QObject
Q_PROPERTY(QColor highlight READ highlight NOTIFY paletteChanged)
Q_PROPERTY(QColor highlightedText READ highlightedText NOTIFY paletteChanged)
Q_PROPERTY(QColor placeholderText READ placeholderText NOTIFY paletteChanged REVISION(6, 2))
+ Q_PROPERTY(QColor accent READ accent NOTIFY paletteChanged REVISION(6, 7) FINAL)
QML_NAMED_ELEMENT(SystemPalette)
QML_ADDED_IN_VERSION(2, 0)
@@ -76,6 +77,7 @@ public:
QColor highlightedText() const;
QColor placeholderText() const;
+ QColor accent() const;
QQuickSystemPalette::ColorGroup colorGroup() const;
void setColorGroup(QQuickSystemPalette::ColorGroup);
@@ -86,6 +88,4 @@ Q_SIGNALS:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickSystemPalette)
-
#endif // QQUICKSYSTEMPALETTE_H
diff --git a/src/quick/util/qquicktextmetrics.cpp b/src/quick/util/qquicktextmetrics.cpp
index 655bf2dd6e..ee200ff878 100644
--- a/src/quick/util/qquicktextmetrics.cpp
+++ b/src/quick/util/qquicktextmetrics.cpp
@@ -92,12 +92,10 @@ void QQuickTextMetrics::setText(const QString &text)
This property holds the elide mode of the text. This determines the
position in which the string is elided. The possible values are:
- \list
- \li \c Qt::ElideNone - No eliding; this is the default value.
- \li \c Qt::ElideLeft - For example: "...World"
- \li \c Qt::ElideMiddle - For example: "He...ld"
- \li \c Qt::ElideRight - For example: "Hello..."
- \endlist
+ \value Qt::ElideNone No eliding; this is the default value.
+ \value Qt::ElideLeft For example: "...World"
+ \value Qt::ElideMiddle For example: "He...ld"
+ \value Qt::ElideRight For example: "Hello..."
\sa elideWidth, QFontMetrics::elidedText
*/
@@ -238,12 +236,11 @@ QString QQuickTextMetrics::elidedText() const
Override the default rendering type for this component.
Supported render types are:
- \list
- \li Text.QtRendering
- \li Text.NativeRendering
- \endlist
- This should match the intended renderType where you draw the text.
+ \value TextEdit.QtRendering Text is rendered using a scalable distance field for each glyph.
+ \value TextEdit.NativeRendering Text is rendered using a platform-specific technique.
+
+ This should match the intended \c renderType where you draw the text.
\since 6.3
\sa {Text::renderType}{Text.renderType}
diff --git a/src/quick/util/qquicktextmetrics_p.h b/src/quick/util/qquicktextmetrics_p.h
index f98d877f24..1660dfc87b 100644
--- a/src/quick/util/qquicktextmetrics_p.h
+++ b/src/quick/util/qquicktextmetrics_p.h
@@ -28,7 +28,7 @@ QT_BEGIN_NAMESPACE
class QFont;
-class Q_QUICK_PRIVATE_EXPORT QQuickTextMetrics : public QObject
+class Q_QUICK_EXPORT QQuickTextMetrics : public QObject
{
Q_OBJECT
@@ -91,6 +91,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickTextMetrics)
-
#endif // QQUICKTEXTMETRICS_H
diff --git a/src/quick/util/qquicktextselection.cpp b/src/quick/util/qquicktextselection.cpp
new file mode 100644
index 0000000000..c363434e5a
--- /dev/null
+++ b/src/quick/util/qquicktextselection.cpp
@@ -0,0 +1,187 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qquicktextselection_p.h"
+
+#include <QFont>
+#include <QTextOption>
+#include <QtQuick/private/qquicktextcontrol_p.h>
+#include <QtQuick/private/qquicktextcontrol_p_p.h>
+#include <QtQuick/private/qquicktextedit_p_p.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \qmltype TextSelection
+ \instantiates QQuickTextSelection
+ \inqmlmodule QtQuick
+ \ingroup qtquick-visual
+ \ingroup qtquick-input
+ \brief Represents a contiguous selection of text and its properties.
+ \since 6.7
+
+ \l {QtQuick::TextEdit::cursorSelection}{TextEdit.cursorSelection}
+ represents the range of text that is currently selected (for example by
+ dragging the mouse). It can be used to query and modify the selected text,
+ as well as properties in the \l {QTextCharFormat}{character} and
+ \l {QTextBlockFormat}{block} formats.
+
+ \note This API is considered tech preview and may change or be removed in
+ future versions of Qt.
+
+ \sa TextEdit, QTextCursor
+*/
+
+/*! \internal
+ QQuickTextSelection provides QML API using QTextCursor.
+ QQuickTextControl owns a text cursor, and one instance of
+ QQuickTextSelection represents it and delegates all operations to it.
+*/
+QQuickTextSelection::QQuickTextSelection(QObject *parent)
+ : QObject(parent)
+{
+ // When QQuickTextEdit creates its cursorSelection, it passes itself as the parent
+ if (auto *textEdit = qmlobject_cast<QQuickTextEdit *>(parent)) {
+ m_control = QQuickTextEditPrivate::get(textEdit)->control;
+ connect(m_control, &QQuickTextControl::currentCharFormatChanged,
+ this, &QQuickTextSelection::updateFromCharFormat);
+ connect(m_control, &QQuickTextControl::cursorPositionChanged,
+ this, &QQuickTextSelection::updateFromBlockFormat);
+ }
+}
+
+/*!
+ \qmlproperty string QtQuick::TextSelection::text
+
+ The selected text, without any rich text markup.
+
+ Setting this property replaces the selected text with the given string.
+*/
+QString QQuickTextSelection::text() const
+{
+ return cursor().selectedText();
+}
+
+void QQuickTextSelection::setText(const QString &text)
+{
+ auto cur = cursor();
+ if (cur.selectedText() == text)
+ return;
+
+ cur.insertText(text);
+ emit textChanged();
+}
+
+/*!
+ \qmlproperty color QtQuick::TextSelection::font
+
+ The font of the selected text.
+
+ \sa QTextCharFormat::font()
+*/
+QFont QQuickTextSelection::font() const
+{
+ return cursor().charFormat().font();
+}
+
+void QQuickTextSelection::setFont(const QFont &font)
+{
+ auto cur = cursor();
+ if (cur.selection().isEmpty())
+ cur.select(QTextCursor::WordUnderCursor);
+
+ if (font == cur.charFormat().font())
+ return;
+
+ QTextCharFormat fmt;
+ fmt.setFont(font);
+ cur.mergeCharFormat(fmt);
+ emit fontChanged();
+}
+
+/*!
+ \qmlproperty color QtQuick::TextSelection::color
+
+ The foreground color of the selected text.
+
+ \sa QTextCharFormat::foreground()
+*/
+QColor QQuickTextSelection::color() const
+{
+ return cursor().charFormat().foreground().color();
+}
+
+void QQuickTextSelection::setColor(QColor color)
+{
+ auto cur = cursor();
+ if (cur.selection().isEmpty())
+ cur.select(QTextCursor::WordUnderCursor);
+
+ if (color == cur.charFormat().foreground().color())
+ return;
+
+ QTextCharFormat fmt;
+ fmt.setForeground(color);
+ cur.mergeCharFormat(fmt);
+ emit colorChanged();
+}
+
+/*!
+ \qmlproperty enumeration QtQuick::TextSelection::alignment
+
+ The alignment of the block containing the selected text.
+
+ \sa QTextBlockFormat::alignment()
+*/
+Qt::Alignment QQuickTextSelection::alignment() const
+{
+ return cursor().blockFormat().alignment();
+}
+
+void QQuickTextSelection::setAlignment(Qt::Alignment align)
+{
+ if (align == alignment())
+ return;
+
+ QTextBlockFormat format;
+ format.setAlignment(align);
+ cursor().mergeBlockFormat(format);
+ emit alignmentChanged();
+}
+
+/*! \internal
+ Return the cursor, which is either the graphically-manipulable cursor from
+ QQuickTextControl if that is set, or else the internally-stored cursor
+ with which the user is trying to mutate and/or monitor the underlying document,
+ in the case that TextSelection is declared in QML.
+*/
+QTextCursor QQuickTextSelection::cursor() const
+{
+ if (m_control)
+ return m_control->textCursor();
+ return m_cursor;
+}
+
+inline void QQuickTextSelection::updateFromCharFormat(const QTextCharFormat &fmt)
+{
+ if (fmt.font() != m_charFormat.font())
+ emit fontChanged();
+ if (fmt.foreground().color() != m_charFormat.foreground().color())
+ emit colorChanged();
+
+ m_charFormat = fmt;
+}
+
+inline void QQuickTextSelection::updateFromBlockFormat()
+{
+ QTextBlockFormat fmt = cursor().blockFormat();
+
+ if (fmt.alignment() != m_blockFormat.alignment())
+ emit alignmentChanged();
+
+ m_blockFormat = fmt;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qquicktextselection_p.cpp"
diff --git a/src/quick/util/qquicktextselection_p.h b/src/quick/util/qquicktextselection_p.h
new file mode 100644
index 0000000000..85feb9a5d8
--- /dev/null
+++ b/src/quick/util/qquicktextselection_p.h
@@ -0,0 +1,80 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQUICKTEXTSELECTION_H
+#define QQUICKTEXTSELECTION_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtquickglobal_p.h>
+
+#include <QtQuick/qquicktextdocument.h>
+
+#include <QtQml/qqml.h>
+
+#include <QtGui/qtextcursor.h>
+
+QT_BEGIN_NAMESPACE
+
+class QFont;
+class QQuickTextControl;
+
+class Q_QUICK_EXPORT QQuickTextSelection : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged FINAL)
+ Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged FINAL)
+ Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged FINAL)
+ Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment NOTIFY alignmentChanged FINAL)
+
+ QML_ANONYMOUS
+ QML_ADDED_IN_VERSION(6, 7)
+
+public:
+ explicit QQuickTextSelection(QObject *parent = nullptr);
+
+ QString text() const;
+ void setText(const QString &text);
+
+ QFont font() const;
+ void setFont(const QFont &font);
+
+ QColor color() const;
+ void setColor(QColor color);
+
+ Qt::Alignment alignment() const;
+ void setAlignment(Qt::Alignment align);
+
+Q_SIGNALS:
+ void textChanged();
+ void fontChanged();
+ void colorChanged();
+ void alignmentChanged();
+
+private:
+ QTextCursor cursor() const;
+ void updateFromCharFormat(const QTextCharFormat &fmt);
+ void updateFromBlockFormat();
+
+private:
+ QTextCursor m_cursor;
+ QTextCharFormat m_charFormat;
+ QTextBlockFormat m_blockFormat;
+ QQuickTextControl *m_control = nullptr;
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQuickTextSelection)
+
+#endif // QQUICKTEXTSELECTION_H
diff --git a/src/quick/util/qquicktimeline_p_p.h b/src/quick/util/qquicktimeline_p_p.h
index 80076f7030..367bbf8c7d 100644
--- a/src/quick/util/qquicktimeline_p_p.h
+++ b/src/quick/util/qquicktimeline_p_p.h
@@ -26,7 +26,7 @@ class QQuickTimeLineValue;
class QQuickTimeLineCallback;
struct QQuickTimeLinePrivate;
class QQuickTimeLineObject;
-class Q_QUICK_PRIVATE_EXPORT QQuickTimeLine : public QObject, QAbstractAnimationJob
+class Q_QUICK_EXPORT QQuickTimeLine : public QObject, QAbstractAnimationJob
{
Q_OBJECT
public:
@@ -134,29 +134,21 @@ template<class T>
class QQuickTimeLineValueProxy : public QQuickTimeLineValue
{
public:
- QQuickTimeLineValueProxy(T *cls, void (T::*func)(qreal), qreal v = 0.)
- : QQuickTimeLineValue(v), _class(cls), _setFunctionReal(func), _setFunctionInt(nullptr)
+ QQuickTimeLineValueProxy(T *object, void (T::*func)(qreal), qreal v = 0.)
+ : QQuickTimeLineValue(v), object(object), setter(func)
{
- Q_ASSERT(_class);
- }
-
- QQuickTimeLineValueProxy(T *cls, void (T::*func)(int), qreal v = 0.)
- : QQuickTimeLineValue(v), _class(cls), _setFunctionReal(0), _setFunctionInt(func)
- {
- Q_ASSERT(_class);
+ Q_ASSERT(object);
}
void setValue(qreal v) override
{
QQuickTimeLineValue::setValue(v);
- if (_setFunctionReal) (_class->*_setFunctionReal)(v);
- else if (_setFunctionInt) (_class->*_setFunctionInt)((int)v);
+ (object->*setter)(v);
}
private:
- T *_class;
- void (T::*_setFunctionReal)(qreal);
- void (T::*_setFunctionInt)(int);
+ T *object;
+ void (T::*setter)(qreal);
};
QT_END_NAMESPACE
diff --git a/src/quick/util/qquicktransition.cpp b/src/quick/util/qquicktransition.cpp
index 062e6e2656..19a1ceb830 100644
--- a/src/quick/util/qquicktransition.cpp
+++ b/src/quick/util/qquicktransition.cpp
@@ -46,6 +46,11 @@ QT_BEGIN_NAMESPACE
values can be set to restrict the animations to only be applied when changing
from one particular state to another.
+ Top-level animations within a transition are run in parallel. To run them
+ sequentially, define them within a SequentialAnimation:
+
+ \snippet qml/transition-reversible.qml sequential animations
+
To define multiple Transitions, specify \l Item::transitions as a list:
\snippet qml/transitions-list.qml list of transitions
@@ -60,7 +65,7 @@ QT_BEGIN_NAMESPACE
\l Behavior, the Transition animation overrides the \l Behavior for that
state change.
- \sa {Animation and Transitions in Qt Quick}, {Qt Quick Examples - Animation#States}{States example}, {Qt Quick States}, {Qt QML}
+ \sa {Animation and Transitions in Qt Quick}, {Qt Quick Examples - Animation#States}{States example}, {Qt Quick States}, {Qt Qml}
*/
//ParallelAnimationWrapper allows us to do a "callback" when the animation finishes, rather than connecting
@@ -419,7 +424,7 @@ bool QQuickTransition::running() const
This property holds a list of the animations to be run for this transition.
- \snippet ../qml/dynamicscene/dynamicscene.qml top-level transitions
+ \snippet qml/transition-animation.qml 0
The top-level animations are run in parallel. To run them sequentially,
define them within a SequentialAnimation:
diff --git a/src/quick/util/qquicktransition_p.h b/src/quick/util/qquicktransition_p.h
index 7e953f528a..7a98a8bea0 100644
--- a/src/quick/util/qquicktransition_p.h
+++ b/src/quick/util/qquicktransition_p.h
@@ -55,7 +55,7 @@ private:
friend class QQuickTransition;
};
-class Q_QUICK_PRIVATE_EXPORT QQuickTransition : public QObject
+class Q_QUICK_EXPORT QQuickTransition : public QObject
{
Q_OBJECT
Q_DECLARE_PRIVATE(QQuickTransition)
@@ -108,6 +108,4 @@ Q_SIGNALS:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickTransition)
-
#endif // QQUICKTRANSITION_H
diff --git a/src/quick/util/qquicktransitionmanager.cpp b/src/quick/util/qquicktransitionmanager.cpp
index efaa01c305..6788cc73a9 100644
--- a/src/quick/util/qquicktransitionmanager.cpp
+++ b/src/quick/util/qquicktransitionmanager.cpp
@@ -15,8 +15,6 @@
QT_BEGIN_NAMESPACE
-Q_DECLARE_LOGGING_CATEGORY(lcStates)
-
class QQuickTransitionManagerPrivate
{
public:
@@ -190,8 +188,8 @@ void QQuickTransitionManager::transition(const QList<QQuickStateAction> &list,
}
return false;
};
- auto newEnd = std::remove_if(applyList.begin(), applyList.end(), isHandledInTransition);
- applyList.erase(newEnd, applyList.end());
+
+ applyList.removeIf(isHandledInTransition);
}
// Any actions remaining have not been handled by the transition and should
diff --git a/src/quick/util/qquicktransitionmanager_p_p.h b/src/quick/util/qquicktransitionmanager_p_p.h
index 8b2f52aff9..3b20dddab0 100644
--- a/src/quick/util/qquicktransitionmanager_p_p.h
+++ b/src/quick/util/qquicktransitionmanager_p_p.h
@@ -23,7 +23,7 @@ QT_BEGIN_NAMESPACE
class QQuickState;
class QQuickStateAction;
class QQuickTransitionManagerPrivate;
-class Q_QUICK_PRIVATE_EXPORT QQuickTransitionManager
+class Q_QUICK_EXPORT QQuickTransitionManager
{
public:
QQuickTransitionManager();
diff --git a/src/quick/util/qquickvalidator.cpp b/src/quick/util/qquickvalidator.cpp
index 76cba4c872..d5d584cccb 100644
--- a/src/quick/util/qquickvalidator.cpp
+++ b/src/quick/util/qquickvalidator.cpp
@@ -162,12 +162,8 @@ void QQuickDoubleValidator::resetLocaleName()
The possible values for this property are:
- \list
- \li DoubleValidator.StandardNotation
- \li DoubleValidator.ScientificNotation (default)
- \endlist
-
- If this property is set to DoubleValidator.ScientificNotation, the written number may have an exponent part (e.g. 1.5E-2).
+ \value DoubleValidator.StandardNotation only decimal numbers with optional sign (e.g. \c -0.015)
+ \value DoubleValidator.ScientificNotation (default) the written number may have an exponent part (e.g. \c 1.5E-2)
*/
/*!
diff --git a/src/quick/util/qquickvalidator_p.h b/src/quick/util/qquickvalidator_p.h
index 72706386e5..da404d80ba 100644
--- a/src/quick/util/qquickvalidator_p.h
+++ b/src/quick/util/qquickvalidator_p.h
@@ -24,7 +24,7 @@
QT_BEGIN_NAMESPACE
#if QT_CONFIG(validator)
-class Q_QUICK_PRIVATE_EXPORT QQuickIntValidator : public QIntValidator
+class Q_QUICK_EXPORT QQuickIntValidator : public QIntValidator
{
Q_OBJECT
Q_PROPERTY(QString locale READ localeName WRITE setLocaleName RESET resetLocaleName NOTIFY localeNameChanged)
@@ -41,7 +41,7 @@ Q_SIGNALS:
void localeNameChanged();
};
-class Q_QUICK_PRIVATE_EXPORT QQuickDoubleValidator : public QDoubleValidator
+class Q_QUICK_EXPORT QQuickDoubleValidator : public QDoubleValidator
{
Q_OBJECT
Q_PROPERTY(QString locale READ localeName WRITE setLocaleName RESET resetLocaleName NOTIFY localeNameChanged)
@@ -61,13 +61,4 @@ Q_SIGNALS:
QT_END_NAMESPACE
-#if QT_CONFIG(validator)
-QML_DECLARE_TYPE(QValidator)
-QML_DECLARE_TYPE(QQuickIntValidator)
-QML_DECLARE_TYPE(QQuickDoubleValidator)
-#if QT_CONFIG(regularexpression)
-QML_DECLARE_TYPE(QRegularExpressionValidator)
-#endif
-#endif
-
#endif // QQUICKVALIDATOR_P_H
diff --git a/src/quick/util/qquickvaluetypes.cpp b/src/quick/util/qquickvaluetypes.cpp
index a5a9424597..265c763566 100644
--- a/src/quick/util/qquickvaluetypes.cpp
+++ b/src/quick/util/qquickvaluetypes.cpp
@@ -5,6 +5,7 @@
#include <qtquickglobal.h>
#include <private/qqmlvaluetype_p.h>
+#include <private/qqmlstringconverters_p.h>
#include <private/qcolorspace_p.h>
#include <private/qfont_p.h>
@@ -162,48 +163,10 @@ void QQuickColorValueType::setHslLightness(qreal hslLightness)
v.setHslF(hue, saturation, hslLightness, alpha);
}
-template<typename T, int NumParams>
-QVariant createValueTypeFromNumberString(const QString &s)
-{
- Q_STATIC_ASSERT_X(NumParams == 2 || NumParams == 3 || NumParams == 4 || NumParams == 16,
- "Unsupported number of params; add an additional case below if necessary.");
-
- if (s.count(u',') != NumParams - 1)
- return QVariant();
-
- QVarLengthArray<float, NumParams> parameters;
- bool ok = true;
- for (qsizetype prev = 0, next = s.indexOf(u','), length = s.size(); ok && prev < length;) {
- parameters.append(s.mid(prev, next - prev).toFloat(&ok));
- prev = next + 1;
- next = (parameters.size() == NumParams - 1) ? length : s.indexOf(u',', prev);
- }
-
- if (!ok)
- return QVariant();
-
- if constexpr (NumParams == 2) {
- return T(parameters[0], parameters[1]);
- } else if constexpr (NumParams == 3) {
- return T(parameters[0], parameters[1], parameters[2]);
- } else if constexpr (NumParams == 4) {
- return T(parameters[0], parameters[1], parameters[2], parameters[3]);
- } else if constexpr (NumParams == 16) {
- return T(parameters[0], parameters[1], parameters[2], parameters[3],
- parameters[4], parameters[5], parameters[6], parameters[7],
- parameters[8], parameters[9], parameters[10], parameters[11],
- parameters[12], parameters[13], parameters[14], parameters[15]);
- } else {
- Q_UNREACHABLE();
- }
-
- return QVariant();
-}
-
QVariant QQuickVector2DValueType::create(const QJSValue &params)
{
if (params.isString())
- return createValueTypeFromNumberString<QVector2D, 2>(params.toString());
+ return QQmlStringConverters::valueTypeFromNumberString<QVector2D, 2, u','>(params.toString());
if (params.isArray())
return QVector2D(params.property(0).toNumber(), params.property(1).toNumber());
return QVariant();
@@ -296,8 +259,10 @@ bool QQuickVector2DValueType::fuzzyEquals(const QVector2D &vec) const
QVariant QQuickVector3DValueType::create(const QJSValue &params)
{
- if (params.isString())
- return createValueTypeFromNumberString<QVector3D, 3>(params.toString());
+ if (params.isString()) {
+ return QQmlStringConverters::valueTypeFromNumberString<QVector3D, 3, u',', u','>(
+ params.toString());
+ }
if (params.isArray()) {
return QVector3D(params.property(0).toNumber(), params.property(1).toNumber(),
@@ -415,8 +380,10 @@ bool QQuickVector3DValueType::fuzzyEquals(const QVector3D &vec) const
QVariant QQuickVector4DValueType::create(const QJSValue &params)
{
- if (params.isString())
- return createValueTypeFromNumberString<QVector4D, 4>(params.toString());
+ if (params.isString()) {
+ return QQmlStringConverters::valueTypeFromNumberString<QVector4D, 4, u',', u',', u','>(
+ params.toString());
+ }
if (params.isArray()) {
return QVector4D(params.property(0).toNumber(), params.property(1).toNumber(),
@@ -542,8 +509,10 @@ bool QQuickVector4DValueType::fuzzyEquals(const QVector4D &vec) const
QVariant QQuickQuaternionValueType::create(const QJSValue &params)
{
- if (params.isString())
- return createValueTypeFromNumberString<QQuaternion, 4>(params.toString());
+ if (params.isString()) {
+ return QQmlStringConverters::valueTypeFromNumberString<QQuaternion, 4, u',', u',', u','>(
+ params.toString());
+ }
if (params.isArray()) {
return QQuaternion(params.property(0).toNumber(), params.property(1).toNumber(),
@@ -682,8 +651,12 @@ QVariant QQuickMatrix4x4ValueType::create(const QJSValue &params)
if (params.isNull() || params.isUndefined())
return QMatrix4x4();
- if (params.isString())
- return createValueTypeFromNumberString<QMatrix4x4, 16>(params.toString());
+ if (params.isString()) {
+ return QQmlStringConverters::valueTypeFromNumberString<QMatrix4x4, 16, u',', u',', u',',
+ u',', u',', u',', u',', u',', u',',
+ u',', u',', u',', u',', u',', u','>(
+ params.toString());
+ }
if (params.isArray() && params.property(QStringLiteral("length")).toInt() == 16) {
return QMatrix4x4(params.property(0).toNumber(),
@@ -790,6 +763,135 @@ bool QQuickMatrix4x4ValueType::fuzzyEquals(const QMatrix4x4 &m) const
return qFuzzyCompare(v, m);
}
+/*!
+ \qmltype PlanarTransform
+ \inqmlmodule QtQuick
+ \since 6.8
+
+ \brief Provides utility functions for matrix4x4 when used for 2D transforms.
+
+ The \c PlanarTransform is a global object with utility functions.
+
+ It is not instantiable; to use it, call the members of the global \c PlanarTransform object
+ directly. For example:
+
+ \qml
+ Item {
+ transform: Matrix4x4 { matrix: PlanarTransform.fromAffineMatrix(1, 0, 0.36, 1, -36, 0) }
+ }
+ \endqml
+*/
+
+QQuickPlanarTransform::QQuickPlanarTransform(QObject *parent)
+ : QObject(parent)
+{
+}
+
+/*!
+ \qmlmethod matrix4x4 PlanarTransform::identity()
+
+ Returns a matrix4x4 for the identity transform.
+
+ This is equivalent to \l Qt::matrix4x4().
+*/
+
+QMatrix4x4 QQuickPlanarTransform::identity()
+{
+ return QMatrix4x4();
+}
+
+/*!
+ \qmlmethod matrix4x4 PlanarTransform::fromAffineMatrix(real scaleX, real shearY,
+ real shearX, real scaleY,
+ real translateX, real translateY)
+
+ Returns a matrix4x4 for an affine (non-projecting) 2D transform with the specified values.
+
+ This method and its argument order correspond to SVG's \c matrix() function and the
+ six-argument QTransform constructor. The result is this 4x4 matrix:
+
+ \table
+ \row \li \a scaleX \li \a shearX \li 0 \li \a translateX
+ \row \li \a shearY \li \a scaleY \li 0 \li \a translateY
+ \row \li 0 \li 0 \li 1 \li 0
+ \row \li 0 \li 0 \li 0 \li 1
+ \endtable
+*/
+
+QMatrix4x4 QQuickPlanarTransform::fromAffineMatrix(float scaleX, float shearY,
+ float shearX, float scaleY,
+ float translateX, float translateY)
+{
+ return QMatrix4x4(scaleX, shearX, 0, translateX,
+ shearY, scaleY, 0, translateY,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1);
+}
+
+/*!
+ \qmlmethod matrix4x4 PlanarTransform::fromTranslate(real translateX, real translateY)
+
+ Returns a matrix4x4 for a 2D transform that translates by \a translateX horizontally and
+ \a translateY vertically.
+*/
+QMatrix4x4 QQuickPlanarTransform::fromTranslate(float translateX, float translateY)
+{
+ QMatrix4x4 xf;
+ xf.translate(translateX, translateY);
+ return xf;
+}
+
+/*!
+ \qmlmethod matrix4x4 PlanarTransform::fromScale(real scaleX, real scaleY, real originX, real originY)
+
+ Returns a matrix4x4 for a 2D transform that scales by \a scaleX horizontally and \a scaleY
+ vertically, centered at the point (\a originX, \a originY).
+
+ \a originX and \a originY are optional and default to (0, 0).
+*/
+QMatrix4x4 QQuickPlanarTransform::fromScale(float scaleX, float scaleY, float originX, float originY)
+{
+ QMatrix4x4 xf;
+ xf.translate(originX, originY);
+ xf.scale(scaleX, scaleY);
+ xf.translate(-originX, -originY);
+ return xf;
+}
+
+/*!
+ \qmlmethod matrix4x4 PlanarTransform::fromRotate(real angle, real originX, real originY)
+
+ Returns a matrix4x4 for a 2D transform that rotates by \a angle degrees around the point (\a
+ originX, \a originY).
+
+ \a originX and \a originY are optional and default to (0, 0).
+*/
+QMatrix4x4 QQuickPlanarTransform::fromRotate(float angle, float originX, float originY)
+{
+ QMatrix4x4 xf;
+ xf.translate(originX, originY);
+ xf.rotate(angle, 0, 0, 1);
+ xf.translate(-originX, -originY);
+ return xf;
+}
+
+/*!
+ \qmlmethod matrix4x4 PlanarTransform::fromShear(float shearX, float shearY, float originX, float originY)
+
+ Returns a matrix4x4 for a 2D transform that shears by \a shearX horizontally and \a shearY
+ vertically, centered at the point (\a originX, \a originY).
+
+ \a originX and \a originY are optional and default to (0, 0).
+*/
+QMatrix4x4 QQuickPlanarTransform::fromShear(float shearX, float shearY, float originX, float originY)
+{
+ QMatrix4x4 xf;
+ xf.translate(originX, originY);
+ xf *= QMatrix4x4(1, shearX, 0, 0, shearY, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
+ xf.translate(-originX, -originY);
+ return xf;
+}
+
template<typename T>
void setFontProperty(QFont &font, void (QFont::*setter)(T value), QString name,
const QJSValue &params, bool *ok)
@@ -836,27 +938,45 @@ QVariant QQuickFontValueType::create(const QJSValue &params)
setFontProperty(ret, &QFont::setPixelSize, QStringLiteral("pixelSize"), params, &ok);
setFontProperty(ret, &QFont::setPointSize, QStringLiteral("pointSize"), params, &ok);
setFontProperty(ret, &QFont::setStrikeOut, QStringLiteral("strikeout"), params, &ok);
+ setFontProperty(ret, &QFont::setStyleName, QStringLiteral("styleName"), params, &ok);
setFontProperty(ret, &QFont::setUnderline, QStringLiteral("underline"), params, &ok);
setFontProperty(ret, &QFont::setWeight, QStringLiteral("weight"), params, &ok);
setFontProperty(ret, &QFont::setWordSpacing, QStringLiteral("wordSpacing"), params, &ok);
setFontProperty(ret, &QFont::setHintingPreference, QStringLiteral("hintingPreference"), params, &ok);
setFontProperty(ret, &QFont::setKerning, QStringLiteral("kerning"), params, &ok);
- const QJSValue vlspac = params.property(QStringLiteral("letterSpacing"));
- if (vlspac.isNumber()) {
- ret.setLetterSpacing(QFont::AbsoluteSpacing, vlspac.toNumber());
- ok = true;
+ {
+ const QJSValue vlspac = params.property(QStringLiteral("letterSpacing"));
+ if (vlspac.isNumber()) {
+ ret.setLetterSpacing(QFont::AbsoluteSpacing, vlspac.toNumber());
+ ok = true;
+ }
}
- const QJSValue vshaping = params.property(QStringLiteral("preferShaping"));
- if (vshaping.isBool()) {
- const bool enable = vshaping.toBool();
- const QFont::StyleStrategy strategy = ret.styleStrategy();
- if (enable)
- ret.setStyleStrategy(QFont::StyleStrategy(strategy & ~QFont::PreferNoShaping));
- else
- ret.setStyleStrategy(QFont::StyleStrategy(strategy | QFont::PreferNoShaping));
- ok = true;
+ {
+ const QJSValue vshaping = params.property(QStringLiteral("preferShaping"));
+ if (vshaping.isBool()) {
+ const bool enable = vshaping.toBool();
+ const QFont::StyleStrategy strategy = ret.styleStrategy();
+ if (enable)
+ ret.setStyleStrategy(QFont::StyleStrategy(strategy & ~QFont::PreferNoShaping));
+ else
+ ret.setStyleStrategy(QFont::StyleStrategy(strategy | QFont::PreferNoShaping));
+ ok = true;
+ }
+ }
+
+ {
+ const QJSValue typoMetrics = params.property(QStringLiteral("preferTypoLineMetrics"));
+ if (typoMetrics.isBool()) {
+ const bool enable = typoMetrics.toBool();
+ const QFont::StyleStrategy strategy = ret.styleStrategy();
+ if (enable)
+ ret.setStyleStrategy(QFont::StyleStrategy(strategy & ~QFont::PreferTypoLineMetrics));
+ else
+ ret.setStyleStrategy(QFont::StyleStrategy(strategy | QFont::PreferTypoLineMetrics));
+ ok = true;
+ }
}
return ok ? ret : QVariant();
@@ -1047,6 +1167,92 @@ void QQuickFontValueType::setPreferShaping(bool enable)
v.setStyleStrategy(static_cast<QFont::StyleStrategy>(v.styleStrategy() | QFont::PreferNoShaping));
}
+void QQuickFontValueType::setVariableAxes(const QVariantMap &variableAxes)
+{
+ v.clearVariableAxes();
+ for (auto [variableAxisName, variableAxisValue] : variableAxes.asKeyValueRange()) {
+ const auto maybeTag = QFont::Tag::fromString(variableAxisName);
+ if (!maybeTag) {
+ qWarning() << "Invalid variable axis" << variableAxisName << "ignored";
+ continue;
+ }
+
+ bool ok;
+ float value = variableAxisValue.toFloat(&ok);
+ if (!ok) {
+ qWarning() << "Variable axis" << variableAxisName << "value" << variableAxisValue << "is not a floating point value.";
+ continue;
+ }
+
+ v.setVariableAxis(*maybeTag, value);
+ }
+}
+
+QVariantMap QQuickFontValueType::variableAxes() const
+{
+ QVariantMap ret;
+ for (const auto &tag : v.variableAxisTags())
+ ret.insert(QString::fromUtf8(tag.toString()), v.variableAxisValue(tag));
+
+ return ret;
+}
+
+void QQuickFontValueType::setFeatures(const QVariantMap &features)
+{
+ v.clearFeatures();
+ for (auto [featureName, featureValue] : features.asKeyValueRange()) {
+ const auto maybeTag = QFont::Tag::fromString(featureName);
+ if (!maybeTag) {
+ qWarning() << "Invalid font feature" << featureName << "ignored";
+ continue;
+ }
+
+ bool ok;
+ quint32 value = featureValue.toUInt(&ok);
+ if (!ok) {
+ qWarning() << "Font feature" << featureName << "value" << featureValue << "is not an integer.";
+ continue;
+ }
+
+ v.setFeature(*maybeTag, value);
+ }
+}
+
+QVariantMap QQuickFontValueType::features() const
+{
+ QVariantMap ret;
+ for (const auto &tag : v.featureTags())
+ ret.insert(QString::fromUtf8(tag.toString()), v.featureValue(tag));
+
+ return ret;
+}
+
+bool QQuickFontValueType::contextFontMerging() const
+{
+ return (v.styleStrategy() & QFont::ContextFontMerging) != 0;
+}
+
+void QQuickFontValueType::setContextFontMerging(bool enable)
+{
+ if (enable)
+ v.setStyleStrategy(static_cast<QFont::StyleStrategy>(v.styleStrategy() | QFont::ContextFontMerging));
+ else
+ v.setStyleStrategy(static_cast<QFont::StyleStrategy>(v.styleStrategy() & ~QFont::ContextFontMerging));
+}
+
+bool QQuickFontValueType::preferTypoLineMetrics() const
+{
+ return (v.styleStrategy() & QFont::PreferTypoLineMetrics) != 0;
+}
+
+void QQuickFontValueType::setPreferTypoLineMetrics(bool enable)
+{
+ if (enable)
+ v.setStyleStrategy(static_cast<QFont::StyleStrategy>(v.styleStrategy() | QFont::PreferTypoLineMetrics));
+ else
+ v.setStyleStrategy(static_cast<QFont::StyleStrategy>(v.styleStrategy() & ~QFont::PreferTypoLineMetrics));
+}
+
QVariant QQuickColorSpaceValueType::create(const QJSValue &params)
{
if (!params.isObject())
diff --git a/src/quick/util/qquickvaluetypes_p.h b/src/quick/util/qquickvaluetypes_p.h
index 70ad5c2b0d..6fd33c1323 100644
--- a/src/quick/util/qquickvaluetypes_p.h
+++ b/src/quick/util/qquickvaluetypes_p.h
@@ -30,7 +30,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QQuickColorValueType
+class Q_QUICK_EXPORT QQuickColorValueType
{
QColor v;
Q_PROPERTY(qreal r READ r WRITE setR FINAL)
@@ -43,7 +43,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickColorValueType
Q_PROPERTY(qreal hslHue READ hslHue WRITE setHslHue FINAL)
Q_PROPERTY(qreal hslSaturation READ hslSaturation WRITE setHslSaturation FINAL)
Q_PROPERTY(qreal hslLightness READ hslLightness WRITE setHslLightness FINAL)
- Q_PROPERTY(bool valid READ isValid)
+ Q_PROPERTY(bool valid READ isValid FINAL)
Q_GADGET
QML_ADDED_IN_VERSION(2, 0)
QML_FOREIGN(QColor)
@@ -54,6 +54,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickColorValueType
public:
static QVariant create(const QJSValue &params);
+ Q_INVOKABLE QQuickColorValueType() = default;
Q_INVOKABLE QQuickColorValueType(const QString &string);
Q_INVOKABLE QString toString() const;
@@ -83,9 +84,11 @@ public:
void setHslHue(qreal);
void setHslSaturation(qreal);
void setHslLightness(qreal);
+
+ operator QColor() const { return v; }
};
-class Q_QUICK_PRIVATE_EXPORT QQuickVector2DValueType
+class Q_QUICK_EXPORT QQuickVector2DValueType
{
QVector2D v;
Q_PROPERTY(qreal x READ x WRITE setX FINAL)
@@ -100,6 +103,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickVector2DValueType
public:
static QVariant create(const QJSValue &params);
+ Q_INVOKABLE QQuickVector2DValueType() = default;
Q_INVOKABLE QString toString() const;
qreal x() const;
@@ -118,9 +122,11 @@ public:
Q_INVOKABLE QVector4D toVector4d() const;
Q_INVOKABLE bool fuzzyEquals(const QVector2D &vec, qreal epsilon) const;
Q_INVOKABLE bool fuzzyEquals(const QVector2D &vec) const;
+
+ operator QVector2D() const { return v; }
};
-class Q_QUICK_PRIVATE_EXPORT QQuickVector3DValueType
+class Q_QUICK_EXPORT QQuickVector3DValueType
{
QVector3D v;
Q_PROPERTY(qreal x READ x WRITE setX FINAL)
@@ -136,6 +142,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickVector3DValueType
public:
static QVariant create(const QJSValue &params);
+ Q_INVOKABLE QQuickVector3DValueType() = default;
Q_INVOKABLE QString toString() const;
qreal x() const;
@@ -158,9 +165,11 @@ public:
Q_INVOKABLE QVector4D toVector4d() const;
Q_INVOKABLE bool fuzzyEquals(const QVector3D &vec, qreal epsilon) const;
Q_INVOKABLE bool fuzzyEquals(const QVector3D &vec) const;
+
+ operator QVector3D() const { return v; }
};
-class Q_QUICK_PRIVATE_EXPORT QQuickVector4DValueType
+class Q_QUICK_EXPORT QQuickVector4DValueType
{
QVector4D v;
Q_PROPERTY(qreal x READ x WRITE setX FINAL)
@@ -177,6 +186,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickVector4DValueType
public:
static QVariant create(const QJSValue &params);
+ Q_INVOKABLE QQuickVector4DValueType() = default;
Q_INVOKABLE QString toString() const;
qreal x() const;
@@ -200,15 +210,17 @@ public:
Q_INVOKABLE QVector3D toVector3d() const;
Q_INVOKABLE bool fuzzyEquals(const QVector4D &vec, qreal epsilon) const;
Q_INVOKABLE bool fuzzyEquals(const QVector4D &vec) const;
+
+ operator QVector4D() const { return v; }
};
-class Q_QUICK_PRIVATE_EXPORT QQuickQuaternionValueType
+class Q_QUICK_EXPORT QQuickQuaternionValueType
{
QQuaternion v;
- Q_PROPERTY(qreal scalar READ scalar WRITE setScalar)
- Q_PROPERTY(qreal x READ x WRITE setX)
- Q_PROPERTY(qreal y READ y WRITE setY)
- Q_PROPERTY(qreal z READ z WRITE setZ)
+ Q_PROPERTY(qreal scalar READ scalar WRITE setScalar FINAL)
+ Q_PROPERTY(qreal x READ x WRITE setX FINAL)
+ Q_PROPERTY(qreal y READ y WRITE setY FINAL)
+ Q_PROPERTY(qreal z READ z WRITE setZ FINAL)
Q_GADGET
QML_ADDED_IN_VERSION(2, 0)
QML_FOREIGN(QQuaternion)
@@ -219,6 +231,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickQuaternionValueType
public:
static QVariant create(const QJSValue &params);
+ Q_INVOKABLE QQuickQuaternionValueType() = default;
Q_INVOKABLE QString toString() const;
qreal scalar() const;
@@ -247,9 +260,11 @@ public:
Q_INVOKABLE bool fuzzyEquals(const QQuaternion &q, qreal epsilon) const;
Q_INVOKABLE bool fuzzyEquals(const QQuaternion &q) const;
+
+ operator QQuaternion() const { return v; }
};
-class Q_QUICK_PRIVATE_EXPORT QQuickMatrix4x4ValueType
+class Q_QUICK_EXPORT QQuickMatrix4x4ValueType
{
QMatrix4x4 v;
Q_PROPERTY(qreal m11 READ m11 WRITE setM11 FINAL)
@@ -278,6 +293,8 @@ class Q_QUICK_PRIVATE_EXPORT QQuickMatrix4x4ValueType
public:
static QVariant create(const QJSValue &params);
+ Q_INVOKABLE QQuickMatrix4x4ValueType() = default;
+
qreal m11() const { return v(0, 0); }
qreal m12() const { return v(0, 1); }
qreal m13() const { return v(0, 2); }
@@ -339,11 +356,35 @@ public:
Q_INVOKABLE bool fuzzyEquals(const QMatrix4x4 &m, qreal epsilon) const;
Q_INVOKABLE bool fuzzyEquals(const QMatrix4x4 &m) const;
+
+ operator QMatrix4x4() const { return v; }
+};
+
+class Q_QUICK_EXPORT QQuickPlanarTransform : public QObject
+{
+ Q_OBJECT
+ QML_SINGLETON
+ QML_NAMED_ELEMENT(PlanarTransform)
+ QML_ADDED_IN_VERSION(6, 8)
+
+public:
+ explicit QQuickPlanarTransform(QObject *parent = nullptr);
+
+ Q_INVOKABLE static QMatrix4x4 identity();
+ Q_INVOKABLE static QMatrix4x4 fromAffineMatrix(float scaleX, float shearY,
+ float shearX, float scaleY,
+ float translateX, float translateY);
+ Q_INVOKABLE static QMatrix4x4 fromTranslate(float translateX, float translateY);
+ Q_INVOKABLE static QMatrix4x4 fromScale(float scaleX, float scaleY,
+ float originX = 0, float originY = 0);
+ Q_INVOKABLE static QMatrix4x4 fromRotate(float angle,float originX = 0, float originY = 0);
+ Q_INVOKABLE static QMatrix4x4 fromShear(float shearX, float shearY,
+ float originX = 0, float originY = 0);
};
namespace QQuickFontEnums
{
-Q_NAMESPACE_EXPORT(Q_QUICK_PRIVATE_EXPORT)
+Q_NAMESPACE_EXPORT(Q_QUICK_EXPORT)
QML_NAMED_ELEMENT(Font)
QML_ADDED_IN_VERSION(2, 0)
@@ -374,7 +415,7 @@ enum HintingPreference {
Q_ENUM_NS(HintingPreference)
};
-class Q_QUICK_PRIVATE_EXPORT QQuickFontValueType
+class Q_QUICK_EXPORT QQuickFontValueType
{
QFont v;
Q_GADGET
@@ -395,6 +436,10 @@ class Q_QUICK_PRIVATE_EXPORT QQuickFontValueType
Q_PROPERTY(QQuickFontEnums::HintingPreference hintingPreference READ hintingPreference WRITE setHintingPreference FINAL)
Q_PROPERTY(bool kerning READ kerning WRITE setKerning FINAL)
Q_PROPERTY(bool preferShaping READ preferShaping WRITE setPreferShaping FINAL)
+ Q_PROPERTY(QVariantMap features READ features WRITE setFeatures FINAL)
+ Q_PROPERTY(QVariantMap variableAxes READ variableAxes WRITE setVariableAxes FINAL)
+ Q_PROPERTY(bool contextFontMerging READ contextFontMerging WRITE setContextFontMerging FINAL)
+ Q_PROPERTY(bool preferTypoLineMetrics READ preferTypoLineMetrics WRITE setPreferTypoLineMetrics FINAL)
QML_VALUE_TYPE(font)
QML_FOREIGN(QFont)
@@ -405,6 +450,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickFontValueType
public:
static QVariant create(const QJSValue &value);
+ Q_INVOKABLE QQuickFontValueType() = default;
Q_INVOKABLE QString toString() const;
QString family() const;
@@ -454,11 +500,25 @@ public:
bool preferShaping() const;
void setPreferShaping(bool b);
+
+ QVariantMap features() const;
+ void setFeatures(const QVariantMap &features);
+
+ QVariantMap variableAxes() const;
+ void setVariableAxes(const QVariantMap &variableAxes);
+
+ bool contextFontMerging() const;
+ void setContextFontMerging(bool b);
+
+ bool preferTypoLineMetrics() const;
+ void setPreferTypoLineMetrics(bool b);
+
+ operator QFont() const { return v; }
};
namespace QQuickColorSpaceEnums
{
-Q_NAMESPACE_EXPORT(Q_QUICK_PRIVATE_EXPORT)
+Q_NAMESPACE_EXPORT(Q_QUICK_EXPORT)
QML_NAMED_ELEMENT(ColorSpace)
QML_ADDED_IN_VERSION(2, 15)
Q_CLASSINFO("RegisterEnumClassesUnscoped", "false")
@@ -491,7 +551,7 @@ enum class TransferFunction {
Q_ENUM_NS(TransferFunction)
}
-class Q_QUICK_PRIVATE_EXPORT QQuickColorSpaceValueType
+class Q_QUICK_EXPORT QQuickColorSpaceValueType
{
QColorSpace v;
Q_GADGET
@@ -518,6 +578,8 @@ public:
void setTransferFunction(QQuickColorSpaceEnums::TransferFunction transferFunction);
float gamma() const noexcept;
void setGamma(float gamma);
+
+ operator QColorSpace() const { return v; }
};
QT_END_NAMESPACE