def test_update_palette(self): w = self.widget w.set_root(t(1.0, leaf(0, 0), leaf(1, 1))) w.setSelectedClusters([w.root()]) p = QPalette() p.setColor(QPalette.All, QPalette.WindowText, QColor(Qt.red)) w.setPalette(p) item = w.item(w.root()) self.assertEqual(item.pen().color(), p.color(QPalette.WindowText))
def __pen_cache(self, palette: QPalette, state: QStyle.State) -> QPen: """Return a QPen from the `palette` for `state`.""" # NOTE: This method exists mostly to avoid QPen, QColor (de)allocations. key = palette.cacheKey(), int(state) & _State_Mask try: return self.__pen_lru_cache[key] except KeyError: cgroup = QPalette.Normal if state & QStyle.State_Active else QPalette.Inactive cgroup = cgroup if state & QStyle.State_Enabled else QPalette.Disabled role = QPalette.HighlightedText if state & QStyle.State_Selected else QPalette.Text pen = QPen(palette.color(cgroup, role)) self.__pen_lru_cache[key] = pen return pen
class NodeBodyItem(GraphicsPathObject): """ The central part (body) of the `NodeItem`. """ def __init__(self, parent=None): # type: (NodeItem) -> None super().__init__(parent) assert isinstance(parent, NodeItem) self.__processingState = 0 self.__progress = -1. self.__animationEnabled = False self.__isSelected = False self.__hover = False self.__shapeRect = QRectF(-10, -10, 20, 20) self.setAcceptHoverEvents(True) self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges, True) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.setPen(QPen(Qt.NoPen)) self.setPalette(default_palette()) self.shadow = QGraphicsDropShadowEffect( blurRadius=0, color=QColor(SHADOW_COLOR), offset=QPointF(0, 0), ) self.shadow.setEnabled(False) # An item with the same shape as this object, stacked behind this # item as a source for QGraphicsDropShadowEffect. Cannot attach # the effect to this item directly as QGraphicsEffect makes the item # non devicePixelRatio aware. shadowitem = GraphicsPathObject(self, objectName="shadow-shape-item") shadowitem.setPen(Qt.NoPen) shadowitem.setBrush(QBrush(QColor(SHADOW_COLOR).lighter())) shadowitem.setGraphicsEffect(self.shadow) shadowitem.setFlag(QGraphicsItem.ItemStacksBehindParent) self.__shadow = shadowitem self.__blurAnimation = QPropertyAnimation(self.shadow, b"blurRadius", self) self.__blurAnimation.setDuration(100) self.__blurAnimation.finished.connect(self.__on_finished) self.__pingAnimation = QPropertyAnimation(self, b"scale", self) self.__pingAnimation.setDuration(250) self.__pingAnimation.setKeyValues([(0.0, 1.0), (0.5, 1.1), (1.0, 1.0)]) # TODO: The body item should allow the setting of arbitrary painter # paths (for instance rounded rect, ...) def setShapeRect(self, rect): # type: (QRectF) -> None """ Set the item's shape `rect`. The item should be confined within this rect. """ path = QPainterPath() path.addEllipse(rect) self.setPath(path) self.__shadow.setPath(path) self.__shapeRect = rect def setPalette(self, palette): # type: (QPalette) -> None """ Set the body color palette (:class:`QPalette`). """ self.palette = QPalette(palette) self.__updateBrush() def setAnimationEnabled(self, enabled): # type: (bool) -> None """ Set the node animation enabled. """ if self.__animationEnabled != enabled: self.__animationEnabled = enabled def setProcessingState(self, state): # type: (int) -> None """ Set the processing state of the node. """ if self.__processingState != state: self.__processingState = state if not state and self.__animationEnabled: self.ping() def setProgress(self, progress): # type: (float) -> None """ Set the progress indicator state of the node. `progress` should be a number between 0 and 100. """ self.__progress = progress self.update() def ping(self): # type: () -> None """ Trigger a 'ping' animation. """ animation_restart(self.__pingAnimation) def hoverEnterEvent(self, event): self.__hover = True self.__updateShadowState() return super().hoverEnterEvent(event) def hoverLeaveEvent(self, event): self.__hover = False self.__updateShadowState() return super().hoverLeaveEvent(event) def paint(self, painter, option, widget=None): # type: (QPainter, QStyleOptionGraphicsItem, Optional[QWidget]) -> None """ Paint the shape and a progress meter. """ # Let the default implementation draw the shape if option.state & QStyle.State_Selected: # Prevent the default bounding rect selection indicator. option.state = QStyle.State(option.state ^ QStyle.State_Selected) super().paint(painter, option, widget) if self.__progress >= 0: # Draw the progress meter over the shape. # Set the clip to shape so the meter does not overflow the shape. painter.save() painter.setClipPath(self.shape(), Qt.ReplaceClip) color = self.palette.color(QPalette.ButtonText) pen = QPen(color, 5) painter.setPen(pen) painter.setRenderHints(QPainter.Antialiasing) span = max(1, int(self.__progress * 57.60)) painter.drawArc(self.__shapeRect, 90 * 16, -span) painter.restore() def __updateShadowState(self): # type: () -> None if self.__isSelected or self.__hover: enabled = True radius = 17 else: enabled = False radius = 0 if enabled and not self.shadow.isEnabled(): self.shadow.setEnabled(enabled) if self.__isSelected: color = QColor(SELECTED_SHADOW_COLOR) else: color = QColor(SHADOW_COLOR) self.shadow.setColor(color) if radius == self.shadow.blurRadius(): return if self.__animationEnabled: if self.__blurAnimation.state() == QPropertyAnimation.Running: self.__blurAnimation.pause() self.__blurAnimation.setStartValue(self.shadow.blurRadius()) self.__blurAnimation.setEndValue(radius) self.__blurAnimation.start() else: self.shadow.setBlurRadius(radius) def __updateBrush(self): # type: () -> None palette = self.palette if self.__isSelected: cg = QPalette.Active else: cg = QPalette.Inactive palette.setCurrentColorGroup(cg) c1 = palette.color(QPalette.Light) c2 = palette.color(QPalette.Button) grad = radial_gradient(c2, c1) self.setBrush(QBrush(grad)) # TODO: The selected state should be set using the # QStyle flags (State_Selected. State_HasFocus) def setSelected(self, selected): # type: (bool) -> None """ Set the `selected` state. .. note:: The item does not have `QGraphicsItem.ItemIsSelectable` flag. This property is instead controlled by the parent NodeItem. """ self.__isSelected = selected self.__updateShadowState() self.__updateBrush() def __on_finished(self): # type: () -> None if self.shadow.blurRadius() == 0: self.shadow.setEnabled(False)
def text_color_for_state(palette: QPalette, state: QStyle.State) -> QColor: """Return the appropriate `palette` text color for the `state`.""" cgroup = QPalette.Normal if state & QStyle.State_Active else QPalette.Inactive cgroup = cgroup if state & QStyle.State_Enabled else QPalette.Disabled role = QPalette.HighlightedText if state & QStyle.State_Selected else QPalette.Text return palette.color(cgroup, role)