class QSlideNavigationBar(QWidget):
    class ItemLineStyle(Enum):
        ItemNone = 1
        ItemTop = 2
        ItemRight = 3
        ItemBottom = 4
        ItemLeft = 5
        ItemRect = 6

    itemClicked = Signal(int, str)

    def __init__(self):
        super(QSlideNavigationBar, self).__init__()

        # -------成员变量定义------------#
        # ==========属性=========#
        self._m_bar_start_color = QColor('#511235')  # type: QColor  # 导航栏起始颜色
        self._m_bar_end_color = QColor('#150507')  # type: QColor  # 导航栏结束颜色
        self._m_bar_radius = 0  # type: int     # 导航栏四个角的圆弧半径
        self._m_item_start_color = QColor(255, 255, 255, 50)  # type: QColor  # item 的起始颜色
        self._m_item_end_color = QColor("black")  # type: QColor  # item 的结束颜色
        self._m_current_hover_index = -1  # type: int    # 当前光标所在的item的index
        self._m_item_hover_start_color = QColor(255, 0, 0, 25)  # type: QColor   # 光标所在的item 的起始颜色
        self._m_item_hover_end_color = QColor(255, 0, 255, 25)  # type: QColor
        self._m_item_text_color = QColor("red")  # type: QColor  # item 的文字颜色
        self._m_item_line_color = QColor("red")  # type: QColor  # item 的线的颜色
        self._m_item_line_width = 5  # type: int     # 线的宽度
        self._m_item_line_style = self.ItemLineStyle.ItemNone  # type: QSlideNavigationBar.ItemLineStyle  # 线的样式类型
        self._m_item_font = QFont('宋体')  # type: QFont   # 字体家族
        self._m_item_font_size = 16  # type: int     # 字体大小
        self._m_item_radius = 0  # type: int     # item 的圆角半径
        self._m_space = 40  # type: int     # 间距大小, item 背景大小
        self._m_orientation = Qt.Horizontal  # type: Qt.Orientation  # 导航栏的方向:横向,纵向
        self._m_enable_key_move = True  # type: bool    # 是否可以使用按键切换item
        self._m_fixed = False  # type: bool    # 大小固定
        self._m_slide_velocity = 10  # type: int     # 滑动速度
        self._m_shake_velocity = 10  # type: int     # 晃动速度

        # ===========内部变量=========#
        self._m_item_maps = {}  # type: map(int, list(str, QRectF)) # 保存的item列表
        self._m_total_text_width = 0  # type: int     # 总的文字的宽度
        self._m_total_text_height = 0  # type: int     # 总的文字的高度
        self._m_current_index = 0  # type: int     # 当前选中的item 的索引
        self._m_start_rect = QRectF()  # type:QRectF  #起始矩形
        self._m_stop_rect = QRectF()  # type: QRectF  # 结束矩形
        self._m_slide_timer = QTimer(self)  # type: QTimer  # 滑动的定时器
        self._m_shake_timer = QTimer(self)  # type: QTimer  # 晃动的定时器
        self._m_forward = False  # type: bool    # 前进

        # ----------执行初始化动作-------------#
        self.setAttribute(Qt.WA_TranslucentBackground)
        self._m_slide_timer.setInterval(self._m_slide_velocity)
        self._m_slide_timer.timeout.connect(self._on_do_slide)
        self._m_shake_timer.setInterval(self._m_shake_velocity)
        self._m_shake_timer.timeout.connect(self._on_do_shake)

        self.setFocusPolicy(Qt.ClickFocus)
        self.setMouseTracking(True)

    # ---------以下是 API 接口----------#
    def add_item(self, item_str: str):
        """
        向导航栏中添加项目(代表一个选项卡),添加之前会查重
        :param item_str:项目名称(显示出来的文字)
        :return:None
        """
        if not item_str:
            return

        for key, value in self._m_item_maps.items():
            if value[0] == item_str:
                return  # 如果存在同名item,则返回

        f = QFont()
        f.setPointSize(self._m_item_font_size)
        fm = QFontMetrics(f)

        text_width = fm.width(item_str)
        text_height = fm.height()
        item_count = len(self._m_item_maps)
        if item_count > 0:
            if self._m_orientation == Qt.Horizontal:
                top_left = QPointF(self._m_total_text_width, 0)
                self._m_total_text_width += text_width + self._m_space
                bottom_right = QPointF(self._m_total_text_width, self._m_total_text_height)
            else:
                top_left = QPointF(0, self._m_total_text_height)
                self._m_total_text_height += text_height + self._m_space
                bottom_right = QPointF(self._m_total_text_width, self._m_total_text_height)

            self._m_item_maps[item_count] = [item_str, QRectF(top_left, bottom_right)]
        else:
            if self._m_orientation == Qt.Horizontal:
                # 水平方向,水平各占1个space, 竖直占1个space
                self._m_total_text_width = text_width + self._m_space
                self._m_total_text_height = text_height + self._m_space
            else:
                # 竖直方向, 水平各占2个space, 竖直占一个space
                self._m_total_text_width = text_width + 2 * self._m_space
                self._m_total_text_height = text_height + self._m_space

            top_left = QPointF(0.0, 0.0)
            bottom_right = QPointF(self._m_total_text_width, self._m_total_text_height)
            self._m_item_maps[item_count] = [item_str, QRectF(top_left, bottom_right)]
        self.setMinimumSize(self._m_total_text_width, self._m_total_text_height)

        if self._m_fixed:
            if self._m_orientation == Qt.Horizontal:
                self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)  # 固定高度
            else:
                self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)  # 固定宽度
        if len(self._m_item_maps):
            self._m_start_rect = QRectF(self._m_item_maps[0][1])
        self.update()

    def set_items(self, items_list: list):
        """
        一次性设置一组项目, 会清空之前设置的项目
        :param items_list: 项目名称列表
        :return: None
        """
        pass

    def get_items(self):
        pass

    def set_bar_start_color(self, color: QColor):
        """
        设置导航栏背景的起始颜色(渐变色的起始)
        :param color:QColor类型
        :return: None
        """
        if color != self._m_bar_start_color:
            self._m_bar_start_color = color
            self.update()

    def set_bar_end_color(self, color: QColor):
        """
        设置导航栏背景的结束颜色(渐变色的结束)
        :param color: QColor 类型
        :return: None
        """
        if color != self._m_bar_end_color:
            self._m_bar_end_color = color
            self.update()

    def set_item_start_color(self, color: QColor):
        """
        设置项目的背景色的起始颜色(渐变色的起始)
        :param color: QColor 类型
        :return: None
        """
        if color != self._m_item_start_color:
            self._m_item_start_color = color
            self.update()

    def set_item_end_color(self, color: QColor):
        """
        设置项目的背景色的结束颜色(渐变色的结束)
        :param color: QColor 类型
        :return: None
        """
        if color != self._m_item_end_color:
            self._m_item_end_color = color
            self.update()

    def set_item_text_color(self, color: QColor) -> None:
        """
        设置 item 的文字颜色
        :param color: QColor 类型
        :return:  None
        """
        if color != self._m_item_text_color:
            self._m_item_text_color = color
            self.update()

    def set_item_line_color(self, color: QColor) -> None:
        """
        设置 item 的线的颜色
        :param color: QColor 类型
        :return:  None
        """
        if color != self._m_item_line_color:
            self._m_item_line_color = color
            self.update()

    def set_bar_radius(self, radius: int):
        """
        设置导航栏四个角的圆弧半径
        :param radius: int 类型
        :return: None
        """
        if radius >= 0 and radius != self._m_bar_radius:
            self._m_bar_radius = radius
            self.update()

    def set_item_radius(self, radius: int):
        """
        设置项目的四个角的圆弧半径
        :param radius: int 类型
        :return: None
        """
        if radius >= 0 and radius != self._m_item_radius:
            self._m_item_radius = radius
            self.update()

    def set_space(self, space: int):
        """
        设置 item 所占的空间大小
        :param space: int 类型
        :return: None
        """
        if space >= 0 and space != self._m_space:
            self._m_space = space
            self.update()

    def set_item_line_width(self, width: int):
        """
        设置项目周围的线宽度
        :param width: int 类型
        :return: None
        """
        if width >= 0 and width != self._m_item_line_width:
            self._m_item_line_width = width
            self.update()

    def set_item_line_style(self, style: ItemLineStyle):
        """
        设置项目周围的线的类型:不显示,上方,下方,左方, 右方, 矩形
        :param style: 枚举类型
        :return: None
        """
        if style != self._m_item_line_style:
            self._m_item_line_style = style
            self.update()

    def set_orientation(self, orientation: Qt.Orientation):
        """
        设置导航栏是横向还是纵向
        :param orientation: Qt.Orientation 类型
        :return: None
        """
        if orientation != self._m_orientation:
            self._m_orientation = orientation
            self.update()

    def set_fixed(self, fixed: bool):
        """
        设置导航栏尺寸固定,不随窗口大小进行缩放
        :param fixed:  bool 类型
        :return:  None
        """
        if fixed != self._m_fixed:
            self._m_fixed = fixed
            self.update()

    def get_current_item_index(self):
        """
        返回当前选中项目的索引号
        :return: int类型,索引号
        """
        return self._m_current_index

    # -----------以下是槽函数--------------#
    def on_set_enable_key_move(self, enable: bool):
        """
        设置可以使用按键来切换导航项目
        :param enable: bool 类型
        :return: None
        """
        if enable != self._m_enable_key_move:
            self._m_enable_key_move = enable

    def on_move_to_first_item(self):
        """
        切换到第一个项目
        :return: None
        """
        self.on_move_to_index(0)

    def on_move_to_last_item(self):
        """
        切换到最后一个项目
        :return: None
        """
        self.on_move_to_index(len(self._m_item_maps) - 1)

    def on_move_to_previous_item(self):
        """
        切换到上一个项目
        :return: None
        """
        if self._m_current_index == 0:
            return
        self.on_move_to_index(self._m_current_index - 1)

    def on_move_to_next_item(self):
        """
        切换到下一个项目
        :return: None
        """
        if self._m_current_index == len(self._m_item_maps) - 1:
            return

        self.on_move_to_index(self._m_current_index + 1)

    def on_move_to_index(self, index: int):
        """
        切换到指定索引的项目
        :param index: int, 项目的索引号
        :return: None
        """
        if (index >= 0) and (index < len(self._m_item_maps)) and (index != self._m_current_index):
            self.itemClicked.emit(index, self._m_item_maps[index][0])
            if self._m_current_index == -1:
                self._m_start_rect = QRectF(self._m_item_maps[index][1])

            self._m_forward = index > self._m_current_index
            self._m_current_index = index
            self._m_stop_rect = QRectF(self._m_item_maps[index][1])
            self._m_slide_timer.start()

    def on_move_to_name(self, name: str):
        """
        切换到指定名称的项目
        :param name: 项目的名称,str类型
        :return: None
        """
        for key, value in self._m_item_maps.items():
            if value[0] == name:
                target_index = key
                if target_index == self._m_current_index:
                    return
                self.on_move_to_index(target_index)
                break

    def on_move_to_position(self, point: QPointF):
        """
        切换到指定位置的项目
        :param point: 位置坐标,QPointF类型
        :return: None
        """
        for key, value in self._m_item_maps.items():
            if value[1].contains(point):
                target_index = key
                if target_index == self._m_current_index:
                    return
                self.on_move_to_index(target_index)
                break

    def on_set_current_item_index(self, index: int):
        """
        将当前选中项目切换到指定索引的项目
        :param index: 索引号, int
        :return: NOne
        """
        self.on_move_to_index(index)

    # ------------以下是私有槽函数---------#
    def _on_do_slide(self):
        """
        完成滑动动作
        :return: None
        """
        if self._m_space <= 0 or self._m_start_rect == self._m_stop_rect:
            self.update()
            self._m_slide_timer.stop()
            return
        if self._m_orientation == Qt.Horizontal:
            dx = self._m_space / 2.0
            dy = 0
        else:
            dx = 0
            dy = self._m_space / 2.0

        if self._m_forward:
            self._m_start_rect.adjust(dx, dy, dx, dy)
            if ((self._m_orientation == Qt.Horizontal) and
                (self._m_start_rect.topLeft().x() >= self._m_stop_rect.topLeft().x())) or \
                    ((self._m_orientation == Qt.Vertical) and
                     (self._m_start_rect.topLeft().y() >= self._m_stop_rect.topLeft().y())):
                self._m_slide_timer.stop()
                if self._m_start_rect != self._m_stop_rect:
                    self._m_shake_timer.start()
        else:
            self._m_start_rect.adjust(-dx, -dy, -dx, -dy)
            if ((self._m_orientation == Qt.Horizontal) and
                (self._m_start_rect.topLeft().x() <= self._m_stop_rect.topLeft().x())) or \
                    ((self._m_orientation == Qt.Vertical) and
                     (self._m_start_rect.topLeft().y() <= self._m_stop_rect.topLeft().y())):
                self._m_slide_timer.stop()
                if self._m_start_rect != self._m_stop_rect:
                    self._m_shake_timer.start()
        self.update()

    def _on_do_shake(self):
        """
        完成晃动动作
        :return: None
        """
        delta = 2.0
        dx1 = dx2 = dy1 = dy2 = 0.0
        if self._m_start_rect.topLeft().x() > self._m_stop_rect.topLeft().x():
            dx1 = -delta
        elif self._m_start_rect.topLeft().x() < self._m_stop_rect.topLeft().x():
            dx1 = delta
        if self._m_start_rect.topLeft().y() > self._m_stop_rect.topLeft().y():
            dy1 = -delta
        elif self._m_start_rect.topLeft().y() < self._m_stop_rect.topLeft().y():
            dy1 = delta
        if self._m_start_rect.bottomRight().x() > self._m_stop_rect.bottomRight().x():
            dx2 = -delta
        elif self._m_start_rect.bottomRight().x() < self._m_stop_rect.bottomRight().x():
            dx2 = delta
        if self._m_start_rect.bottomRight().y() > self._m_stop_rect.bottomRight().y():
            dy2 = -delta
        elif self._m_start_rect.bottomRight().y() < self._m_stop_rect.bottomRight().y():
            dy2 = delta

        self._m_start_rect.adjust(dx1, dy1, dx2, dy2)

        if abs(self._m_start_rect.topLeft().x() - self._m_stop_rect.topLeft().x()) <= delta:
            self._m_start_rect.setLeft(self._m_stop_rect.topLeft().x())
        if abs(self._m_start_rect.topLeft().y() - self._m_stop_rect.topLeft().y()) <= delta:
            self._m_start_rect.setTop(self._m_stop_rect.topLeft().y())
        if abs(self._m_start_rect.bottomRight().x() - self._m_stop_rect.bottomRight().x()) <= delta:
            self._m_start_rect.setRight(self._m_stop_rect.bottomRight().x())
        if abs(self._m_start_rect.bottomRight().y() - self._m_stop_rect.bottomRight().y()) <= delta:
            self._m_start_rect.setBottom(self._m_stop_rect.bottomRight().y())
        if self._m_start_rect == self._m_stop_rect:
            self._m_shake_timer.stop()
        self.update()

    # -----------以下是事件响应函数的重载函数-----------#
    def paintEvent(self, a0: QtGui.QPaintEvent) -> None:
        """
        重载paintEvent函数
        :param a0:
        :return:
        """
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)

        self._draw_bar_background(painter)
        self._draw_item_background(painter)
        self._draw_item_line(painter)
        self._draw_text(painter)

    def resizeEvent(self, a0: QtGui.QResizeEvent) -> None:
        """
        重载resizeEvent函数
        :param a0:
        :return:
        """
        self._adjust_item_size()

    def mousePressEvent(self, a0: QtGui.QMouseEvent) -> None:
        """
        重载mousePressEvent函数
        :param a0: 事件
        :return:None
        """
        self.on_move_to_position(a0.pos())

    def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None:
        """
        重载mouseMoveEvent函数
        :param a0: 事件
        :return: None
        """
        is_on_item = False
        for key, value in self._m_item_maps.items():
            if value[1].contains(a0.pos()):
                self._m_current_hover_index = key
                is_on_item = True
                break
        if not is_on_item:
            self._m_current_hover_index = -1

        self.update()

    def keyPressEvent(self, a0: QtGui.QKeyEvent) -> None:
        """
        重载keyPressEvent函数
        :param a0:
        :return:
        """
        if not self._m_enable_key_move:
            # self.keyPressEvent(a0)
            return
        if a0.key() == Qt.Key_Home:
            self.on_move_to_first_item()
        elif a0.key() == Qt.Key_End:
            self.on_move_to_last_item()
        elif a0.key() == Qt.Key_Up or a0.key() == Qt.Key_Left:
            self.on_move_to_previous_item()
        elif a0.key() == Qt.Key_Down or a0.key() == Qt.Key_Right:
            self.on_move_to_next_item()
        else:
            # self.keyPressEvent(a0)
            return

    # -----------------以下是私有成员函数---------------------#
    def _draw_bar_background(self, p: QPainter):
        """
        绘制导航栏的背景
        :param p: 画刷
        :return: None
        """
        p.save()
        p.setPen(Qt.NoPen)
        lgt = QLinearGradient(QPointF(0, 0), QPointF(0, self.height()))
        lgt.setColorAt(0.0, self._m_bar_start_color)
        lgt.setColorAt(1.0, self._m_bar_end_color)
        p.setBrush(lgt)
        p.drawRoundedRect(self.rect(), self._m_bar_radius, self._m_bar_radius)
        p.restore()

    def _draw_item_background(self, p: QPainter):
        """
        绘制项目的背景
        :param p: 画刷
        :return: None
        """
        if self._m_start_rect.isNull():
            return

        p.save()
        lgt = QLinearGradient(self._m_start_rect.topLeft(), self._m_start_rect.bottomRight())
        lgt.setColorAt(0.0, self._m_item_start_color)
        lgt.setColorAt(1.0, self._m_item_end_color)
        p.setPen(Qt.NoPen)
        p.setBrush(lgt)
        p.drawRoundedRect(self._m_start_rect, self._m_item_radius, self._m_item_radius)

        # 绘制 hover 状态下的item
        if self._m_current_hover_index != -1:
            hover_rect = QRectF(self._m_item_maps[self._m_current_hover_index][1])
            lgt = QLinearGradient(hover_rect.topLeft(), hover_rect.bottomRight())
            lgt.setColorAt(0.0, self._m_item_hover_start_color)
            lgt.setColorAt(1.0, self._m_item_hover_end_color)
            p.setPen(Qt.NoPen)
            p.setBrush(lgt)
            p.drawRoundedRect(hover_rect, self._m_item_radius, self._m_item_radius)
        p.restore()

    def _draw_item_line(self, p: QPainter) -> None:
        """
        绘制项目周边的线条
        :param p: 画刷
        :return: None
        """
        if self._m_start_rect.isNull():
            return

        if self._m_item_line_style == self.ItemLineStyle.ItemNone:
            return
        elif self._m_item_line_style == self.ItemLineStyle.ItemTop:
            p1 = self._m_start_rect.topLeft()
            p2 = self._m_start_rect.topRight()
        elif self._m_item_line_style == self.ItemLineStyle.ItemRight:
            p1 = self._m_start_rect.topRight()
            p2 = self._m_start_rect.bottomRight()
        elif self._m_item_line_style == self.ItemLineStyle.ItemBottom:
            p1 = self._m_start_rect.bottomLeft()
            p2 = self._m_start_rect.bottomRight()
        elif self._m_item_line_style == self.ItemLineStyle.ItemLeft:
            p1 = self._m_start_rect.topLeft()
            p2 = self._m_start_rect.bottomLeft()
        elif self._m_item_line_style == self.ItemLineStyle.ItemRect:
            p1 = self._m_start_rect.topLeft()
            p2 = self._m_start_rect.bottomRight()
        else:
            return

        p.save()
        line_pen = QPen()
        line_pen.setColor(self._m_item_line_color)
        line_pen.setWidth(self._m_item_line_width)
        p.setPen(line_pen)
        if self._m_item_line_style == self.ItemLineStyle.ItemRect:
            p.drawRoundedRect(QRectF(p1, p2), self._m_item_radius, self._m_item_radius)
        else:
            p.drawLine(p1, p2)

        p.restore()

    def _draw_text(self, p: QPainter) -> None:
        """
        绘制项目的名称
        :param p: 画刷
        :return: None
        """
        p.save()
        p.setPen(self._m_item_text_color)
        for key, value in self._m_item_maps.items():
            self._m_item_font.setPointSize(self._m_item_font_size)
            p.setFont(self._m_item_font)
            p.drawText(value[1], Qt.AlignCenter, value[0])
        p.restore()

    def _adjust_item_size(self) -> None:
        """
        调整Item大小
        :return:
        """

        if self._m_fixed:
            return

        item_count = len(self._m_item_maps)

        if self._m_orientation == Qt.Horizontal:
            add_width = 1.0 * (self.width() - self._m_total_text_width) / item_count
            add_height = 1.0 * (self.height() - self._m_total_text_height)
        else:
            add_width = 1.0 * (self.width() - self._m_total_text_width)
            add_height = 1.0 * (self.height() - self._m_total_text_height) / item_count

        dx = dy = 0.0
        for key, value in self._m_item_maps.items():
            # f = QFont()
            fm = QFontMetrics(self._m_item_font)
            text_width = fm.width(value[0])
            text_height = fm.height()
            if self._m_orientation == Qt.Horizontal:
                topLeft = QPointF(dx, 0)
                dx += text_width + self._m_space + add_width
                dy = self._m_total_text_height + add_height
            else:
                topLeft = QPointF(0, dy)
                dx = self._m_total_text_width + add_width
                dy += text_height + self._m_space + add_height

            bottomRight = QPointF(dx, dy)
            text_rect = QRectF(topLeft, bottomRight)
            self._m_item_maps[key] = [value[0], QRectF(text_rect)]
            if key == self._m_current_index:
                self._m_start_rect = text_rect
                self._m_stop_rect = text_rect

        self.update()
Esempio n. 2
0
class TextLineItem(QAbstractGraphicsShapeItem):
    """A one-line text item.

    Default origin point is at the left side of the baseline. This can change if setAlignMode is called. TextLineItem
    also supports drawing text background. Its brush can be set using setBackgroundBrush(). Text must not contain
    newline characters.
    """
    def __init__(self, text=None, parent=None):
        super().__init__(parent)

        self._text = text or ""
        self._elided_text = text or ""
        self._elide_mode = Qt.ElideRight
        self._align_mode = Qt.AlignLeft
        self._max_width = None
        self._font = QFont()
        self._bounding_rect = QRectF()
        """Bounding and drawing rectangle. Determined automatically."""

        self.background = QGraphicsRectItem(self)
        self.background.setPen(Qt.NoPen)
        self.background.setFlag(QGraphicsItem.ItemStacksBehindParent)

        self.adjust()

    def setText(self, text: str, /) -> None:
        if '\n' in text:
            raise ValueError("text must not contain newline characters")

        self.prepareGeometryChange()
        self._text = text
        self.adjust()

    def setElideMode(self, mode: int, /) -> None:
        """Elide mode specifies where the ellipsis should be located when the text is elided. Default value is 
        Qt.ElideRight.
        """
        self.prepareGeometryChange()
        self._elide_mode = mode
        self.adjust()

    def setAlignMode(self, mode: int, /) -> None:
        """Align mode specifies text alignment.

        Text alignment changes the origin point x position:  
        - If mode is Qt.AlignLeft, the origin point is on the left of the baseline.
        - If mode is Qt.AlignHCenter, the origin point is in the center of the baseline.
        - If mode is Qt.AlignRight, the origin point is on the right of the baseline.

        Vertical alignment has no meaning for one line of text and should not be set. Default value is Qt.AlignLeft.
        """
        self.prepareGeometryChange()
        self._align_mode = mode
        self.adjust()

    def setMaximumWidth(self, width, /):
        """Set the maximum width the text is allowed to be. `None` represents unlimited width."""
        self.prepareGeometryChange()
        self._max_width = width
        self.adjust()

    def setFont(self, font: QFont, /) -> None:
        self.prepareGeometryChange()
        self._font = font
        self.adjust()

    def setBackgroundBrush(self, brush: QBrush, /):
        self.background.setBrush(brush)

    def text(self) -> str:
        return self._text

    def elidedText(self) -> str:
        return self._elided_text

    def elideMode(self) -> int:
        return self._elide_mode

    def alignMode(self) -> int:
        return self._align_mode

    def maximumWidth(self):
        """Maximum width the text is allowed to be. `None` represents unlimited width."""
        return self._max_width

    def font(self) -> QFont:
        return self._font

    def backgroundBrush(self):
        return self.background.brush()

    def adjust(self):
        """Adjust the item's geometry in response to changes."""
        metrics = QFontMetricsF(self.font())

        # Get bounding rectangle for full text
        self._bounding_rect = metrics.boundingRect(self.text())

        # Constrain by maximum width
        if self.maximumWidth() is not None:
            self._bounding_rect.setWidth(self.maximumWidth())

        # Compute elided text
        self._elided_text = metrics.elidedText(self.text(), self.elideMode(),
                                               self.boundingRect().width())

        # Get bounding rectangle for elided text
        self._bounding_rect = metrics.boundingRect(self.elidedText())
        # It seems that for small characters like "..." the bounding rect returned is too small. Adjust it by a small
        # value.
        metrics_correction = px(1 / 72)
        self._bounding_rect.adjust(-metrics_correction, 0, metrics_correction,
                                   0)

        # Move origin point according to the alignment
        if self.alignMode() & Qt.AlignLeft:
            self._bounding_rect.moveLeft(0)
        elif self.alignMode() & Qt.AlignRight:
            self._bounding_rect.moveRight(0)
        else:
            self._bounding_rect.moveLeft(-self._bounding_rect.width() / 2)

        # Set background rect
        self.background.setRect(self.boundingRect())

    def boundingRect(self) -> QRectF:
        return self._bounding_rect

    def paint(self, painter: QPainter, option, widget=...) -> None:
        painter.setBrush(self.brush())
        painter.setPen(self.pen())
        painter.setFont(self.font())

        painter.drawText(self.boundingRect(), self.alignMode(),
                         self.elidedText())