handOver2/ui/components/custom_button.py

379 lines
10 KiB
Python

# -*- coding: utf-8 -*-
"""
커스텀 버튼 모듈
다양한 스타일의 커스텀 버튼을 정의합니다.
이 모듈은 다음 버튼 유형을 제공합니다:
- CustomButton: 기본 커스텀 버튼
- IconButton: 아이콘 버튼
- ToggleButton: 토글 버튼
"""
from PySide6.QtWidgets import QPushButton, QGraphicsDropShadowEffect
from PySide6.QtCore import Qt, Signal, QPropertyAnimation, QEasingCurve, Property
from PySide6.QtGui import QColor, QFont, QIcon, QPainter, QLinearGradient
from core.config import ConfigManager
from core.logger import get_logger
logger = get_logger(__name__)
class CustomButton(QPushButton):
"""
커스텀 스타일 버튼
현대적인 디자인의 커스텀 버튼입니다.
다양한 스타일(primary, secondary, danger, success)을 지원합니다.
Attributes:
style_type: 버튼 스타일 유형
Examples:
>>> btn = CustomButton("저장", style_type="primary")
>>> btn.clicked.connect(self.on_save)
"""
STYLES = {
"primary": {
"bg": "#3b82f6",
"bg_hover": "#2563eb",
"bg_pressed": "#1d4ed8",
"text": "#ffffff",
},
"secondary": {
"bg": "#64748b",
"bg_hover": "#475569",
"bg_pressed": "#334155",
"text": "#ffffff",
},
"danger": {
"bg": "#ef4444",
"bg_hover": "#dc2626",
"bg_pressed": "#b91c1c",
"text": "#ffffff",
},
"success": {
"bg": "#22c55e",
"bg_hover": "#16a34a",
"bg_pressed": "#15803d",
"text": "#ffffff",
},
"warning": {
"bg": "#f59e0b",
"bg_hover": "#d97706",
"bg_pressed": "#b45309",
"text": "#ffffff",
},
"outline": {
"bg": "transparent",
"bg_hover": "#f1f5f9",
"bg_pressed": "#e2e8f0",
"text": "#3b82f6",
"border": "#3b82f6",
},
"ghost": {
"bg": "transparent",
"bg_hover": "#f1f5f9",
"bg_pressed": "#e2e8f0",
"text": "#64748b",
},
}
def __init__(
self,
text: str = "",
parent=None,
style_type: str = "primary",
icon: QIcon = None,
fixed_width: int = None,
fixed_height: int = 40
):
super().__init__(text, parent)
self.config = ConfigManager()
self.style_type = style_type
# 기본 설정
self.setCursor(Qt.PointingHandCursor)
self.setFont(QFont("GmarketSans", 13))
if icon:
self.setIcon(icon)
if fixed_width:
self.setFixedWidth(fixed_width)
if fixed_height:
self.setFixedHeight(fixed_height)
self._apply_style()
def apply_style(self, style_type: str):
"""외부에서 동적으로 스타일을 변경할 때 사용"""
self.style_type = style_type
self._apply_style()
def _apply_style(self):
"""스타일 적용"""
style = self.STYLES.get(self.style_type, self.STYLES["primary"])
border = f"border: 2px solid {style.get('border', 'transparent')};"
self.setStyleSheet(f"""
QPushButton {{
background-color: {style['bg']};
color: {style['text']};
{border}
border-radius: 8px;
padding: 8px 20px;
font-weight: 600;
}}
QPushButton:hover {{
background-color: {style['bg_hover']};
}}
QPushButton:pressed {{
background-color: {style['bg_pressed']};
}}
QPushButton:disabled {{
background-color: #94a3b8;
color: #cbd5e1;
}}
""")
def set_style_type(self, style_type: str):
"""스타일 유형 변경"""
self.style_type = style_type
self._apply_style()
class IconButton(QPushButton):
"""
아이콘 버튼
아이콘만 표시되는 원형 또는 사각형 버튼입니다.
Examples:
>>> btn = IconButton("🔄", tooltip="새로고침")
>>> btn.clicked.connect(self.on_refresh)
"""
def __init__(
self,
icon_text: str = "",
parent=None,
size: int = 36,
circle: bool = True,
tooltip: str = ""
):
super().__init__(icon_text, parent)
self.config = ConfigManager()
self._size = size
self._circle = circle
# 기본 설정
self.setFixedSize(size, size)
self.setCursor(Qt.PointingHandCursor)
self.setFont(QFont("Segoe UI Emoji", size // 2))
if tooltip:
self.setToolTip(tooltip)
self._apply_style()
def _apply_style(self):
"""스타일 적용"""
theme = self.config.theme
radius = self._size // 2 if self._circle else 8
if theme == 'dark':
bg = "#334155"
bg_hover = "#475569"
text_color = "#f8fafc"
else:
bg = "#e2e8f0"
bg_hover = "#cbd5e1"
text_color = "#1e293b"
self.setStyleSheet(f"""
QPushButton {{
background-color: {bg};
color: {text_color};
border: none;
border-radius: {radius}px;
}}
QPushButton:hover {{
background-color: {bg_hover};
}}
QPushButton:pressed {{
background-color: {bg};
}}
""")
class ToggleButton(QPushButton):
"""
토글 버튼
켜짐/꺼짐 상태를 전환하는 버튼입니다.
Signals:
toggled_signal: 토글 상태 변경 시그널 (bool)
Examples:
>>> btn = ToggleButton("알림", initial_state=True)
>>> btn.toggled_signal.connect(self.on_toggle)
"""
toggled_signal = Signal(bool)
def __init__(
self,
text: str = "",
parent=None,
initial_state: bool = False,
on_text: str = None,
off_text: str = None
):
super().__init__(text, parent)
self.config = ConfigManager()
self._is_on = initial_state
self._on_text = on_text or text
self._off_text = off_text or text
# 기본 설정
self.setCheckable(True)
self.setChecked(initial_state)
self.setCursor(Qt.PointingHandCursor)
self.setFont(QFont("GmarketSans", 13))
self.setFixedHeight(40)
# 시그널 연결
self.clicked.connect(self._on_clicked)
self._update_state()
def _on_clicked(self):
"""클릭 이벤트"""
self._is_on = self.isChecked()
self._update_state()
self.toggled_signal.emit(self._is_on)
def _update_state(self):
"""상태 업데이트"""
self.setText(self._on_text if self._is_on else self._off_text)
self._apply_style()
def _apply_style(self):
"""스타일 적용"""
if self._is_on:
bg = "#22c55e"
bg_hover = "#16a34a"
else:
bg = "#64748b"
bg_hover = "#475569"
self.setStyleSheet(f"""
QPushButton {{
background-color: {bg};
color: white;
border: none;
border-radius: 8px;
padding: 8px 20px;
font-weight: 600;
}}
QPushButton:hover {{
background-color: {bg_hover};
}}
""")
@property
def is_on(self) -> bool:
"""현재 상태 반환"""
return self._is_on
def set_state(self, state: bool):
"""상태 설정"""
self._is_on = state
self.setChecked(state)
self._update_state()
class GradientButton(QPushButton):
"""
그라데이션 버튼
배경에 그라데이션 효과가 있는 버튼입니다.
"""
def __init__(
self,
text: str = "",
parent=None,
start_color: str = "#3b82f6",
end_color: str = "#8b5cf6"
):
super().__init__(text, parent)
self.start_color = start_color
self.end_color = end_color
# 기본 설정
self.setCursor(Qt.PointingHandCursor)
self.setFont(QFont("GmarketSans", 13, QFont.Bold))
self.setFixedHeight(44)
self._apply_style()
def _apply_style(self):
"""스타일 적용"""
self.setStyleSheet(f"""
QPushButton {{
background: qlineargradient(
x1:0, y1:0, x2:1, y2:0,
stop:0 {self.start_color},
stop:1 {self.end_color}
);
color: white;
border: none;
border-radius: 10px;
padding: 10px 24px;
font-weight: bold;
}}
QPushButton:hover {{
background: qlineargradient(
x1:0, y1:0, x2:1, y2:0,
stop:0 {self._darken(self.start_color)},
stop:1 {self._darken(self.end_color)}
);
}}
QPushButton:pressed {{
background: qlineargradient(
x1:0, y1:0, x2:1, y2:0,
stop:0 {self._darken(self.start_color, 0.2)},
stop:1 {self._darken(self.end_color, 0.2)}
);
}}
""")
@staticmethod
def _darken(hex_color: str, amount: float = 0.1) -> str:
"""색상을 어둡게"""
color = QColor(hex_color)
h, s, l, a = color.getHslF()
l = max(0, l - amount)
color.setHslF(h, s, l, a)
return color.name()