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!