Custom GUI Controls

Thanks to @logan and his post DirectSlider with progressBar I managed to have the effect I wanted.

The code still needs some cleaning up (for instance it doesn’t handle the horizontal vs. vertical case…), but I’m almost there… I post it below here in case it might be useful to someone else.

There are two points of improvements in particular that’s I’m struggling with:

  1. When I pass the reference to command to the ControlSlider options, I need to rely on a dictionary where I define in the container control (ControlPanel) and pass this together with the slider’s name as extraArgs to the showValue function. I couldn’t think of a more elegant way to realize this, clearly I can’t do something like slider = ControlSlider( ... command=command, extraArgs=slider ) in addSliderControl because the variable slider isn’t initialized yet. Can someone suggest any better solution?

  2. The .png images I’m using for the control have an Alpha channel with transparency to allow for rounded corners. Unfortunately I couldn’t find a way to make the ControlPanel background show trough it. Any suggestions?

from direct.gui.DirectGui import *
from panda3d.core import *
import direct.directbase.DirectStart
from panda3d.core import Vec4
from direct.gui.DirectGui import DGG,DirectButton,DirectSlider, OnscreenImage
from panda3d.core import TransparencyAttrib
	
class ControlPanel(DirectFrame):
	
    def __init__(self, application):
        self.application = application
        super().__init__(frameColor=(1, 0, 0, 1), pos=(1.5, 0, 0))
	
        background_cm = CardMaker("image_plane")
        background_cm.set_frame(-1, 1, -1, 1)
        self.background_plane = self.attach_new_node(background_cm.generate())
        self.background_plane.setColor((.85, .85, .85, 1))
	
        self.allSliders = {}
	
    def addSliderControl( self, image, pos_x, pos_y, scale, command, name ):
	
        slider = ControlSlider(
            image=image,
            pos=(pos_x,0,pos_y),
            scale=scale,
            value=50, 
            range=[0,100],
            command=command,
            extraArgs=[name, self.allSliders]
            )
	
        # self.attach_new_node(slider)
        slider.reparentTo(self)
        print(f"slider: {slider.getPos()}")
        
        self.allSliders[name] = slider
	
	
class ControlSlider(DirectSlider):
    
    def __init__(self, parent = None, **kw):
	
        optiondefs = (
            ('allowprogressBar', True, self.__progressBar),
            ('image', None, None)
            )
	
        background_texture = self.createImageTexture(kw['image'])
        image_width, image_height = self.getImageTextureDims(background_texture)
        image_aspect_ratio = image_height / image_width
        print(f"image_aspect_ratio = {image_aspect_ratio}")
        
        optiondefs += (
                ('frameSize',      (-1, 1, -.5, .5),   None),
                ('frameVisibleScale', (1, 1),         None),        
                ('image_scale', (1, 1, image_aspect_ratio), None),
                ('orientation', DGG.HORIZONTAL, None ),
                ('relief', DGG.FLAT, None ),
                ('progressBar_relief', DGG.FLAT, None ),
                ('progressBar_frameColor', (1, 1, 1, .5), None ),
                ('progressBar_frameTexture', 'models/textures/blue.PNG', None ),
                ('thumb_relief', DGG.FLAT, None ),
                ('thumb_frameColor', (1.0,1.0,1.0,0), None )
        )
	
        self.orientation = None
        self.progressBar = None
        self.defineoptions(kw, optiondefs)
	
        # Initialize superclasses
        DirectSlider.__init__(self, parent)
        
        self.progressBar = self.createcomponent("progressBar", (), None,
                                          DirectButton, (self,),
                                          borderWidth = self['borderWidth'],
                                          state=DGG.DISABLED,
                                          sortOrder=-1)
        
	
        # Call option initialization functions
        self.initialiseoptions(ControlSlider)
	
        self.setTransparency(TransparencyAttrib.MAlpha)
        self.progressBar.setTransparency(TransparencyAttrib.MAlpha)
        self.component('image0').setTransparency(TransparencyAttrib.MAlpha)
        
        
    def createImageTexture(self, image):
	
        tex = loader.load_texture(image)
        
        # Enable transparency on the texture
        tex.set_format(Texture.F_rgba)
        tex.set_wrap_u(Texture.WM_clamp)
        tex.set_wrap_v(Texture.WM_clamp)
        tex.set_minfilter(Texture.FT_linear_mipmap_linear)
        tex.set_magfilter(Texture.FT_linear)
	
        return tex
    
	
    def getImageTextureDims(self, image_tex):
        image_width = image_tex.get_x_size()
        image_height = image_tex.get_y_size()
	
        return image_width, image_height
    
    # see code at https://discourse.panda3d.org/t/directslider-with-progressbar/29126
    # for the following methods
	
    def __progressBar(self):
       ...
        
    def __updProgressBar(self):
       ...
    
    #override
    def setOrientation(self):
        ...
        
    #override
    def destroy(self):
        ...
	
    #override
    def commandFunc(self):
        ...
	
    #override
    def setFrameSize(self, fClearFrame = 0):
        ...
	
	

	
def showValue(name, sliders):
    print(f">>>{name}: {sliders[name]['value']}")
	
control_panel = ControlPanel(base)
control_panel.addSliderControl("../models/textures/ctrl_base_heading.png", 
                                            -0.35, 0.1, 0.5,
                                            command=showValue,
                                            name="base_heading"
                                            )
control_panel.addSliderControl("../models/textures/ctrl_1st_arm_heading.png", 
                                            -0.35, 0.65, 0.5,
                                            command=showValue,
                                            name="1st_arm_heading")
	
	
base.setBackgroundColor(0.8, 0.2, 0.2, 1.0)  # R, G, B, A
base.run()

image02