Unsolved Build advanced text element, embedding QML elements into text
-
Hello,
for my app, it would be nice to have an advanced text element which allows to interleave text and QML components. These two screenshots come from the Telegram app (https://github.com/telegramdesktop/tdesktop) which apparently uses Qt (I think QWidgets or something entirely custom). However I could not find the particular place in the code where the chat is designed.
As it seems, you can also select text with the mouse, including the emoji (which my system does not display by default, since I don't have a Mac)
The quoted message, in this example, cannot be selected.
Now, I would like to know whether you can implement such functionality in QML. I am aware that you can visually mimic this by using a WebView and implementing everything in HTML, but this is not a satisfying solution, as I want to include Buttons, MouseAreas or other elements QML has to offer, between words.In other words: I would like to have something like a Flow layout, but with the ability to separate Text elements if necessary and to allow user selections (and determine the exact text which is then copied to clipboard).
Example: Consider a Text element with content "Hello world!", followed by a Rectangle with some certain size, followed by another Text element with content "Hello world".
Now, assume that these elements are positioned in a container which has just enough width to show the first "Hello world", the rectangle and the "Hello" from the second Text element in a line.
When using a Flow positioner, the result would look like this:Hello World! [rectangle]
Hello World!because it cannot cut a Text element in half. However, I want it to look like this:
Hello World! [rectanlge] Hello
World!Of course, I could just split the wrapped text element into "Hello" and "World!" in this case, but it should work with variable dynamic text and container sizes and there would still no user selection be possible.
just like a sequence of inline-block elements in HTML.
I believe this is not possible with built-in QML elements only, so does somebody know an implementation of such a structure or another QML/Qt way of implementing it with the same visual and behavioral result?
Thanks and best regards,
Alexej
-
@alexej.k Interesting problem. My thought to hack a solution:
- Put somrthing like "Hello World! XXXXXXXX Hello World!" in a QML TextEdit or TextArea element (lots of properties to control cut/paste/edit-ability); the XXX is just a placeholder.
- Use that element's
rectangle positionToRectangle(position)
method to query the pixel extent of each of the XXX placeholder characters; keep track of the extremes of the range to determine your[rectangle]
position (make sure the TextEdit'swrapMode
is set toTextEdit.WordWrap
) - Control your
[rectangle]
'sx,y,width & height
to cover up the XXXXXXXX.
If anything is supposed to happen when the user selects across your overlaid
[rectangle]
you'd need to keep watch on the TextEdit's selectionStart, selectionEnd and/or selectedText for the XXXXXXXX being selected and update the appearance of your[rectangle]
accordingly. -
Just for fun: Not quite the same thing but here's some silliness with overlaying particle system instances which are triggered by a particular string in a textedit area....
Can be run with qmlscene.... this main.qml:
import QtQuick 2.7 Rectangle { id: main width: 640 height: 480 Column { MyTextEdit { width: main.width height: main.height/2 text: 'This is an editable text area.' } MyTextEdit { width: main.width height: main.height/2 text: 'This is another editable text area.' } } }
and this accompanying MyTextEdit.qml
import QtQuick 2.7 import QtQuick.Particles 2.0 TextEdit { id: textedit selectByMouse: true font.pixelSize: 16 wrapMode: TextEdit.WordWrap property var current: {} property var fizz: {} onTextChanged: { var fresh={} for (var i=0;i<length-1;i++) { if (text[i]=='Q' && text[i+1]=='t') { fresh[i]=null } } for (var i in fresh) { if (!(i in current)) { createFizz(i) } } for (var i in current) { if (!(i in fresh)) { destroyFizz(i) } } current=fresh } function createFizz(i) { var r0=positionToRectangle(+i) var r1=positionToRectangle(+i+1) var it=fizzcomponent.createObject( textedit, {'x':r0.x, 'y':r0.y, 'width':2*(r1.x-r0.x), 'height':r0.height} ) var updated={} for (var k in fizz) updated[k]=fizz[k] updated[i]=it fizz=updated } function destroyFizz(i) { fizz[i].destroy() var updated={} for (var k in fizz) if (k!=i) updated[k]=fizz[k] fizz=updated } Component { id: fizzcomponent ParticleSystem { ItemParticle { delegate: Rectangle {color: '#ff0000';width:5;height:5;radius:2;opacity: 0.5} fade: false } Wander { pace: 100 affectedParameter: Wander.Velocity xVariance: 50 yVariance: 50 } Gravity { angle: -60 magnitude: 25 } Emitter { enabled: true emitRate: 50 anchors.fill: parent maximumEmitted: 1000 lifeSpan: 5000 lifeSpanVariation: 4000 } } } }