我可以将Neumorphism效果应用于QWidget吗?

10

虽然Qt提供了QGraphicsDropShadowEffect,但没有可用的 "Neumorphism" 效果:

Comparison between drop shadow and neumorphism

在CSS中有一个名为box-shadow的属性(如上图所示),它可以包含多种颜色,但是Qt缺乏对该属性的支持,并且不可能同时应用多个图形效果。
这能做到吗?
2个回答

19
解决方案是创建一个自定义的QGraphicsEffect子类,并使用渐变。
起初,我考虑按照CSS所使用的相同概念,对QGraphicsDropShadowEffect进行子类化,并在内部使用另一个效果来绘制“其他”阴影,但我不喜欢结果:在某些情况下(通常是当半径和对比度过大时),它就无法正常工作。

wrong result

如果你仔细观察,你会发现结果与投影太相似,就像物体浮在空中一样,而应该是“凸出”的。
我找到的唯一有效的解决方案是手动绘制所有内容,使用线性渐变来实现边框,并使用组合渐变来实现角落。虽然第一个比较容易理解,但第二个需要一些巧思,需要使用QPainter的组合模式:Qt只有放射和圆锥渐变,但它们之间没有“混合”。
然后的诀窍是为“亮”颜色创建一个径向渐变,在中心处使用完整的颜色,在边框处使用相同的颜色和0 alpha,然后叠加一个“暗”颜色的圆锥渐变(从“暗”颜色开始,到90°时变成“亮”颜色),这将使用第一个渐变的alpha分量进行绘制。

steps to create the composite gradient

然后只需要创建函数来更新每个属性:距离(效果的范围),颜色(用于渐变,默认为应用程序的QPalette.Window颜色角色),原点(用作光源的角落)和可选的clipRadius用于圆角边框。
一些重要的注意事项:
- 由于它是一个QGraphicsEffect,因此它只能应用于“父”窗口小部件:子窗口小部件不能有另一个效果应用于它们,这意味着如果您有一个容器,如QGroupBox或QTabWidget,您必须选择是将其应用于父级还是每个子级; - 由于其“简单”的性质,它仅支持矩形形状:如果小部件具有掩码,则效果形状仍将基于矩形; - 布局边距和间距应该被考虑在内,因为如果使用它们的小部件太窄,多个效果可能会重叠;我建议使用QProxyStyle并为PM_Layout [*] Margin和PM_Layout [*] Spacing设置最小默认值,并根据length属性返回值; - clipRadius属性允许圆角边框剪切,但它并不完美,因为QPainter的剪辑不支持抗锯齿;我会看看是否可以在未来解决这个问题; - 当应用于QGraphicsScene项时,类似于QGraphicsDropShadowEffect,效果处于设备坐标中,因此变换(旋转、缩放、剪切)不会被应用;我将在解决这个问题时更新这个答案。

final neumorphism effect result

下面是Qt QGraphicsDropShadowEffect、css模拟和我的NeumorphismEffect之间的比较(后两者都有圆角边框:css版本使用border-radius属性,而我的版本则使用clipRadius属性):

a cool comparison

class NeumorphismEffect(QtWidgets.QGraphicsEffect):
    originChanged = QtCore.pyqtSignal(QtCore.Qt.Corner)
    distanceChanged = QtCore.pyqtSignal(float)
    colorChanged = QtCore.pyqtSignal(QtGui.QColor)
    clipRadiusChanged = QtCore.pyqtSignal(int)

    _cornerShift = (QtCore.Qt.TopLeftCorner, QtCore.Qt.TopRightCorner, 
        QtCore.Qt.BottomRightCorner, QtCore.Qt.BottomLeftCorner)

    def __init__(self, distance=4, color=None, origin=QtCore.Qt.TopLeftCorner, clipRadius=0):
        super().__init__()

        self._leftGradient = QtGui.QLinearGradient(1, 0, 0, 0)
        self._leftGradient.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)
        self._topGradient = QtGui.QLinearGradient(0, 1, 0, 0)
        self._topGradient.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)

        self._rightGradient = QtGui.QLinearGradient(0, 0, 1, 0)
        self._rightGradient.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)
        self._bottomGradient = QtGui.QLinearGradient(0, 0, 0, 1)
        self._bottomGradient.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)

        self._radial = QtGui.QRadialGradient(.5, .5, .5)
        self._radial.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)
        self._conical = QtGui.QConicalGradient(.5, .5, 0)
        self._conical.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)

        self._origin = origin
        distance = max(0, distance)
        self._clipRadius = min(distance, max(0, clipRadius))
        self._setColor(color or QtWidgets.QApplication.palette().color(QtGui.QPalette.Window))
        self._setDistance(distance)

    def color(self):
        return self._color

    @QtCore.pyqtSlot(QtGui.QColor)
    @QtCore.pyqtSlot(QtCore.Qt.GlobalColor)
    def setColor(self, color):
        if isinstance(color, QtCore.Qt.GlobalColor):
            color = QtGui.QColor(color)
        if color == self._color:
            return
        self._setColor(color)
        self._setDistance(self._distance)
        self.update()
        self.colorChanged.emit(self._color)

    def _setColor(self, color):
        self._color = color
        self._baseStart = color.lighter(125)
        self._baseStop = QtGui.QColor(self._baseStart)
        self._baseStop.setAlpha(0)
        self._shadowStart = self._baseStart.darker(125)
        self._shadowStop = QtGui.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), (.25, self._shadowStop), 
            (.75, self._shadowStop), (1, self._shadowStart)]

        self._setOrigin(self._origin)

    def distance(self):
        return self._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 _getCornerPixmap(self, rect, grad1, grad2=None):
        pm = QtGui.QPixmap(self._distance + self._clipRadius, self._distance + self._clipRadius)
        pm.fill(QtCore.Qt.transparent)
        qp = QtGui.QPainter(pm)
        if self._clipRadius > 1:
            path = QtGui.QPainterPath()
            path.addRect(rect)
            size = self._clipRadius * 2 - 1
            mask = QtCore.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

    def _setDistance(self, distance):
        distance = max(1, distance)
        self._distance = distance
        if self._clipRadius > distance:
            self._clipRadius = distance
        distance += self._clipRadius
        r = QtCore.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])

        # create the 4 corners as if the light source was top-left
        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)

        # rotate the images according to the actual light source
        images = topLeft, topRight, bottomRight, bottomLeft
        shift = self._cornerShift.index(self._origin)
        if shift:
            transform = QtGui.QTransform().rotate(shift * 90)
            for img in images:
                img.swap(img.transformed(transform, QtCore.Qt.SmoothTransformation))

        # and reorder them if required
        self.topLeft, self.topRight, self.bottomRight, self.bottomLeft = images[-shift:] + images[:-shift]

    def origin(self):
        return self._origin

    @QtCore.pyqtSlot(QtCore.Qt.Corner)
    def setOrigin(self, origin):
        origin = QtCore.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

        # assign color stops to gradients based on the light source position
        shift = self._cornerShift.index(self._origin)
        for grad, stops in zip(gradients, stops[-shift:] + stops[:-shift]):
            grad.setStops(stops)

    def clipRadius(self):
        return self._clipRadius

    @QtCore.pyqtSlot(int)
    @QtCore.pyqtSlot(float)
    def setClipRadius(self, radius):
        if radius == self._clipRadius:
            return
        oldRadius = self._clipRadius
        self._setClipRadius(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)

    def draw(self, qp):
        restoreTransform = qp.worldTransform()

        qp.setPen(QtCore.Qt.NoPen)
        x, y, width, height = self.sourceBoundingRect(QtCore.Qt.DeviceCoordinates).getRect()
        right = x + width
        bottom = y + height
        clip = self._clipRadius
        doubleClip = clip * 2

        qp.setWorldTransform(QtGui.QTransform())
        leftRect = QtCore.QRectF(x - self._distance, y + clip, self._distance, height - doubleClip)
        qp.setBrush(self._leftGradient)
        qp.drawRect(leftRect)

        topRect = QtCore.QRectF(x + clip, y - self._distance, width - doubleClip, self._distance)
        qp.setBrush(self._topGradient)
        qp.drawRect(topRect)

        rightRect = QtCore.QRectF(right, y + clip, self._distance, height - doubleClip)
        qp.setBrush(self._rightGradient)
        qp.drawRect(rightRect)

        bottomRect = QtCore.QRectF(x + clip, bottom, width - doubleClip, self._distance)
        qp.setBrush(self._bottomGradient)
        qp.drawRect(bottomRect)

        qp.drawPixmap(x - self._distance, y - self._distance, self.topLeft)
        qp.drawPixmap(right - clip, y - self._distance, self.topRight)
        qp.drawPixmap(right - clip, bottom - clip, self.bottomRight)
        qp.drawPixmap(x - self._distance, bottom - clip, self.bottomLeft)

        qp.setWorldTransform(restoreTransform)
        if self._clipRadius:
            path = QtGui.QPainterPath()
            source, offset = self.sourcePixmap(QtCore.Qt.DeviceCoordinates)

            sourceBoundingRect = self.sourceBoundingRect(QtCore.Qt.DeviceCoordinates)
            qp.save()
            qp.setTransform(QtGui.QTransform())
            path.addRoundedRect(sourceBoundingRect, self._clipRadius, self._clipRadius)
            qp.setClipPath(path)
            qp.drawPixmap(source.rect().translated(offset), source)
            qp.restore()
        else:
            self.drawSource(qp)

1
好的,非常感谢!顺便提一下修复不平滑剪切的问题:在draw函数中设置qp.setRenderHints(qp.Antialiasing | qp.SmoothPixmapTransform),在_getCornerPixmap函数中也做同样操作。 - SamG101
我在我的项目中使用了一个稍微修改过的版本的这段代码,但是当我将效果应用到QMenu对象时,效果没有显示出来。菜单有圆角,我可以看到效果在角落周围,也就是正常矩形的切口处。你有任何想法为什么菜单没有显示效果吗?我还在添加效果之前执行了menu.setWindowFlags(menu.windowFlags() | Qt.NoDropShadowWindowHint)。谢谢。 - SamG101
您不能在QMenu上应用此类效果,因为该效果的基本要求是具有足够的空间周围以显示它(该效果位于小部件几何图形之外),因此它必须是另一个小部件的子级。 - musicamante
@avi 不,QSS不支持图形效果,这只能通过代码应用。 - musicamante
它在PyQt5中很好地工作,但是当使用圆形小部件时需要更大的空间。顺便问一下,有人知道为什么在PySide2中没有显示效果或如何使其工作吗? - lymphatic
显示剩余7条评论

0

我只是稍微进行了一些编辑。具体来说,我删除了值更改事件,添加了更改浅色和深色颜色的功能,修复了容器内容的显示。此外,基于这个类,创建了另一个类来添加内部阴影。它可以与PySide6一起使用。

重要提示!内部阴影是在内容绘制后绘制的,因此不要使阴影过大,否则可能会遮挡内容。这样做是为了保留更改背景颜色的能力。

class OutsideNeumorphismEffect(QtWidgets.QGraphicsEffect):
    _cornerShift = (
    QtCore.Qt.TopLeftCorner, QtCore.Qt.TopLeftCorner, QtCore.Qt.BottomRightCorner, QtCore.Qt.BottomLeftCorner)

    def __init__(self, distance: int = 4, lightColor: QtGui.QColor = QtGui.QColor("#FFFFFF"),
                 darkColor: QtGui.QColor = QtGui.QColor("#7d7d7d"), clipRadius: int = 4,
                 origin=QtCore.Qt.TopLeftCorner):
        super().__init__()

        self._leftGradient = QtGui.QLinearGradient(1, 0, 0, 0)
        self._leftGradient.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)
        self._topGradient = QtGui.QLinearGradient(0, 1, 0, 0)
        self._topGradient.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)

        self._rightGradient = QtGui.QLinearGradient(0, 0, 1, 0)
        self._rightGradient.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)
        self._bottomGradient = QtGui.QLinearGradient(0, 0, 0, 1)
        self._bottomGradient.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)

        self._radial = QtGui.QRadialGradient(.5, .5, .5)
        self._radial.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)
        self._conical = QtGui.QConicalGradient(.5, .5, 0)
        self._conical.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)

        self._origin = origin
        distance = max(0, distance)
        self._clipRadius = min(distance, max(0, clipRadius))
        self._setColors(lightColor, darkColor)
        self._setDistance(distance)

    def setColors(self, color1, color2):
        if isinstance(color1, QtCore.Qt.GlobalColor) and isinstance(color2, QtCore.Qt.GlobalColor):
            color1 = QtGui.QColor(color1)
            color2 = QtGui.QColor(color2)

            self._setColors(color1, color2)

            self._setDistance(self._distance)
            self.update()

    def _setColors(self, color1, color2):

        self._baseStart = color1
        self._baseStop = QtGui.QColor(color1)
        self._baseStop.setAlpha(0)
        self._shadowStart = color2
        self._shadowStop = QtGui.QColor(color2)
        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), (.25, self._shadowStop),
                            (.75, self._shadowStop), (1, self._shadowStart)]

        self._setOrigin(self._origin)

    def distance(self):
        return self._distance

    def setDistance(self, distance):
        if distance == self._distance:
            return

        self._setDistance(distance)
        self.updateBoundingRect()

    def _getCornerPixmap(self, rect, grad1, grad2=None):
        pm = QtGui.QPixmap(self._distance + self._clipRadius, self._distance + self._clipRadius)
        pm.fill(QtCore.Qt.transparent)
        qp = QtGui.QPainter(pm)
        if self._clipRadius > 1:
            path = QtGui.QPainterPath()
            path.addRect(rect)
            size = self._clipRadius * 2 - 1
            mask = QtCore.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

    def _setDistance(self, distance):
        distance = max(1, distance)
        self._distance = distance
        if self._clipRadius > distance:
            self._clipRadius = distance
        distance += self._clipRadius
        r = QtCore.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])

        # create the 4 corners as if the light source was top-left
        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)

        # rotate the images according to the actual light source
        images = topLeft, topRight, bottomRight, bottomLeft
        shift = self._cornerShift.index(self._origin)
        if shift:
            transform = QtGui.QTransform().rotate(shift * 90)
            for img in images:
                img.swap(img.transformed(transform, QtCore.Qt.SmoothTransformation))

        # and reorder them if required
        self.topLeft, self.topRight, self.bottomRight, self.bottomLeft = images[-shift:] + images[:-shift]

    def origin(self):
        return self._origin

    def setOrigin(self, origin):
        origin = QtCore.Qt.Corner(origin)
        if origin == self._origin:
            return
        self._setOrigin(origin)
        self._setDistance(self._distance)
        self.update()

    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

        # assign color stops to gradients based on the light source position
        shift = self._cornerShift.index(self._origin)
        for grad, stops in zip(gradients, stops[-shift:] + stops[:-shift]):
            grad.setStops(stops)

    def clipRadius(self):
        return self._clipRadius

    def setClipRadius(self, radius):
        if radius == self._clipRadius:
            return
        self._setClipRadius(radius)
        self.update()

    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
        return rect.adjusted(-d, -d, d, d)

    def draw(self, qp):
        restoreTransform = qp.worldTransform()

        qp.setPen(QtCore.Qt.NoPen)
        x, y, width, height = self.sourceBoundingRect(QtCore.Qt.DeviceCoordinates).getRect()
        right = x + width
        bottom = y + height
        clip = self._clipRadius
        doubleClip = clip * 2

        if self._clipRadius:
            path = QtGui.QPainterPath()
            source = self.sourcePixmap(QtCore.Qt.DeviceCoordinates)
            sourceBoundingRect = self.sourceBoundingRect(QtCore.Qt.DeviceCoordinates)
            qp.save()
            qp.setTransform(QtGui.QTransform())
            path.addRoundedRect(sourceBoundingRect.x(), sourceBoundingRect.y(), sourceBoundingRect.width(),
                                sourceBoundingRect.height(), self._clipRadius, self._clipRadius)
            qp.setClipPath(path)
            qp.drawPixmap(sourceBoundingRect.x() - self._distance, sourceBoundingRect.y() - self._distance, source)
            qp.restore()
        else:
            path = QtGui.QPainterPath()
            source = self.sourcePixmap(QtCore.Qt.DeviceCoordinates)
            sourceBoundingRect = self.sourceBoundingRect(QtCore.Qt.DeviceCoordinates)
            qp.save()
            qp.setTransform(QtGui.QTransform())
            path.addRect(sourceBoundingRect.x(), sourceBoundingRect.y(), sourceBoundingRect.width(),
                         sourceBoundingRect.height())
            qp.setClipPath(path)
            qp.drawPixmap(sourceBoundingRect.x() - self._distance, sourceBoundingRect.y() - self._distance, source)
            qp.restore()

        qp.setWorldTransform(QtGui.QTransform())
        leftRect = QtCore.QRectF(x - self._distance, y + clip, self._distance, height - doubleClip)
        qp.setBrush(self._leftGradient)
        qp.drawRect(leftRect)

        topRect = QtCore.QRectF(x + clip, y - self._distance, width - doubleClip, self._distance)
        qp.setBrush(self._topGradient)
        qp.drawRect(topRect)

        rightRect = QtCore.QRectF(right, y + clip, self._distance, height - doubleClip)
        qp.setBrush(self._rightGradient)
        qp.drawRect(rightRect)

        bottomRect = QtCore.QRectF(x + clip, bottom, width - doubleClip, self._distance)
        qp.setBrush(self._bottomGradient)
        qp.drawRect(bottomRect)

        qp.drawPixmap(x - self._distance, y - self._distance, self.topLeft)
        qp.drawPixmap(right - clip, y - self._distance, self.topRight)
        qp.drawPixmap(right - clip, bottom - clip, self.bottomRight)
        qp.drawPixmap(x - self._distance, bottom - clip, self.bottomLeft)

        qp.setWorldTransform(restoreTransform)


class InsideNeumorphismEffect(QtWidgets.QGraphicsEffect):
    _cornerShift = (QtCore.Qt.TopLeftCorner, QtCore.Qt.TopRightCorner,
                    QtCore.Qt.BottomRightCorner, QtCore.Qt.BottomLeftCorner)

    def __init__(self, distance: int = 4, lightColor: QtGui.QColor = QtGui.QColor("#FFFFFF"),
                 darkColor: QtGui.QColor = QtGui.QColor("#7d7d7d"), clipRadius: int = 4,
                 origin=QtCore.Qt.BottomRightCorner):
        super().__init__()

        self._leftGradient = QtGui.QLinearGradient(0, 0, 1, 0)
        self._leftGradient.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)
        self._topGradient = QtGui.QLinearGradient(0, 0, 0, 1)
        self._topGradient.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)

        self._rightGradient = QtGui.QLinearGradient(1, 0, 0, 0)
        self._rightGradient.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)
        self._bottomGradient = QtGui.QLinearGradient(0, 1, 0, 0)
        self._bottomGradient.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)

        self._radial = QtGui.QRadialGradient(.5, .5, .5)
        self._radial.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)
        self._conical = QtGui.QConicalGradient(.5, .5, 0)
        self._conical.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)

        self._origin = origin
        distance = max(0, distance)
        self._clipRadius = min(distance, max(0, clipRadius))
        self._setColors(lightColor, darkColor)
        self._setDistance(distance)

    def setColors(self, color1, color2):
        if isinstance(color1, QtCore.Qt.GlobalColor) and isinstance(color2, QtCore.Qt.GlobalColor):
            color1 = QtGui.QColor(color1)
            color2 = QtGui.QColor(color2)

            self._setColors(color1, color2)

            self._setDistance(self._distance)
            self.update()

    def _setColors(self, color1, color2):

        self._baseStart = color1
        self._baseStop = QtGui.QColor(color1)
        self._baseStop.setAlpha(0)
        self._shadowStart = color2
        self._shadowStop = QtGui.QColor(color2)
        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), (.25, self._shadowStop),
                            (.75, self._shadowStop), (1, self._shadowStart)]

        self._setOrigin(self._origin)

    def distance(self):
        return self._distance

    def setDistance(self, distance):
        if distance == self._distance:
            return

        self._setDistance(distance)
        self.updateBoundingRect()

    def _getCornerPixmap(self, rect, grad1, grad2=None):
        pm = QtGui.QPixmap(self._distance + self._clipRadius, self._distance + self._clipRadius)
        pm.fill(QtCore.Qt.transparent)
        qp = QtGui.QPainter(pm)
        if self._clipRadius > 1:
            path = QtGui.QPainterPath()
            size = self._clipRadius * 2 - 1
            el = QtCore.QRectF(0, 0, size, size)
            path.addEllipse(rect)
            qp.setClipPath(path)
        qp.fillRect(rect, grad1)
        if grad2:
            qp.setCompositionMode(qp.CompositionMode_SourceAtop)
            qp.fillRect(rect, grad2)
        qp.end()
        return pm

    def _setDistance(self, distance):
        distance = max(1, distance)
        self._distance = distance
        if self._clipRadius > distance:
            self._clipRadius = distance
        distance += self._clipRadius
        r = QtCore.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] = (1, lightSideStops[0][1])
            lightSideStops[1] = (gradStart, lightSideStops[1][1])
            shadowSideStops[1] = (gradStart, shadowSideStops[1][1])
            shadowSideStops[0] = (1, shadowSideStops[0][1])
        else:
            lightSideStops[0] = (1, lightSideStops[0][1])
            lightSideStops[1] = (0, lightSideStops[1][1])
            shadowSideStops[1] = (0, shadowSideStops[1][1])
            shadowSideStops[0] = (1, shadowSideStops[0][1])

        # create the 4 corners as if the light source was top-left
        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)

        # rotate the images according to the actual light source
        images = topLeft, topRight, bottomRight, bottomLeft
        shift = self._cornerShift.index(self._origin)
        if shift:
            transform = QtGui.QTransform().rotate(shift * 90)
            for img in images:
                img.swap(img.transformed(transform, QtCore.Qt.SmoothTransformation))

        # and reorder them if required
        self.topLeft, self.topRight, self.bottomRight, self.bottomLeft = images[-shift:] + images[:-shift]

    def origin(self):
        return self._origin

    def setOrigin(self, origin):
        origin = QtCore.Qt.Corner(origin)
        if origin == self._origin:
            return
        self._setOrigin(origin)
        self._setDistance(self._distance)
        self.update()

    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

        # assign color stops to gradients based on the light source position
        shift = self._cornerShift.index(self._origin)
        for grad, stops in zip(gradients, stops[-shift:] + stops[:-shift]):
            grad.setStops(stops)

    def clipRadius(self):
        return self._clipRadius

    def setClipRadius(self, radius):
        if radius == self._clipRadius:
            return

        self._setClipRadius(radius)
        self.update()

    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
        return rect.adjusted(-d, -d, d, d)

    def draw(self, qp):

        restoreTransform = qp.worldTransform()

        qp.setPen(QtCore.Qt.NoPen)
        x, y, width, height = self.sourceBoundingRect(QtCore.Qt.DeviceCoordinates).getRect()
        right = x + width
        bottom = y + height
        clip = self._clipRadius
        doubleClip = clip * 2

        if self._clipRadius:
            path = QtGui.QPainterPath()
            source = self.sourcePixmap(QtCore.Qt.DeviceCoordinates)
            sourceBoundingRect = self.sourceBoundingRect(QtCore.Qt.DeviceCoordinates)
            qp.save()
            qp.setTransform(QtGui.QTransform())
            path.addRoundedRect(sourceBoundingRect, self._clipRadius, self._clipRadius)
            qp.setClipPath(path)
            qp.drawPixmap(sourceBoundingRect.x() - self._distance, sourceBoundingRect.y() - self._distance, source)
            qp.restore()
        else:
            path = QtGui.QPainterPath()
            source = self.sourcePixmap(QtCore.Qt.DeviceCoordinates)
            sourceBoundingRect = self.sourceBoundingRect(QtCore.Qt.DeviceCoordinates)
            qp.save()
            qp.setTransform(QtGui.QTransform())
            path.addRect(sourceBoundingRect.x(), sourceBoundingRect.y(), sourceBoundingRect.width(),
                         sourceBoundingRect.height())
            qp.setClipPath(path)
            qp.drawPixmap(sourceBoundingRect.x() - self._distance, sourceBoundingRect.y() - self._distance, source)
            qp.restore()

        qp.setWorldTransform(QtGui.QTransform())
        leftRect = QtCore.QRectF(x, y + clip + self._distance, self._distance,
                                 height - doubleClip - self._distance * 2)
        qp.setBrush(self._leftGradient)
        qp.drawRect(leftRect)

        topRect = QtCore.QRectF(x + clip + self._distance, y, width - doubleClip - self._distance * 2,
                                self._distance)
        qp.setBrush(self._topGradient)
        qp.drawRect(topRect)

        rightRect = QtCore.QRectF(right - self._distance, y + clip + self._distance, self._distance,
                                  height - doubleClip - self._distance * 2)
        qp.setBrush(self._rightGradient)
        qp.drawRect(rightRect)

        bottomRect = QtCore.QRectF(x + clip + self._distance, bottom - self._distance,
                                   width - doubleClip - self._distance * 2, self._distance)
        qp.setBrush(self._bottomGradient)
        qp.drawRect(bottomRect)

        qp.drawPixmap(x, y, self.topLeft)
        qp.drawPixmap(right - clip - self._distance, y, self.topRight)
        qp.drawPixmap(right - clip - self._distance, bottom - clip - self._distance, self.bottomRight)
        qp.drawPixmap(x, bottom - clip - self._distance, self.bottomLeft)

        qp.setWorldTransform(restoreTransform)

请问您能否尝试澄清一下您实际上改变了什么(除了PyQt/Side和版本的明显差异),这样人们就不必自己找出来了吗?此外,请确保代码被正确地显示和缩进(请阅读有关格式化代码的更多信息)。 - musicamante
我已经为阴影创建了一个新的效果。现在你可以混合它们并指定多个阴影。如果你指定了边框半径,它们也会被切断。https://github.com/GvozdevLeonid/BoxShadow-in-PyQt-PySide - leonid gvozdev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接