# -*- coding: utf-8 -*- """ 공통 칩 버튼 베이스 - 색상/크기/hover/checked(선택) 시각 처리 - 선택형 칩(Choice)과 필터 칩(Filter)의 공통 기반 """ from __future__ import annotations from dataclasses import dataclass from typing import Optional from PySide6.QtWidgets import QToolButton, QSizePolicy from PySide6.QtCore import Qt, Signal, QSize from core.logger import get_logger logger = get_logger(__name__) try: from ui.styles.style_manager import StyleManager # type: ignore except Exception: StyleManager = None # noqa: N816 @dataclass(frozen=True) class ChipTheme: height: int = 28 padding_x: int = 10 radius: int = 14 font_px: int = 12 max_width: Optional[int] = None class ChipBaseButton(QToolButton): """ 공통 칩 버튼 - key 기반 신호 제공 - 선택형(checked) 시각 지원 """ clicked_key = Signal(str) toggled_key = Signal(str, bool) def __init__( self, text: str, key: str, bg: str = "#64748b", fg: str = "#ffffff", theme: ChipTheme = ChipTheme(), checkable: bool = False, parent=None, ): super().__init__(parent) self._style_manager = StyleManager() if StyleManager else None self.key = key self._bg = bg self._fg = fg self.theme = theme self.setText(text) self.setCursor(Qt.PointingHandCursor) self.setAutoRaise(True) self.setFocusPolicy(Qt.NoFocus) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.setFixedHeight(self.theme.height) self.setMinimumWidth(40) if self.theme.max_width: self.setMaximumWidth(self.theme.max_width) self.setProperty("chipHover", False) # 선택형 지원 self.setCheckable(checkable) # 신호 연결 self.clicked.connect(self._on_clicked) self.toggled.connect(self._on_toggled) self._apply_style() def sizeHint(self) -> QSize: hint = super().sizeHint() h = self.theme.height return QSize(max(hint.width() + 6, 40), h) def enterEvent(self, event): self.setProperty("chipHover", True) self.style().unpolish(self) self.style().polish(self) super().enterEvent(event) def leaveEvent(self, event): self.setProperty("chipHover", False) self.style().unpolish(self) self.style().polish(self) super().leaveEvent(event) def _on_clicked(self): self.clicked_key.emit(self.key) def _on_toggled(self, checked: bool): self.toggled_key.emit(self.key, checked) # checked 시각 반영을 위해 re-polish self.style().unpolish(self) self.style().polish(self) def set_bg(self, bg: str): """배경색 설정""" self._bg = bg self._apply_style() def set_fg(self, fg: str): """전경색 설정""" self._fg = fg self._apply_style() def _apply_style(self): # 폰트(StyleManager가 있으면 dialog.content 기반) if self._style_manager: try: f = self._style_manager.get_font("dialog", "content") self.setFont(f) except Exception: logger.debug("StyleManager 폰트 적용 실패. 시스템 기본 폰트 사용") h = self.theme.height r = self.theme.radius px = self.theme.padding_x self.setStyleSheet(f""" QToolButton {{ background: {self._bg}; color: {self._fg}; border: 1px solid rgba(0,0,0,0.12); border-radius: {r}px; padding-left: {px}px; padding-right: {px}px; min-height: {h}px; max-height: {h}px; font-size: {self.theme.font_px}px; font-weight: 700; }} QToolButton[chipHover="true"] {{ border: 1px solid rgba(255,255,255,0.55); background: qlineargradient( x1:0, y1:0, x2:0, y2:1, stop:0 rgba(255,255,255,0.15), stop:1 rgba(255,255,255,0.00) ), {self._bg}; }} /* 선택됨(checked) 강조 */ QToolButton:checked {{ border: 1px solid rgba(255,255,255,0.80); background: qlineargradient( x1:0, y1:0, x2:0, y2:1, stop:0 rgba(255,255,255,0.25), stop:1 rgba(0,0,0,0.10) ), {self._bg}; }} QToolButton:pressed {{ background: qlineargradient( x1:0, y1:0, x2:0, y2:1, stop:0 rgba(0,0,0,0.18), stop:1 rgba(0,0,0,0.05) ), {self._bg}; }} """)