Are you encountering this issue when running my code snippet itself, or after integrating it into your own project? In the latter case, maybe you are overriding the ShowBase handler for the window-event
, such that the aspect ratio isn’t being respected at all?
Another thing you could try is to use ShowBase.win.getPointer(0)
instead of ShowBase.mouseWatcherNode
;
replace instances of this code:
x, y = self._app.mouseWatcherNode.getMouse()
with this:
mouse_pointer = self._app.win.getPointer(0)
x = mouse_pointer.getX()
y = -mouse_pointer.getY()
x = (x * 2. / win_w) - 1.
y = (y * 2. / win_h) + 1.
This still needs to take aspect ratio into account, so it probably won’t help much either. getPointer
is also more suited to be used together with pixel2d
instead of aspect2d
, so here’s an alternative version of the code snippet that reparents the colour picker widgets to pixel2d
:
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
mouse_pointer = self._app.win.getPointer(0)
x = mouse_pointer.getX()
y = -mouse_pointer.getY()
width, height = self._palette_size
screen = self._app.pixel2d
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
mouse_pointer = self._app.win.getPointer(0)
x = mouse_pointer.getX()
y = -mouse_pointer.getY()
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):
mouse_pointer = self._app.win.getPointer(0)
x = mouse_pointer.getX()
y = -mouse_pointer.getY()
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(),
parent=self.pixel2d,
relief=DGG.RAISED,
borderWidth=(.05, .05),
frameColor=(.5, .5, .5, 1.),
pos=(250 * btn_id + 150, 0., -200),
scale=(150, 1., 150))
self.col_pickers[btn_id] = col_picker
app = MyApp()
app.run()
If you don’t mind that the size and position of the colour pickers remains the same regardless of the window size, then this might fix your issue. Fingers crossed!