Main UI: How to create curved text in a custom widget?

Hi, I recently started creating custom widgets by myself, and now I came to a point where I don’t know how to do it.

In this topic, OH3 Livio Energy Summary Animated I found out that you can create any HTML structure by using f7-rows with a tag. Now, my plan was to recreate the remote control of my TV as a custom widget that also includes curved text.

My idea was to do the curved text with a SVG like the linked widget was done, but here comes the problem.
With f7-rows, you can create any HTML-object with any properties, but I could not find out how to get actual text in there:

<svg>
    <text> How to get text here? </text>
</svg>

Do you know if and how this is possible?

Here is the code of my test widget. To get the text displayed, I used the onclick function for testing purposes. Of course, I would like to do this without any user interaction and have it displayed by default. (The onload function unfortunately does not work.)

uid: svg_test
tags: []
props: []
timestamp: Sep 2, 2022, 10:55:43 PM
component: f7-card
config: []
slots:
  default:
    - component: f7-row
      config:
        tag: svg
        viewBox: "0 0 100 100"
        width: 100%
        height: 100%
        onclick: document.getElementById('textsvg').textContent = 'HOME'
      slots:
        default:
          - component: f7-row
            config:
              d: M 10 50 a 50 50 0 0 1 80 0 
              fill: transparent
              id: myOwnTextPath
              tag: path
          - component: f7-row
            config:
              style:
                fill: white
              tag: text
              text-anchor: middle
              font-weight: bold
            slots:
              default:
                - component: f7-row
                  config:
                    id: textsvg
                    tag: textPath
                    xlink:href: "#myOwnTextPath"
                    startOffset: 50%

image

If you have another approach to creating curved text in a custom widget (Main UI) without using an actual image, I would love to know them.

Thanks in advance!

The only way I can think of off the top of my head to use strictly MainUI components to build curved text would be to use a repeater on a character array to produce and element with each character and use css to turn and place each element. It’s probably fewer lines of yaml and a
more “native” solution, but not necessarily a better solution. What you’ve got works pretty well.

At present I don’t think this is possible just with the available widgets. There are plenty of uses for such a feature within the f7 components and the community is building more and more advanced widgets, however, so it might be worth a pull request or feature request on github. I could envision something simple like the Label component only which doesn’t wrap the given text in a div element. Or even just a setting on the Label component that strips the div.

onload works within widgets, but not every element triggers an onload event. One of those, however is style which is otherwise completely innocuous, so you can use the same f7 tag technique to add an f7-row with a style tag and set onload.

1 Like

Thank you for the quick reply!

One of those, however is style which is otherwise completely innocuous, so you can use the same f7 tag technique to add an f7-row with a style tag and set onload.

I tried this out and it works perfectly. For anyone interested: Here is the working code with the style element inserted.

uid: svg_test
tags: []
props: []
timestamp: Sep 4, 2022, 9:27:07 AM
component: f7-card
config:
  title: '=(props.item) ? "State of " + props.item : "Set props to test!"'
slots:
  default:
    - component: f7-row
      config:
        tag: style
        onload: document.getElementById('textsvg').textContent = 'HOME'
    - component: f7-row
      config:
        tag: svg
        viewBox: 0 0 100 100
        width: 100%
        height: 100%
      slots:
        default:
          - component: f7-row
            config:
              d: M 10 50 a 50 50 0 0 1 80 0
              fill: transparent
              id: myOwnTextPath
              tag: path
          - component: f7-row
            config:
              style:
                fill: white
              tag: text
              text-anchor: middle
              font-weight: bold
            slots:
              default:
                - component: f7-row
                  config:
                    id: textsvg
                    tag: textPath
                    xlink:href: "#myOwnTextPath"
                    startOffset: 50%

The only way I can think of off the top of my head to use strictly MainUI components to build curved text would be to use a repeater on a character array to produce and element with each character and use css to turn and place each element.

Maybe I will try that out and if I manage to do it, the code will be shared here.

so it might be worth a pull request or feature request on github.

I have never used github yet, but this might be a reason to get started and test it out.

For anyone interested:
I just created the other solution JustinG came up with. It involves some math and is not as flexible as using svg elements. Nevertheless it was fun to create and it works:

uid: curved_text_css
tags: []
props:
  parameters:
    - description: The text to display
      label: text
      name: text
      required: false
      type: TEXT
    - default: "50"
      description: The radius of the curve
      label: radius
      name: radius
      required: true
      type: TEXT
    - default: "25"
      description: Distance between two letters
      label: letter distance
      name: letterDistance
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Sep 5, 2022, 10:00:20 PM
component: f7-card
config: {}
slots:
  default:
    - component: f7-block
      config:
        style:
          height: 100px
      slots:
        default:
          - component: oh-repeater
            config:
              for: letter
              fragment: true
              in: =props.text.split("")
              sourceType: array
            slots:
              default:
                - component: Label
                  config:
                    style:
                      width: =props.letterDistance + 'px'
                      text-align: center
                      left: =(1 + Math.sin((-(props.text.length-1)/2 + loop.letter_idx)*Math.asin(Number(props.letterDistance)/(2*Number(props.radius)))))*Number(props.radius) + 'px'
                      position: absolute
                      top: =(1 - Math.cos((-(props.text.length-1)/2 + loop.letter_idx)*Math.asin(Number(props.letterDistance)/(2*Number(props.radius)))))*Number(props.radius) + 'px'
                      transform: ='rotate(' + (-(props.text.length-1)/2 + loop.letter_idx)*Math.asin(Number(props.letterDistance)/(2*Number(props.radius))) + 'rad)'
                    text: =loop.letter

image

1 Like