📜 ⬆️ ⬇️

Model-View in QML. Part Two: Custom Submissions

Not always ready representations ideally approach. Consider the components that allow you to create a fully customized view and achieve great flexibility in building the interface. And from me a small bonus for patient readers :)

Model-View in QML:
  1. Model-View in QML. Part zero, introductory
  2. Model-View in QML. Part One: Predefined Component Views
  3. Model-View in QML. Part Two: Custom Submissions
  4. Model-View in QML. Part Three: Models in QML and JavaScript
  5. Model-View in QML. Part Four: C ++ Models


1. PathView

This component partly belongs to the same group of ready-made mappings, but the location of elements is completely controlled by the user. This is achieved by the fact that the component allows you to arrange elements along a certain path, which is composed of straight lines, curves and arcs. The path may be closed, but it is not necessary. Throughout the way, we can also control the properties of the elements. For example, you can make it so that in a certain place the element becomes larger and it seems that it is closer to the user. In addition to this, those elements that should appear to be further away from the user can be made translucent. In general, the possibilities for customizing the appearance of the elements are many.

The main purpose of PathView is not just to display data, but also to make it look visually appealing. It is with this component that things like CoverFlow are done (a popular way to display album covers in multimedia players).
')
1) simple example

Let's start with a simple example, placing the elements along the curve (to be perfectly accurate, we use the quadratic Bezier curve )

import QtQuick 2.0 Rectangle { width: 500 height: 200 PathView { id: view anchors.fill: parent model: 30 path: Path { startX: 0 startY: 0 PathQuad { x: view.width y: 0 controlX: view.width / 2 controlY: view.height } } delegate: Rectangle { width: 20 height: 20 color: "orchid" border { color: "black" width: 1 } } } } 

The path is described with the help of an object of the Path type, into which we place objects describing parts of this path. In our case, the path consists of a single section in the form of a curve.

The parameters startX and startY describe the coordinates of the start of the path. It ends where its last section ends. In the section, on the contrary, not the beginning, but the end is specified, using the parameters x and y. In our case, the curve is built on three points: in addition to the end and the beginning, we need another coordinate, on which it depends on what the bend will be. For its coordinates are the properties controlX and controlY. For a part of the way, coordinates are specified relative to the parent Path object. There are special properties that allow you to set the coordinates relative to the beginning of the path. Such properties have the prefix relative (for example, relativeControlY).

Let's see what we got:



Since all elements are placed in the PathView, we can drag the elements with the mouse and will move along the path.

2) closed path

In the previous example, the path is unlocked. After an element reaches its path, it appears at the beginning. Not much harder to make the path closed. For this, it is necessary that the coordinates of its beginning and end coincide. The Path object even has a special property closed, which reflects whether it is closed.

Let's slightly redo the first example and make a closed path of two Bezier curves (PathQuad):

 import QtQuick 2.0 Rectangle { width: 400 height: 400 PathView { id: view anchors.fill: parent model: 50 path: Path { startX: view.width / 2 startY: view.height / 2 PathQuad { relativeX: 0 y: view.height controlX: view.width controlY: 0 } PathQuad { relativeX: 0 y: view.height / 2 controlX: 0 controlY: 0 } } delegate: Rectangle { width: 20 height: 20 color: "hotpink" border { color: "black" width: 1 } } } } 

Each element of the path begins where the previous one ends. The last segment we end where the whole path begins.
As a result, we get the following figure:



3) elements of the path

In addition to the curve considered, QtQuick has cubic Bezier curves - the same as the quadratic, but with two control points (PathCubic), a curve with an arbitrary number of points (PathCurve), an arc — that is, part of a circle (PathArk) and a straight line (PathLine). In addition, you can specify a curve with a description in SVG format using the PathSvg component. All these components can be combined to make the right path.

There are additional components that do not control the placement of elements and their parameters. One of them is PathPercent, which allows you to control the distribution of elements along sections of the path. By default, the elements are distributed evenly, but with the help of this component it is possible to specify for parts of the path which part of the elements the number will be located there. To do this, a PathPercent object is placed after the path section, the value parameter of which contains a part of the elements for this path (for example, 0.5 for half of the elements).

Consider this with an example:

 import QtQuick 2.0 Rectangle { width: 500 height: 200 PathView { id: view anchors.fill: parent model: 20 path: Path { startX: 0 startY: height PathCurve { x: view.width / 5 y: view.height / 2 } PathCurve { x: view.width / 5 * 2 y: view.height / 4 } PathPercent { value: 0.49 } PathLine { x: view.width / 5 * 3 y: view.height / 4 } PathPercent { value: 0.51 } PathCurve { x: view.width / 5 * 4 y: view.height / 2 } PathCurve { x: view.width y: view.height } PathPercent { value: 1 } } delegate: Rectangle { width: 20 height: 20 color: "orchid" border { color: "black" width: 1 } } } } 

We make a path of two arcs and one straight line in the middle. At the same time we are doing so that the elements concentrate on the extreme parts of the path. And it turns out that the central area contains only one element:



Another additional element of the path is the PathAttribute, which allows you to control the parameters of elements, depending on their location on the path. In the delegate, these parameters will be available through the attached PathView.name properties, where the name is set using the name property.

PathAttribute sets the parameter values ​​at the point in the path where it is located. The values ​​of the parameters of the elements in the section of the path that is between two PathAttribute objects will smoothly move from the values ​​of one PathAttribute to another. If on one side there is no this object, then zero values ​​will be taken.

Let's make it so that the central object is twice as large and that the extreme elements are translucent:
 import QtQuick 2.0 Rectangle { property int itemSize: 20 width: 500 height: 200 PathView { id: view anchors.fill: parent model: 20 path: Path { startX: 0 startY: height PathAttribute { name: "size"; value: itemSize } PathAttribute { name: "opacity"; value: 0.5 } PathCurve { x: view.width / 5 y: view.height / 2 } PathCurve { x: view.width / 5 * 2 y: view.height / 4 } PathPercent { value: 0.49 } PathAttribute { name: "size"; value: itemSize * 2 } PathAttribute { name: "opacity"; value: 1 } PathLine { x: view.width / 5 * 3 y: view.height / 4 } PathAttribute { name: "size"; value: itemSize * 2 } PathAttribute { name: "opacity"; value: 1 } PathPercent { value: 0.51 } PathCurve { x: view.width / 5 * 4 y: view.height / 2 } PathCurve { x: view.width y: view.height } PathPercent { value: 1 } PathAttribute { name: "size"; value: itemSize } PathAttribute { name: "opacity"; value: 0.5 } } delegate: Rectangle { width: PathView.size height: PathView.size color: "orchid" opacity: PathView.opacity border { color: "black" width: 1 } } } } 

And we get the elements in which the size and transparency are smoothly changed:



If you do not need a smooth transition, but you just need to make different sized elements, you can surround each such section of the path with PathView objects, and add additional sections of zero-length path between adjacent sections, where the transition from one parameter value to another will take place. But due to the zero size, there are no elements there and this will not be seen.

To demonstrate, we will slightly modify the previous example and make so that each of the three parts of the path is surrounded by PathAttribute objects, and between these parts we place PathLine objects of zero length.

 import QtQuick 2.0 Rectangle { property int itemSize: 20 width: 500 height: 200 PathView { id: view anchors.fill: parent model: 20 path: Path { startX: 0 startY: height PathAttribute { name: "size"; value: itemSize } PathAttribute { name: "opacity"; value: 0.5 } PathCurve { x: view.width / 5 y: view.height / 2 } PathCurve { x: view.width / 5 * 2 y: view.height / 4 } PathAttribute { name: "size"; value: itemSize } PathAttribute { name: "opacity"; value: 0.5 } PathPercent { value: 0.49 } PathLine { relativeX: 0; relativeY: 0 } //  PathAttribute { name: "size"; value: itemSize * 2 } PathAttribute { name: "opacity"; value: 1 } PathLine { x: view.width / 5 * 3 y: view.height / 4 } PathAttribute { name: "size"; value: itemSize * 2 } PathAttribute { name: "opacity"; value: 1 } PathPercent { value: 0.51 } PathLine { relativeX: 0; relativeY: 0 } //  PathAttribute { name: "size"; value: itemSize } PathAttribute { name: "opacity"; value: 0.5 } PathCurve { x: view.width / 5 * 4 y: view.height / 2 } PathCurve { x: view.width y: view.height } PathPercent { value: 1 } PathAttribute { name: "size"; value: itemSize } PathAttribute { name: "opacity"; value: 0.5 } } delegate: Rectangle { width: PathView.size height: PathView.size color: "orchid" opacity: PathView.opacity border { color: "black" width: 1 } } } } 

As a result, we obtain small translucent elements along the edges and one large and opaque element in the center:



4) CoverFlow

At the beginning of the section, I mentioned CoverFlow. As a bonus to those who read up to this point, a small example of the implementation :)

 import QtQuick 2.0 Rectangle { property int itemAngle: 60 property int itemSize: 300 width: 1200 height: 400 ListModel { id: dataModel ListElement { color: "orange" text: "first" } ListElement { color: "lightgreen" text: "second" } ListElement { color: "orchid" text: "third" } ListElement { color: "tomato" text: "fourth" } ListElement { color: "skyblue" text: "fifth" } ListElement { color: "hotpink" text: "sixth" } ListElement { color: "darkseagreen" text: "seventh" } } PathView { id: view anchors.fill: parent model: dataModel pathItemCount: 6 path: Path { startX: 0 startY: height / 2 PathPercent { value: 0.0 } PathAttribute { name: "z"; value: 0 } PathAttribute { name: "angle"; value: itemAngle } PathAttribute { name: "origin"; value: 0 } PathLine { x: (view.width - itemSize) / 2 y: view.height / 2 } PathAttribute { name: "angle"; value: itemAngle } PathAttribute { name: "origin"; value: 0 } PathPercent { value: 0.49 } PathAttribute { name: "z"; value: 10 } PathLine { relativeX: 0; relativeY: 0 } PathAttribute { name: "angle"; value: 0 } PathLine { x: (view.width - itemSize) / 2 + itemSize y: view.height / 2 } PathAttribute { name: "angle"; value: 0 } PathPercent { value: 0.51 } PathLine { relativeX: 0; relativeY: 0 } PathAttribute { name: "z"; value: 10 } PathAttribute { name: "angle"; value: -itemAngle } PathAttribute { name: "origin"; value: itemSize } PathLine { x: view.width y: view.height / 2 } PathPercent { value: 1 } PathAttribute { name: "z"; value: 0 } PathAttribute { name: "angle"; value: -itemAngle } PathAttribute { name: "origin"; value: itemSize } } delegate: Rectangle { property real rotationAngle: PathView.angle property real rotationOrigin: PathView.origin width: itemSize height: width z: PathView.z color: model.color border { color: "black" width: 1 } transform: Rotation { axis { x: 0; y: 1; z: 0 } angle: rotationAngle origin.x: rotationOrigin } Text { anchors.centerIn: parent font.pointSize: 32 text: model.text } } } } 

For a start, let's see the resulting result, and then analyze the implementation. And we got something like this:



All elements, except the central one, are rotated around the Y axis. To do this, we assign delegates the transformation of rotation using the Rotation component. In the axis property you need to set 1 for those axes around which the object will rotate.
For elements, we change several parameters: the angle of rotation, the location along the Z axis, and the point of rotation (origin). From an angle, everything is simple and obvious: the elements that are on the left rotate by 60 degrees, and those on the right, respectively, by -60. But the rest of the parameters should stay in more detail.

The Z coordinate determines which element will be “higher”, i.e. when two objects in some place intersect, the object at which less Z will be blocked by an object at which Z coordinate is greater. By default, in PathView, an item with a large index overlaps the previous one. In CoverFlow for the elements on the left you need to be the opposite: “above” are those elements that are closer to the center. If nothing is done, the last element will be added to the last but one, and that in turn will be placed on the element in front of it, etc. Therefore, we change the Z coordinate so that the further the element is from the center, the “lower” it is. In our example, the dimensions are such that the elements do not overlap, but if you slightly reduce the width of the window, the overlap will immediately appear:



Finally, the turning point. We set a point on our rectangle around which the rotation will take place. By default, this is the upper left corner, i.e. point with coordinates (0, 0). Since we rotate an element around the Y axis, then the Y coordinate itself does not matter here. But on X pay attention. In the case of elements located on the left side, we set these coordinates to 0 and rotate the element around the left edge and it turns out that the right edge visually gets farther. If we do the same for the elements on the right, then it turns out that the elements on the left we turn "from ourselves", and the elements on the right - "to ourselves", i.e. the left edge will be close, and the right will be even closer and the right side will be larger. As a result, we get such a situation that the elements on the left and right will be of different sizes, which we do not need at all. We all turn the elements “from ourselves” and for this purpose in the elements to the right we shift the turning point to the upper right corner so that they rotate around their right edge.

In previous examples, the PathView displayed all the elements from the model. The number of items that can be displayed simultaneously can be limited using the pathItemCount parameter. Here I set it equal to six.

Summarizing, we can say that using QML, such a popular way of presenting data as CoverFlow, is quite simply implemented using elements from the standard library.

Short summary

PathView is a component focused primarily on creating an attractive interface. This tool has great flexibility, allowing you to place elements not only in a straight line, but also along an arbitrary path, as well as change the delegate’s parameters depending on which part of the path it is in.

2. Its presentation

QML gives us the tools to make our presentation if we have such a need. This is not very difficult and is realized by combining simple elements.

First we need to create delegate objects for each element of the model. For this we use a special component - Repeater. He deals exclusively with the creation of elements, no positioning, etc. things he does not do. It is used in the same way as the components of * View: we give it a model and a delegate and it will create delegate instances for each element of the model.

For positioning, we can apply the Row and Column elements in which we place our Repeater. Elements created with Repeater become the children of its parent, i.e. in this case, Row or Column, which position their elements as a row or column, respectively.

There is only the task of navigation. If there are so many elements that all of them do not fit into the space allotted to them, we need to implement scrolling of elements. It is done using the Flickable component, which processes the mouse wheel and gestures of the touch screen or the same mouse and scrolls the elements.

For example, we will make the elements arranged not vertically, but horizontally:

 import QtQuick 2.0 Rectangle { width: 360 height: 360 ListModel { id: dataModel ListElement { color: "orange" text: "first" } ListElement { color: "lightgreen" text: "second" } ListElement { color: "orchid" text: "third" } ListElement { color: "tomato" text: "fourth" } } Flickable { anchors.fill: parent contentWidth: row.width Row { id: row height: parent.height Repeater { model: dataModel delegate: Item { height: parent.height width: 100 Rectangle { anchors.margins: 5 anchors.fill: parent color: model.color border { color: "black" width: 1 } Text { anchors.centerIn: parent renderType: Text.NativeRendering text: model.text } } } } } } } 

We set the height of the Row element to be fixed, and the width will automatically change, depending on the total width of its children. At Flickable, we set the contentWidth - this is, as you might guess, the width of its content. If it is larger than the width of the Flickable itself, it will enable them to scroll. In our example, the last element just does not hold and you can make sure that the scrolling works.



As you can see, the QtQuick library allows you to do without the use of ready-made views and create your own from simple components, which will also work well.

findings

Standard components allow realizing representations of the most diverse types: from tables to elements arranged along an arbitrary path. In addition to ready-made views, you can create your own completely from the basic components.

PathView is designed to create a display focused on a beautiful appearance and animation and allows you to set the trajectory of the elements, vary the parameters of the element, depending on its location and the density of the elements in different parts of the path.

Source: https://habr.com/ru/post/190090/


All Articles