UICircularProgress

I made a control called “UICircularProgress” based on an old contribution by tobspr.

Here is the link to the contribution made by tobspr

This control is based on shaders and DirectFrame.
Here are some properties exposed for a simple configuration. I hope you find this contribution useful.

  • bgValue: 0 to 1
  • progressValue: 0 to 1
  • bgRadiusStart
  • progressRadiusStart
  • bgColor
  • progressColor

This is the control script:

__all__ = ['UICircularProgress']

import uuid
from direct.gui.DirectGui import DGG,DirectFrame
from panda3d.core import CardMaker, Shader, Vec4

class UICircularProgress(DirectFrame):
    def __init__(self, parent = None, **kw):
        self.bg = None
        self.progress = None
        optiondefs = ( 
            ('parent',              parent,                                         None),
            ('bgValue',             1.0,                                            self.__setBgValue),
            ('progressValue',       0.0,                                            self.__setProgressValue),
            ('bgRadiusStart',       .5,                                             self.__setBgRStart),
            ('progressRadiusStart', .5,                                             self.__setProgressRStart),
            ('bgColor',             Vec4(0.501960784,0.501960784,0.501960784, 1.0), self.__setBgColor),
            ('progressColor',       Vec4(0.5215686,0.7568627,0.9137254, 1.0),       self.__setProgressColor),
        )
        self.defineoptions(kw, optiondefs)
        DirectFrame.__init__(self, parent)
        if self["text"] is None:
            self["text"] = " "
        self["frameSize"] = Vec4(-1, 1, -1, 1)
        self.initialiseoptions(UICircularProgress)
        self.__load()
        color = Vec4(self["frameColor"])
        color[3] = 0
        self["frameColor"] = color
        text = self.component('text0')
        text.reparentTo(self)
        
    def __load(self):
        vertex = """
        #version 150
        uniform mat4 p3d_ModelViewProjectionMatrix;
        uniform mat4 trans_model_to_world;
        in vec4 p3d_Vertex;
        in vec2 p3d_MultiTexCoord0;
        out vec2 texcoord;
        void main() {
            gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex;
            texcoord = (p3d_Vertex).xz;
        }
        """

        fragment = """
        #version 150
        out vec4 color;
        in vec2 texcoord;
        uniform float radiusStart;
        uniform float radiusEnd;
        uniform vec4 circleColor;
        uniform float progress;

        const float PI = 3.14159265359;
        void main() {
            float radius = distance(texcoord, vec2(0));
            color = vec4(0);
            if (radius > radiusStart && radius < radiusEnd) {
                float angle = atan(texcoord.x, texcoord.y) / (2.0*PI);
                if (angle < 0.0) angle = 1.0 + angle;
                if (angle < progress) {
                    // Uncomment this to get a gradient
                    //color = vec4(angle*circleColor, 1);    
                    color = vec4(circleColor);    
                }
            }
        }
        """
        self.bg = UICircularProgressItem(self,vertex, fragment)
        self.bg.frame.setPos(self.getPos())
        self.progress = UICircularProgressItem(self,vertex, fragment)
        self.progress.frame.setPos(self.getPos())
        self.__setBgValue()
        self.__setProgressValue()
        self.__setBgRStart()
        self.__setProgressRStart()
        self.__setBgColor()
        self.__setProgressColor()

    def __setBgValue(self):
        self.__setValue(self.bg, "bgValue")

    def __setProgressValue(self):
        self.__setValue(self.progress, "progressValue")

    def __setBgRStart(self):
        self.__setRadius(1, self.bg, "bgRadiusStart")

    def __setProgressRStart(self):
        self.__setRadius(1, self.progress, "progressRadiusStart")

    def __setProgressColor(self):
        self.__setCircleColor(self.progress, "progressColor")

    def __setBgColor(self):
        self.__setCircleColor(self.bg, "bgColor")

    def __setCircleColor(self, ui, key):
        if ui is not None:
            ui.setColor(self[key])

    def __setValue(self, ui, key):
        if ui is not None:
            ui.setValue(self[key])

    def __setRadius(self, t, ui, key):
        if ui is not None:
            if t == 1:
                ui.setRadiusStart(self[key])
        
    def getProgressValue(self):
        return self.progress.getValue()

class UICircularProgressItem():
    def __init__(self, parent, vertex, fragment):
        self.progress = 1.0
        cm = CardMaker(str(uuid.uuid4()))
        cm.setFrame(-1, 1, -1, 1)
        self.frame = parent.attachNewNode(cm.generate())
        self.frame.setDepthTest(False)
        self.frame.setDepthWrite(False)
        self.frame.setShader(Shader.make(Shader.SLGLSL, vertex, fragment))
        self.frame.setShaderInput("circleColor", Vec4(1,1,1,1))
        self.frame.setShaderInput("progress", self.progress)
        self.frame.setShaderInput("radiusStart", .5)
        self.frame.setShaderInput("radiusEnd", .7)
        self.frame.setTransparency(True)

    def setColor(self, *kw):
        color = Vec4(1,1,1,1)
        if isinstance(kw, tuple) or isinstance(kw, Vec4):
            color = kw[0]
        else:
            color[0] = kw[0]
            color[1] = kw[1]
            color[2] = kw[2]
            color[3] = kw[3]
        self.frame.setShaderInput("circleColor", color)

    def setValue(self, value):
        if value < 0:
            value = 0
        elif value > 1.0:
            value = 1.0
        self.progress = value
        self.frame.setShaderInput("progress", self.progress)

    def setRadiusStart(self, value):
        self.frame.setShaderInput("radiusStart", value)

    def getValue(self):
        return self.progress


Here are some examples. The circular figures can be accessed directly and manipulated at your convenience.

from direct.showbase.ShowBase import ShowBase
from direct.task.Task import Task
from direct.gui.DirectGui import DGG,DirectLabel
from panda3d.core import Vec4, DepthTestAttrib, RenderAttrib
from UICircularProgress import UICircularProgress

class MyApp(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        self.node = aspect2d.attachNewNode("node")
        self.progress = 0
        self.isPlay = False
        self.ui = UICircularProgress(self.node,scale=0.3)
        self.ui["progressRadiusStart"] = 0
        self.ui["bgRadiusStart"] = 0
        self.ui.setX(-0.8)

        self.ui2 = UICircularProgress(self.node,
                                      scale=0.3,
                                      text="0",
                                      text_scale=0.4,
                                      text_pos=(0,0.-0.15),
                                      text_fg=(1,1,1,1),
                                      progressRadiusStart=.6, bgRadiusStart=.6)
        self.ui2.setX(-0.2)

        self.ui3 = UICircularProgress(self.node,
                                      text="0",
                                      scale=0.3,
                                      text_scale=0.3,
                                      text_pos=(0.05,0.-0.03),
                                      text_fg=(1,1,1,1),
                                      progressRadiusStart=.6, bgRadiusStart=0)
        self.ui3.setX(0.35)
        self.ui3.bg.frame.setScale(0.7)

        self.ui4 = UICircularProgress(self.node,
                                      relief=DGG.FLAT,
                                      text="||",
                                      scale=0.25,
                                      text_scale=(1, 0.5),
                                      text_pos=(0,-0.15),
                                      text_fg=(1,1,1,1),
                                      state=DGG.NORMAL,
                                      progressValue=0,
                                      progressColor=(0.050196078,0.331372549,0.772549019, 1),
                                      progressRadiusStart=.55, bgRadiusStart=0)
        self.ui4.setX(0.85)
        self.ui4.bind(DGG.B1PRESS, self.__ui4_event, extraArgs=["click"])
        self.ui4.bind(DGG.WITHIN, self.__ui4_event, extraArgs=["within"])
        self.ui4.bind(DGG.WITHOUT, self.__ui4_event, extraArgs=["without"])
        size = Vec4(self.ui4["frameSize"])
        self.ui4["frameSize"] = (-0.6,0.6,-0.6,0.6)
        self.ui4.progress.frame.setH(180)
        
        lbl = DirectLabel(self.node, scale=0.08,text="Button", text_fg=(1,1,1,1),pos=(0.85,0,0.25), frameColor=(1,1,1,0))

        taskMgr.doMethodLater(0.08, self.updTask, 'updTask')
     
    def __ui4_event(self, *args):
        color = Vec4(self.ui4["bgColor"])
        e = args[0]
        c1,c2 = 0.501960784, 0.601960784
        if e == "click":
            self.isPlay = not self.isPlay
            if not self.isPlay:
                self.ui4["text"] = "||"
                self.ui4["text_scale"] = (1, 0.5)
                self.ui4["text_pos"] =(0,-0.15)
                color[2] = c1
            else:
                self.ui4["text"] = f"{int(self.ui4.getProgressValue() * 100)}%"
                self.ui4["text_scale"] = 0.35
                self.ui4["text_pos"] =(0,-0.15)
                color[2] = c1
        elif e == "within":
            if not self.isPlay:
                color[2] = c2
        elif e == "without":
            if not self.isPlay:
                color[2] = c1
        self.ui4["bgColor"] = color
        
    def updTask(self, task):
        if self.progress > 1.0:
            self.progress = 0
        else:
            self.progress += 0.01
        if self.isPlay:
            if self.ui4.getProgressValue() >= 1.0:
                self.ui4["progressValue"] = 0.0
                messenger.send(DGG.B1PRESS + self.ui4.guiId)
            else:
                self.ui4["progressValue"] += 0.025
                self.ui4["text"] = f"{int(self.ui4.getProgressValue() * 100)}%"
        self.ui3["progressValue"] = self.ui2["progressValue"] = self.ui["progressValue"] = self.progress
        self.ui3["text"] = self.ui2["text"] = f"{int(self.progress * 100)}%"
        return task.again
    
MyApp()
base.run()

Regards!

7 Likes

That looks pretty cool! Thank you for contributing this! :slight_smile:

1 Like

Thank you @Thaumaturge

Regards!

1 Like

That looks indeed very cool, I would have resorted to some animated texture if you hadn’t made this. Thanks a lot! will try later!

1 Like