379 lines
10 KiB
Python
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()
|
|
|
|
|