У меня есть QMenu
объект, и я применяю к нему эффект нейморфизма. Графический эффект для эффекта нейморфизма (ниже) является модифицированной версией, которую предоставил @musicamante.
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import inspect
import sys
class QNeumorphismEffect(QGraphicsEffect):
originChanged = pyqtSignal(Qt.Corner)
distanceChanged = pyqtSignal(float)
colorChanged = pyqtSignal(QColor)
clipRadiusChanged = pyqtSignal(int)
_cornerShift = (Qt.TopLeftCorner,Qt.TopRightCorner,Qt.BottomRightCorner,Qt.BottomLeftCorner)
def __init__(self,color,darkShadow,lightShadow,distance=6,origin=Qt.TopLeftCorner,clipRadius=0,*args,**kwargs):
QGraphicsEffect.__init__(self,*args,**kwargs)
distance = max(distance,0)
self._leftGradient = QLinearGradient(1,0,0,0)
self._topGradient = QLinearGradient(0,1,0,0)
self._rightGradient = QLinearGradient(0,0,1,0)
self._bottomGradient = QLinearGradient(0,0,0,1)
self._radial = QRadialGradient(0.5,0.5,0.5)
self._conical = QConicalGradient(0.5,0.5,0)
self._leftGradient.setCoordinateMode(QGradient.ObjectBoundingMode)
self._topGradient.setCoordinateMode(QGradient.ObjectBoundingMode)
self._rightGradient.setCoordinateMode(QGradient.ObjectBoundingMode)
self._bottomGradient.setCoordinateMode(QGradient.ObjectBoundingMode)
self._radial.setCoordinateMode(QGradient.ObjectBoundingMode)
self._conical.setCoordinateMode(QGradient.ObjectBoundingMode)
self._origin = origin
self._clipRadius = max(0,clipRadius)
self._setColor(color or QColor("#ff0000"),darkShadow or QColor("#00ff00"),lightShadow or QColor("#0000ff"))
self._setDistance(distance)
##### COLOR MANAGEMENT #####
def color(self):
return self._color
@pyqtSlot(QColor)
@pyqtSlot(Qt.GlobalColor)
def setColor(self,color,dark,light):
if isinstance(color,Qt.GlobalColor):
color = QColor(color)
if color == self._color:
return
self._setColor(color,dark,light)
self._setDistance(self._distance)
self.update()
self.colorChanged.emit(self._color)
def _setColor(self,color,dark,light): #use custom colors here for shadows, bg etc...
self._color = color
self._baseStart = light
self._baseStop = QColor(self._baseStart)
self._baseStop.setAlpha(0)
self._shadowStart = dark
self._shadowStop = QColor(self._shadowStart)
self._shadowStop.setAlpha(0)
self.lightSideStops = [(0,self._baseStart),(1,self._baseStop)]
self.shadowSideStops = [(0,self._shadowStart),(1,self._shadowStop)]
self.cornerStops = [(0,self._shadowStart),(0.25,self._shadowStop),(0.75,self._shadowStop),(1,self._shadowStart)]
self._setOrigin(self._origin)
##### DISTANCE MANAGEMENT #####
def distance(self):
return seld._distance
def setDistance(self,distance):
if distance == self._distance:
return
oldRadius = self._clipRadius
self._setDistance(distance)
self.updateBoundingRect()
self.distanceChanged.emit(self._distance)
if oldRadius != self._clipRadius:
self.clipRadiusChanged.emit(self._clipRadius)
def _setDistance(self,distance):
distance = max(1,distance)
self._distance = distance
#if self._clipRadius > distance:
#self._clipRadius = distance
distance += self._clipRadius
r = QRectF(0,0,distance * 2,distance * 2)
lightSideStops = self.lightSideStops[:]
shadowSideStops = self.shadowSideStops[:]
if self._clipRadius:
gradStart = self._clipRadius / (self._distance + self._clipRadius)
lightSideStops[0] = (gradStart,lightSideStops[0][1])
shadowSideStops[0] = (gradStart,shadowSideStops[0][1])
self._radial.setStops(lightSideStops)
topLeft = self._getCornerPixmap(r,self._radial)
self._conical.setAngle(359.9)
self._conical.setStops(self.cornerStops)
topRight = self._getCornerPixmap(r.translated(-distance,0),self._radial,self._conical)
self._conical.setAngle(270)
self._conical.setStops(self.cornerStops)
bottomLeft = self._getCornerPixmap(r.translated(0,-distance),self._radial,self._conical)
self._radial.setStops(shadowSideStops)
bottomRight = self._getCornerPixmap(r.translated(-distance,-distance),self._radial)
images = topLeft,topRight,bottomRight,bottomLeft
shift = self._cornerShift.index(self._origin)
if shift:
transform = QTransform().rotate(shift * 90)
for img in images:
img.swap(img.transformed(transform,Qt.SmoothTransformation))
self.topLeft,self.topRight,self.bottomRight,self.bottomLeft = images[-shift:] + images[:-shift]
def _getCornerPixmap(self,rect,grad1,grad2=None):
pm = QPixmap(self._distance + self._clipRadius,
self._distance + self._clipRadius)
pm.fill(Qt.transparent)
qp = QPainter(pm)
qp.setRenderHints(qp.Antialiasing | qp.SmoothPixmapTransform)
if self._clipRadius > 1:
path = QPainterPath()
path.addRect(rect)
size = self._clipRadius * 2 - 1
mask = QRectF(0,0,size,size)
mask.moveCenter(rect.center())
path.addEllipse(mask)
qp.setClipPath(path)
qp.fillRect(rect,grad1)
if grad2:
qp.setCompositionMode(qp.CompositionMode_SourceAtop)
qp.fillRect(rect,grad2)
qp.end()
return pm
##### ORIGIN MANAGEMENT #####
def origin(self):
return self._origin
@pyqtSlot(Qt.Corner)
def setOrigin(self,origin):
origin = Qt.Corner(origin)
if origin == self._origin: return
self._setOrigin(origin)
self._setDistance(self._distance)
self.update()
self.originChanged.emit(self._origin)
def _setOrigin(self,origin):
self._origin = origin
gradients = self._leftGradient,self._topGradient,self._rightGradient,self._bottomGradient
stops = self.lightSideStops,self.lightSideStops,self.shadowSideStops,self.shadowSideStops
shift = self._cornerShift.index(self._origin)
for grad,stops in zip(gradients,stops[-shift:] + stops[:-shift]):
grad.setStops(stops)
##### CLIP RADIUS MANAGEMENT #####
def clipRadius(self):
return self.clipRadius
@pyqtSlot(int)
@pyqtSlot(float)
def setClipRadius(self,radius):
if radius == self._clipRadius: return
oldRadius = self._clipRadius
self.set_clipRadius(radius)
self.update()
if oldRadius != self._clipRadius:
self.clipRadiusChanged.emit(self._clipRadius)
def _setClipRadius(self,radius):
radius = min(self.distance,max(0,int(radius)))
self._clipRadius = radius
self._setDistance(self._distance)
def boundingRectFor(self,rect):
d = self._distance + 1
return rect.adjusted(-d,-d,d,d)
##### DRAWING MANAGEMENT #####
def draw(self,qp):
restoreTransform = qp.worldTransform()
qp.setRenderHints(qp.Antialiasing | qp.SmoothPixmapTransform)
qp.setPen(Qt.NoPen)
x,y,width,height = self.sourceBoundingRect(Qt.DeviceCoordinates).getRect()
right = x + width
bottom = y + height
clip = self._clipRadius
doubleClip = clip * 2
qp.setWorldTransform(QTransform())
leftRect = QRectF(x - self._distance,y + clip,self._distance,height - doubleClip)
qp.setBrush(self._leftGradient)
qp.drawRect(leftRect)
topRect = QRectF(x + clip,y - self._distance,width - doubleClip,self._distance)
qp.setBrush(self._topGradient)
qp.drawRect(topRect)
rightRect = QRectF(right,y + clip,self._distance,height - doubleClip)
qp.setBrush(self._rightGradient)
qp.drawRect(rightRect)
bottomRect = QRectF(x + clip,bottom,width - doubleClip,self._distance)
qp.setBrush(self._bottomGradient)
qp.drawRect(bottomRect)
qp.drawPixmap(round(x - self._distance),round(y - self._distance),self.topLeft)
qp.drawPixmap(round(right - clip),round(y - self._distance),self.topRight)
qp.drawPixmap(round(right - clip),round(bottom - clip),self.bottomRight)
qp.drawPixmap(round(x - self._distance),round(bottom - clip),self.bottomLeft)
qp.setWorldTransform(restoreTransform)
if self._clipRadius:
path = QPainterPath()
source,offset = self.sourcePixmap(Qt.DeviceCoordinates,mode=QGraphicsEffect.PadToTransparentBorder)
sourceBoundingRect = self.sourceBoundingRect(Qt.DeviceCoordinates)
qp.save()
qp.setTransform(QTransform())
qp.setBrush(self._color)
path.addRoundedRect(sourceBoundingRect,self._clipRadius,self._clipRadius)
qp.setClipPath(path)
qp.fillPath(path,qp.brush())
qp.drawPixmap(source.rect().translated(offset),source)
qp.restore()
else:
self.drawSource(qp)
Я применяю эффект к QMenu
, как это.
app = QApplication(sys.argv)
menu = QMenu()
menu.setGraphicsEffect(QNeumorphismEffect(color=QColor("#1E1E1E"),darkShadow=QColor("#000000"),lightShadow=QColor("#454545"),clipRadius=8)
menu.setStyleSheet(self.styleSheet() + "QMenu {background-color: #1E1E1E; padding: 2px; menu-scrollable: 1; border: none;}")
menu.setStyleSheet(self.styleSheet() + "QMenu:item {background-color: #1E1E1E; border-radius: 8px; padding: 5px 20px; margin: 1px; height: 16px; width: 250px; color: #AAAAAA;}")
menu.setStyleSheet(self.styleSheet() + "QMenu:item:selected {background-color: #050505;}")
menu.setStyleSheet(self.styleSheet() + "QMenu:separator {background-color: #FCCE03;}")
menu.setStyleSheet(self.styleSheet() + "QMenu:icon {padding: 5px; height: 16px}")
menu.setStyleSheet(self.styleSheet() + "QMenu:icon:checked {flat: true;}")
menu.show()
app.quit()
Я могу видеть эффект в углах, но не где-нибудь еще вокруг меню - я знаю, что это эффект, который я могу видеть в углах, потому что в нижних углах я вижу более темную тень. Тем не менее, он прекрасно работает с кнопками и виджетами:
Я пробовал:
- Заполнение в таблице стилей меню
- Поля в таблице стилей меню
- Попытка расширить прямоугольник рисования или меню
Я не уверен, почему эффект не показывает по меню, кто-нибудь еще знает?
Другая информация:
- PyQt 5.14.2
- Python 3.8.0
- Windows 10