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.

    alt text

    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)

    alt text

    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's wrapMode is set to TextEdit.WordWrap)
    • Control your [rectangle]'s x,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....

    alt text

    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
          }
        }
      }
    }
    

Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.