Nine-element DirectFrame?


I have a situation where I’d like to create a frame with a custom border. Normally, I’d do it by specifying a custom texture for the frame’s image. However, I’d like to be able to resize the frame without having to re-draw the custom texture (to cleanly support different window sizes).

I’m familiar with a trick in HTML where you can use tables to make a frame with custom image borders that is resizeable to account for different web browsers. I don’t know what that technique is called, but visually it looks like this:

| 1 |   2   | 3 |                                  
|   |       |   |
| 4 |   5   | 6 |
|   |       |   |
| 7 |   8   | 9 |

… where 1,3,7, and 9 are corner images, 2, 4, 6, and 8 are repeatable images for the edges (in Panda, I guess these would be textures on cards with the UVs scaled to wrap the image), and 5 is either a tilable image or solid color.

My question is: does Panda3D have a built in Direct widget to allow me to create a frame like this?


Sort of. We have an old system that we implemented before we had the whole DirectGui system, and then we’d stopped using it by the time we went to DirectGui–so it never got fully ported into the DirectGui interface. But you can still use it.

You need to paint a single texture divided into the nine squares you describe. You can decide how much texture space you need for the margins, but all the margins have to be the same distance in from the edges. Let’s say you decide to paint a 256 x 256 texture, and you want to reserve 16 pixels around each edge for this margin. Your texture will look like the picture you have drawn.

Then you need to use TextNode to generate the card geometry. You will do something like this:

tex = loader.loadTexture('my-texture.png')
uvMargin = 16./256.  # the amount of space reserved in the texture
screenMargin = 1     # the size of the margins onscreen

frame = DirectFrame(text = 'This is a GUI item with\na couple of lines of text',
                    text_fg = (0, 0, 0, 1),
                    pad = (screenMargin, screenMargin),
                    scale = 0.1,
                    relief = None)

bounds = frame.getBounds()

tn = TextNode('card')
tn.setText(' ')  # a space
tn.setCardBorder(screenMargin, uvMargin)

frame['geom'] = NodePath(tn.generate())

The key here is that we are using a TextNode to generate the background card, but not the text. Thus, we don’t put any text in the TextNode, but we set its card to be the same size as our GuiItem’s bounding rectangle. We also use TextNode.setCardBorder(), which is the magic that turns on this funny mode where it generates a card that is made up of 9 quads, instead of just 1 quad, and the UV’s are carefully chosen to stretch the texture over the whole card, paying attention to stretching the margins in the expected way.

Since this is not (yet) tightly integrated with the DirectGui system, if your DirectFrame changes properties–for instance, you change the text–you will have to re-do the TextNode bit by hand.


Wonderful! That’s exactly what we need.