from PySide6.QtCore import Qt, QRect, QPropertyAnimation, Property, Signal, QPoint, QSize from PySide6.QtGui import QPainter, QColor, QFont from PySide6.QtWidgets import QWidget, QCheckBox, QAbstractButton class ToggleSwitch(QAbstractButton): clicked = Signal(bool) stateChanged = Signal(int) # Qt.Checked 또는 Qt.Unchecked 값을 전달하는 신호 추가 def __init__(self, parent=None, blue_theme=False): super(ToggleSwitch, self).__init__(parent) self.setFixedSize(70, 30) self._checked = False self._circle_color_checked = QColor('red') self._circle_color_unchecked = QColor('gray') self._background_color = QColor('white') self._circle_pos = QPoint(0, 0) self.animation = QPropertyAnimation(self, b"circle_pos") self.animation.setDuration(250) self.blue_theme = blue_theme # 추가: 토글 상태에 따른 텍스트 self._on_text = "ON" self._off_text = "OFF" self._text_font = QFont("Arial", 10, QFont.Bold) self._init_position() # clicked 신호가 발생할 때 stateChanged 신호도 함께 발생시킴 self.clicked.connect(self._handle_clicked) def _handle_clicked(self, checked): """ clicked 신호 처리 및 stateChanged 신호 발생 """ self.stateChanged.emit(Qt.Checked if checked else Qt.Unchecked) @Property(QPoint) def circle_pos(self): return self._circle_pos @circle_pos.setter def circle_pos(self, pos): self._circle_pos = pos self.update() def _init_position(self): if self._checked: self._circle_pos.setX(30) else: self._circle_pos.setX(0) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: old_state = self._checked self._checked = not self._checked self.clicked.emit(self._checked) if old_state != self._checked: # 상태가 변경되면 stateChanged 신호 발생 self.stateChanged.emit(Qt.Checked if self._checked else Qt.Unchecked) self._update_animation() self.update() super(ToggleSwitch, self).mousePressEvent(event) def _update_animation(self): if self._checked: self.animation.setStartValue(QPoint(0, 0)) self.animation.setEndValue(QPoint(30, 0)) else: self.animation.setStartValue(QPoint(30, 0)) self.animation.setEndValue(QPoint(0, 0)) self.animation.start() def paintEvent(self, event): painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) painter.setPen(Qt.NoPen) # 배경 색상 설정 if self.isChecked(): if self.blue_theme: self._background_color = QColor(0, 120, 215) # 활성화 상태: 진한 파란색 else: self._background_color = QColor(33, 150, 243) # 활성화 상태: 파란색 else: self._background_color = QColor(180, 180, 180) # 비활성화 상태: 회색 # 3D 효과를 위한 그림자 그리기 shadow_color = QColor(0, 0, 0, 50) # 반투명 검정색 painter.setPen(Qt.NoPen) painter.setBrush(shadow_color) painter.drawRoundedRect(2, 2, self.width(), self.height(), 15, 15) # 배경 그리기 painter.setPen(Qt.NoPen) painter.setBrush(self._background_color) painter.drawRoundedRect(0, 0, self.width(), self.height(), 15, 15) # 토글 손잡이 그리기 # 손잡이 그림자 painter.setBrush(QColor(0, 0, 0, 30)) if self.isChecked(): painter.drawEllipse(self.width() - 27, 3, 26, 26) else: painter.drawEllipse(3, 3, 26, 26) # 손잡이 본체 painter.setBrush(QColor(255, 255, 255)) if self.isChecked(): painter.drawEllipse(self.width() - 28, 2, 26, 26) else: painter.drawEllipse(2, 2, 26, 26) # 텍스트 그리기: 토글 상태에 따라 왼쪽(ON) 또는 오른쪽(OFF) 정렬 painter.setFont(self._text_font) painter.setPen(QColor("black")) if self._checked: text = self._on_text alignment = Qt.AlignLeft | Qt.AlignVCenter else: text = self._off_text alignment = Qt.AlignRight | Qt.AlignVCenter painter.drawText(self.rect(), alignment, text) def setChecked(self, checked): """ ToggleSwitch의 상태를 설정합니다. Args: checked (bool): True로 설정하면 스위치를 체크 상태로, False로 설정하면 언체크 상태로 변경합니다. """ if self._checked != checked: self._checked = checked # setChecked에서는 stateChanged 시그널만 발생시키고 clicked 시그널은 발생시키지 않음 self.stateChanged.emit(Qt.Checked if self._checked else Qt.Unchecked) self._update_animation() self.update() def isChecked(self): return self._checked def sizeHint(self): """위젯 크기 힌트""" return QSize(60, 30) def setState(self, state): """ToggleSwitch의 상태를 설정합니다. Args: state (bool): True로 설정하면 스위치를 체크 상태로, False로 설정하면 언체크 상태로 변경합니다. """ if self._checked != state: self._checked = state self._update_animation() # clicked 시그널을 발생시키지 않고 stateChanged 시그널만 발생 self.stateChanged.emit(Qt.Checked if self._checked else Qt.Unchecked) self.update() # 텍스트를 설정하는 메서드들 def setOnText(self, text): self._on_text = text self.update() def setOffText(self, text): self._off_text = text self.update() def get_toggle_style(): """토글 다이얼로그의 스타일을 반환합니다.""" return """ /* 전체 다이얼로그 스타일 */ QDialog { background-color: #f5f5f7; border-radius: 10px; } /* 탭 위젯 스타일 */ QTabWidget { background-color: #ffffff; border: none; } QTabWidget::pane { background-color: #ffffff; border: 1px solid #e0e0e0; border-radius: 5px; } QTabBar::tab { background-color: #f0f0f0; color: #505050; padding: 10px 20px; border: 1px solid #e0e0e0; border-bottom: none; border-top-left-radius: 4px; border-top-right-radius: 4px; margin-right: 2px; font-weight: bold; } QTabBar::tab:selected { background-color: #ffffff; color: #2196f3; border-bottom: 2px solid #2196f3; } QTabBar::tab:hover:!selected { background-color: #e6e6e6; } /* 토글 스위치 스타일 */ QLabel { color: #404040; font-size: 13px; font-weight: 500; } /* 설명 영역 스타일 */ QTextEdit, QTextBrowser { background-color: #ffffff; border: 1px solid #e0e0e0; border-radius: 5px; padding: 5px; color: #505050; } /* 버튼 스타일 */ QPushButton { background-color: #2196f3; color: white; border: none; border-radius: 4px; padding: 8px 16px; font-weight: bold; } QPushButton:hover { background-color: #1e88e5; } QPushButton:pressed { background-color: #1976d2; } QPushButton:disabled { background-color: #90caf9; } /* 슬라이더 스타일 */ QSlider::groove:horizontal { border: 1px solid #999999; height: 8px; background: #f0f0f0; margin: 2px 0; border-radius: 4px; } QSlider::handle:horizontal { background: #2196f3; border: 1px solid #5c5c5c; width: 18px; margin: -5px 0; border-radius: 9px; } /* 입력 필드 스타일 */ QLineEdit, QSpinBox { border: 1px solid #e0e0e0; border-radius: 4px; padding: 5px; background-color: #ffffff; } QLineEdit:focus, QSpinBox:focus { border: 1px solid #2196f3; } /* 스크롤바 스타일 */ QScrollBar:vertical { border: none; background: #f0f0f0; width: 10px; margin: 0px; } QScrollBar::handle:vertical { background: #c0c0c0; min-height: 20px; border-radius: 5px; } QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { border: none; background: none; } QScrollBar:horizontal { border: none; background: #f0f0f0; height: 10px; margin: 0px; } QScrollBar::handle:horizontal { background: #c0c0c0; min-width: 20px; border-radius: 5px; } QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { border: none; background: none; } """ def get_main_style(): """메인 UI의 스타일을 반환합니다.""" return """ /* 메인 윈도우 스타일 */ QMainWindow { background-color: #f5f5f7; } /* 중앙 위젯 스타일 */ QWidget { background-color: #f5f5f7; color: #404040; } /* 버튼 스타일 */ QPushButton { background-color: #2196f3; color: white; border: none; border-radius: 4px; padding: 10px 20px; font-weight: bold; font-size: 13px; min-width: 100px; } QPushButton:hover { background-color: #1e88e5; } QPushButton:pressed { background-color: #1976d2; } QPushButton:disabled { background-color: #90caf9; } /* 메인 버튼 스타일 - 크고 강조된 버튼 */ #start_chrome_button, #PercentyJob_button { background-color: #4caf50; font-size: 14px; padding: 12px 24px; min-width: 150px; } #start_chrome_button:hover, #PercentyJob_button:hover { background-color: #43a047; } #start_chrome_button:pressed, #PercentyJob_button:pressed { background-color: #388e3c; } /* 그룹 박스 스타일 */ QGroupBox { border: 1px solid #e0e0e0; border-radius: 5px; margin-top: 1ex; font-weight: bold; color: #404040; } QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top center; padding: 0 3px; } /* 레이블 스타일 */ QLabel { color: #505050; font-size: 13px; } /* 입력 필드 스타일 */ QLineEdit, QSpinBox, QComboBox { border: 1px solid #e0e0e0; border-radius: 4px; padding: 8px; background-color: #ffffff; } QLineEdit:focus, QSpinBox:focus, QComboBox:focus { border: 1px solid #2196f3; } /* 프로그래스 바 스타일 */ QProgressBar { border: 1px solid #e0e0e0; border-radius: 4px; background-color: #f0f0f0; text-align: center; padding: 2px; height: 25px; } QProgressBar::chunk { background-color: #2196f3; border-radius: 3px; } /* 스크롤 영역 스타일 */ QScrollArea { border: none; } /* 콤보 박스 스타일 */ QComboBox { border: 1px solid #e0e0e0; border-radius: 4px; padding: 8px; background-color: #ffffff; min-width: 100px; } QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: top right; width: 20px; border-left-width: 1px; border-left-color: #e0e0e0; border-left-style: solid; } QComboBox QAbstractItemView { border: 1px solid #e0e0e0; background-color: white; selection-background-color: #2196f3; selection-color: white; } /* 체크박스 스타일 */ QCheckBox { spacing: 5px; color: #505050; } QCheckBox::indicator { width: 18px; height: 18px; } QCheckBox::indicator:unchecked { border: 2px solid #e0e0e0; background-color: white; border-radius: 3px; } QCheckBox::indicator:checked { border: 2px solid #2196f3; background-color: #2196f3; border-radius: 3px; } /* 메뉴 바 스타일 */ QMenuBar { background-color: #ffffff; border-bottom: 1px solid #e0e0e0; } QMenuBar::item { spacing: 3px; padding: 5px 15px; background: transparent; color: #505050; } QMenuBar::item:selected { background: #e0e0e0; color: #2196f3; } QMenu { background-color: #ffffff; border: 1px solid #e0e0e0; } QMenu::item { padding: 5px 20px 5px 20px; } QMenu::item:selected { background-color: #e0e0e0; color: #2196f3; } /* 상태 바 스타일 */ QStatusBar { background-color: #f0f0f0; color: #505050; } """