DirectGUI and Component-Keywords

In working on a custom DirectGUI sub-class, I discovered something: it seems that DirectGUI discards component-related constructor-keywords–that is, keywords that contain an underscore–after initialisation. They’re used for any construction done during that period, but appear to be lost afterwards.

As a result, components constructed after initialisation aren’t provided any such arguments, and have only the default styling.

I bumped into this previously, I think, when dealing with DirectOptionMenu–I found that if its item-list was changed after construction, the resultant popup-menu lost any stylisation applied via constructor-keywords.

However, I hesitate to post this to the GitHub issue-tracker, for two reasons: First, I may well be missing something here; I don’t fully understand what DirectGUI does behind the scenes, I fear. And second, it I am correct, it seems possible that this issue is sufficiently fundamental that it wouldn’t be feasible to fix it.

Thus I want to first open the matter for discussion here!

A quick example-program that shows the problem (on my machine, at least):

from direct.task import Task

from direct.showbase import ShowBase as showBase

from direct.gui.DirectGui import DirectFrame, DirectButton

class Mew(DirectFrame):
    def __init__(self, parent = None, **kwargs):
        optiondefs = (
            ('frameSize',      (-0.5, 0.5, -0.5, 0.5), None),

        self.defineoptions(kwargs, optiondefs)

        DirectFrame.__init__(self, parent)

        self.buttonCounter = 0


    def addButton(self):
        btn = self.createcomponent("Mew%d" % self.buttonCounter, (), "mewButton",
                                   text = "Mew",
                                   pos = (0, 0, 0.4 - self.buttonCounter*0.1),
                                   scale = 0.1)
        self.buttonCounter += 1

class Game(showBase.ShowBase):

    def __init__(self):

        self.frame = Mew(mewButton_frameColor = (0, 1, 0, 1))

        self.accept("escape", self.userExit)
        self.accept("space", self.frame.addButton)

app = Game()

Note that the first call to “addButton”–done in the constructor, before “initialiseoptions”–produces a button with the specified green “frameColor”, but that later calls produce buttons of the default grey.

It is a bit strange behavior but it is intended. According to the description in DirectGuiBase:

If a keyword (or substituted alias keyword) is used during creation of the
component, it is deleted from self._constructorKeywords.  If a group
keyword applies to the component, that keyword is marked as used, but is
not deleted from self._constructorKeywords, in case it applies to another
component.  If any constructor keywords remain at the end of component
construction (and initialisation), an error is raised.

The self._constructorKeywords holds all the options given to the component at construction time (the mewButton_frameColor in your example). Later on the mewButton_frameColor is used in the createcomponent function and hence gets “marked” as to be deleted from the constructor keywords list, which will then cause the resulting behavior of not using this keyword when creating another button.

I also don’t know of any DGui element that creates multiple components with the same name. Usually every component has a distinct name and will either be set at construction time or specifically by the user afterward. There also are AFAIK no other elements yet that create components at runtime, so this might be a special case where you may better change the logic behind the creation of new buttons.

Interesting! Thank you for that! :slight_smile:

It does still seem like very counter-intuitive behaviour, to me.

As I said, DirectOptionMenu can do this, I believe: if the user changes the item-list after construction, the popup-menu is rebuilt. And since there are no more keywords, any stylisation is lost.

(I don’t know offhand whether there are any other controls with similar behaviour; I imagine not, however.)

I have a pull-request in for DirectOptionMenu that stores the “item_” keywords for potential later use (and altered “setItems” to make use of them), and did similarly for the control that I started the original post with mention of, if I recall correctly.

So perhaps it’s not worth changing, then. That said, I could see other developers bumping into the same issue in making their own sub-classes, and it’s not a terribly obvious problem to find the source of, I feel.