Alright, I made a DirectColorPicker class that wraps several DirectGui objects into a rather basic - but fully functional - color picker object. You can create one by clicking any of the 3 “Color me” buttons (only if there isn’t one already for that button). You can drag it around using the left mouse button on an empty space on the color picker’s frame. Click the color spectrum to select the color at the clicked pixel. Use the slider to change the color’s brightness. Click the OK button to apply the selected color to the corresponding “Color me” button.
Here’s the code:
from panda3d.core import *
from direct.showbase.ShowBase import ShowBase
from direct.gui.DirectGui import *
class ColorSpectrum(object):
def __init__(self, app, click_handler, **kwargs):
self._app = app
self._frame = DirectFrame(**kwargs)
self._marker = DirectFrame(parent=self._frame,
frameColor=(0., 0., 0., 1.),
frameSize=(-.06, .06, -.06, .06),
pos=(-.89, 0., -.89))
self._marker_center = DirectFrame(parent=self._marker,
frameColor=(.5, .5, .5, 1.),
frameSize=(-.02, .02, -.02, .02))
texture_filename = self._frame['image']
self._palette_img = img = PNMImage(Filename.fromOsSpecific(texture_filename))
width = img.getReadXSize()
height = img.getReadYSize()
self._palette_size = (width, height)
self._frame['state'] = DGG.NORMAL
self._frame.bind(DGG.B1PRESS, command=click_handler)
def getColorUnderMouse(self, update_marker=False):
if not self._app.mouseWatcherNode.hasMouse():
return
x, y = self._app.mouseWatcherNode.getMouse()
win_w, win_h = self._app.win.getSize()
width, height = self._palette_size
if win_w < win_h:
y *= 1. * win_h / win_w
else:
x *= 1. * win_w / win_h
screen = self._app.aspect2d
x -= self._frame.getX(screen)
y -= self._frame.getZ(screen)
img_scale = self._frame['image_scale']
sx = self._frame.getSx(screen) * img_scale[0]
sy = self._frame.getSz(screen) * img_scale[2]
marker_x = max(-.89, min(.89, x / sx))
marker_y = max(-.89, min(.89, y / sy))
x = (.5 + x / (2. * sx)) * width
y = (.5 + y / -(2. * sy)) * height
if 0 <= x < width and 0 <= y < height:
r, g, b = color = self._palette_img.getXel(int(x), int(y))
if update_marker:
self._marker_center['frameColor'] = (r, g, b, 1.)
self._marker.setPos(marker_x, 0., marker_y)
return color
def setMarkerCoord(self, coord):
x, y = coord
marker_x = max(-.89, min(.89, x))
marker_y = max(-.89, min(.89, y))
self._marker.setPos(marker_x, 0., marker_y)
def getMarkerCoord(self):
marker_x, y, marker_y = self._marker.getPos()
return (marker_x, marker_y)
def setMarkerColor(self, color):
r, g, b = color
self._marker_center['frameColor'] = (r, g, b, 1.)
class ColorSwatch(object):
def __init__(self, parent, color=None):
self._frame = DirectFrame(parent=parent,
relief=DGG.SUNKEN,
borderWidth=(.05, .05),
frameColor=(.3, .3, .3, 1.),
frameSize=(-.4, .4, -.3, .3),
scale=(.5, 1., .5))
self._swatch = DirectFrame(parent=self._frame,
frameColor=color if color else (.5, .5, .5, 1.),
frameSize=(-.35, .35, -.25, .25))
self._color = color
def setColor(self, color=None):
self._swatch['frameColor'] = color if color else (.5, .5, .5, 1.)
self._color = color
def getColor(self):
return self._color
def setPos(self, *pos):
self._frame.setPos(*pos)
class ColorData(object):
def __init__(self, color=None, brightness=None, spectrum_coord=None):
self._color = color if color else Vec3(.5, .5, .5)
self._brightness = .5 if brightness is None else brightness
self._coord = spectrum_coord if spectrum_coord else (-1., -1.)
def copy(self):
color_data = ColorData(self._color, self._brightness, self._coord)
return color_data
def setColor(self, color):
self._color = color
def getColor(self):
return self._color
def setBrightness(self, brightness):
self._brightness = brightness
def getBrightness(self):
return self._brightness
def getColorShade(self):
if self._brightness < .5:
color = self._color
else:
color = Vec3(1., 1., 1.) - self._color
color = color * 2. * (self._brightness - .5)
color += self._color
r, g, b = color
return VBase4(r, g, b, 1.)
def setCoord(self, coord):
self._coord = coord
def getCoord(self):
return self._coord
class DirectColorPicker(object):
def __init__(self, app, picker_command, on_destroy, selected_color=None, **kwargs):
if 'frameSize' not in kwargs:
kwargs['frameSize'] = (-.8, .8, -1., 1.)
self._app = app
self._on_destroy = on_destroy
self._frame = DirectFrame(**kwargs)
self._frame['state'] = DGG.NORMAL
self._frame.bind(DGG.B1PRESS, command=self.__startDrag)
self._frame.bind(DGG.B1RELEASE, command=self.__stopDrag)
spectrum_filename = 'color_spectrum.png'
self._spectrum = ColorSpectrum(app,
self.__handleColorSelection,
parent=self._frame,
relief=DGG.SUNKEN,
borderWidth=(.05, .05),
image=spectrum_filename,
image_scale=(.95, 1., .95),
frameColor=(.3, .3, .3, 1.),
frameSize=(-1., 1., -1., 1.),
pos=(-.15, 0., .3),
scale=(.5, 1., .5))
self._selected_color = selected_color if selected_color else ColorData()
self._spectrum.setMarkerCoord(self._selected_color.getCoord())
self._spectrum.setMarkerColor(self._selected_color.getColor())
def updateBrightness():
self._selected_color.setBrightness(self._slider['value'])
self._swatch_selected.setColor(self._selected_color.getColorShade())
brightness_filename = 'brightness.png'
brightness = self._selected_color.getBrightness()
self._slider = DirectSlider(parent=self._frame,
orientation=DGG.VERTICAL,
relief=DGG.SUNKEN,
borderWidth=(.05, .05),
image=brightness_filename,
image_scale=(.05, 1., .95),
frameColor=(.3, .3, .3, 1.),
frameSize=(-.4, .4, -1., 1.),
thumb_frameSize=(-.2, .2, -.1, .1),
command=updateBrightness,
value=brightness,
pos=(.55, 0., .3),
scale=(.5, 1., .5))
self._swatch_selected = ColorSwatch(self._frame)
self._swatch_selected.setPos(-.45, 0., -.4)
self._swatch_current = ColorSwatch(self._frame)
self._swatch_current.setPos(.15, 0., -.4)
self._task = app.taskMgr.add(self.__showColorUnderMouse, 'show_color')
self._drag_start = Point2() # used when dragging the color picker frame
self._drag_offset = Vec2() # used when dragging the color picker frame
def onOK():
self.destroy()
picker_command(self._selected_color)
self._btn_ok = DirectButton(parent=self._frame,
borderWidth=(.05, .05),
frameColor=(.35, .35, .35, 1.),
frameSize=(-.3, .3, -.15, .15),
command=onOK,
text='OK',
text_pos=(0., -.03),
text_scale=(.12, .12),
pos=(-.35, 0., -.75))
self._btn_cancel = DirectButton(parent=self._frame,
borderWidth=(.05, .05),
frameColor=(.35, .35, .35, 1.),
frameSize=(-.3, .3, -.15, .15),
command=self.destroy,
text='Cancel',
text_pos=(0., -.03),
text_scale=(.12, .12),
pos=(.35, 0., -.75))
def destroy(self):
self._task.remove()
del self._task
self._frame.destroy()
del self._frame
self._on_destroy()
def __handleColorSelection(self, *args):
color = self._spectrum.getColorUnderMouse(update_marker=True)
if color:
coord = self._spectrum.getMarkerCoord()
self._selected_color.setCoord(coord)
self._selected_color.setColor(color)
self._swatch_selected.setColor(self._selected_color.getColorShade())
def __showColorUnderMouse(self, task):
color = self._spectrum.getColorUnderMouse()
if color:
r, g, b = color
self._swatch_current.setColor(VBase4(r, g, b, 1.))
return task.cont
def __drag(self, task):
if not self._app.mouseWatcherNode.hasMouse():
return task.cont
x, y = self._app.mouseWatcherNode.getMouse()
win_w, win_h = self._app.win.getSize()
if win_w < win_h:
y *= 1. * win_h / win_w
else:
x *= 1. * win_w / win_h
pos = Point2(x, y)
x, z = pos + self._drag_offset
self._frame.setPos(x, 0., z)
self._drag_start = Point2(pos)
return task.cont
def __startDrag(self, *args):
x, y = self._app.mouseWatcherNode.getMouse()
win_w, win_h = self._app.win.getSize()
if win_w < win_h:
y *= 1. * win_h / win_w
else:
x *= 1. * win_w / win_h
self._drag_start = Point2(x, y)
x, y, z = self._frame.getPos()
self._drag_offset = Point2(x, z) - self._drag_start
self._app.taskMgr.add(self.__drag, 'drag_color_picker')
def __stopDrag(self, *args):
self._app.taskMgr.remove('drag_color_picker')
class MyApp(ShowBase):
def __init__(self):
ShowBase.__init__(self)
self.btns = []
self.btn_colors = []
self.col_pickers = {}
for i in range(3):
getCommand = lambda btn_id: lambda: self.createColorPicker(btn_id)
btn = DirectButton(borderWidth=(.05, .05),
frameColor=(.5, .5, .5, 1.),
frameSize=(-.4, .4, -.15, .15),
command=getCommand(i),
text='Color me',
text_pos=(0., -.03),
text_scale=(.12, .12),
pos=(.85 * i - .85, 0., -.65))
self.btns.append(btn)
color_data = ColorData(Vec3(.5, .5, .5))
self.btn_colors.append(color_data)
self.col_pickers[i] = None
def setColor(self, btn_id, color_data):
self.btns[btn_id]['frameColor'] = color_data.getColorShade()
self.btn_colors[btn_id] = color_data.copy()
def removeColorPicker(self, btn_id):
self.col_pickers[btn_id] = None
def createColorPicker(self, btn_id):
if not self.col_pickers[btn_id]:
color_data = self.btn_colors[btn_id]
col_picker = DirectColorPicker(self,
lambda color: self.setColor(btn_id, color),
lambda: self.removeColorPicker(btn_id),
color_data.copy(),
relief=DGG.RAISED,
borderWidth=(.05, .05),
frameColor=(.5, .5, .5, 1.),
pos=(.85 * btn_id - .85, 0., .35),
scale=(.5, 1., .5))
self.col_pickers[btn_id] = col_picker
app = MyApp()
app.run()
And these are the images I used:
Hope it will be useful.