# -*- coding: utf-8 -*- """novelWriter Addition QSwitch novelWriter – Addition QSwitch ================================ A custom Qt switch button File History: Created: 2020-05-03 [0.4.5] This class is based on Stack Overflow code by Stefan Scherfke, based again on contribution by IMAN4K, published under license CC BY-SA 4.0. https://stackoverflow.com/a/51825815/5825851 The above code has been modified to integrate with novelWriter, and re-released under compatible GPLv3 (see creativecommons.org). This file is a part of novelWriter Copyright 2020, Veronica Berglyd Olsen This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/. """ import logging import nw from PyQt5.QtCore import QPropertyAnimation, QRectF, QSize, Qt, pyqtProperty from PyQt5.QtGui import QPainter from PyQt5.QtWidgets import ( QAbstractButton, QApplication, QHBoxLayout, QSizePolicy, QWidget, ) logger = logging.getLogger(__name__) class Switch(QAbstractButton): def __init__(self, parent=None, track_radius=10, thumb_radius=8): super().__init__(parent=parent) self.setCheckable(True) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self._track_radius = track_radius self._thumb_radius = thumb_radius self._margin = max(0, self._thumb_radius - self._track_radius) self._base_offset = max(self._thumb_radius, self._track_radius) self._end_offset = { True: lambda: self.width() - self._base_offset, False: lambda: self._base_offset, } self._offset = self._base_offset palette = self.palette() if self._thumb_radius > self._track_radius: self._track_color = { True: palette.highlight(), False: palette.dark(), } self._thumb_color = { True: palette.highlight(), False: palette.light(), } self._text_color = { True: palette.highlightedText().color(), False: palette.dark().color(), } self._thumb_text = { True: '', False: '', } self._track_opacity = 0.5 else: self._thumb_color = { True: palette.highlightedText(), False: palette.light(), } self._track_color = { True: palette.highlight(), False: palette.dark(), } self._text_color = { True: palette.highlight().color(), False: palette.dark().color(), } self._thumb_text = { True: '✔', False: '✕', } self._track_opacity = 1 @pyqtProperty(int) def offset(self): return self._offset @offset.setter def offset(self, value): self._offset = value self.update() def sizeHint(self): # pylint: disable=invalid-name return QSize( 4 * self._track_radius + 2 * self._margin, 2 * self._track_radius + 2 * self._margin, ) def setChecked(self, checked): super().setChecked(checked) self.offset = self._end_offset[checked]() def resizeEvent(self, event): super().resizeEvent(event) self.offset = self._end_offset[self.isChecked()]() def paintEvent(self, event): # pylint: disable=invalid-name, unused-argument p = QPainter(self) p.setRenderHint(QPainter.Antialiasing, True) p.setPen(Qt.NoPen) track_opacity = self._track_opacity thumb_opacity = 1.0 text_opacity = 1.0 if self.isEnabled(): track_brush = self._track_color[self.isChecked()] thumb_brush = self._thumb_color[self.isChecked()] text_color = self._text_color[self.isChecked()] else: track_opacity *= 0.8 track_brush = self.palette().shadow() thumb_brush = self.palette().mid() text_color = self.palette().shadow().color() p.setBrush(track_brush) p.setOpacity(track_opacity) p.drawRoundedRect( self._margin, self._margin, self.width() - 2 * self._margin, self.height() - 2 * self._margin, self._track_radius, self._track_radius, ) p.setBrush(thumb_brush) p.setOpacity(thumb_opacity) p.drawEllipse( self.offset - self._thumb_radius, self._base_offset - self._thumb_radius, 2 * self._thumb_radius, 2 * self._thumb_radius, ) p.setPen(text_color) p.setOpacity(text_opacity) font = p.font() font.setPixelSize(1.5 * self._thumb_radius) p.setFont(font) p.drawText( QRectF( self.offset - self._thumb_radius, self._base_offset - self._thumb_radius, 2 * self._thumb_radius, 2 * self._thumb_radius, ), Qt.AlignCenter, self._thumb_text[self.isChecked()], ) def mouseReleaseEvent(self, event): # pylint: disable=invalid-name super().mouseReleaseEvent(event) if event.button() == Qt.LeftButton: anim = QPropertyAnimation(self, b'offset', self) anim.setDuration(120) anim.setStartValue(self.offset) anim.setEndValue(self._end_offset[self.isChecked()]()) anim.start() def enterEvent(self, event): # pylint: disable=invalid-name self.setCursor(Qt.PointingHandCursor) super().enterEvent(event) # def main(): # app = QApplication([]) # # Thumb size < track size (Gitlab style) # s1 = Switch() # s1.toggled.connect(lambda c: print('toggled', c)) # s1.clicked.connect(lambda c: print('clicked', c)) # s1.pressed.connect(lambda: print('pressed')) # s1.released.connect(lambda: print('released')) # s2 = Switch() # s2.setEnabled(False) # # Thumb size > track size (Android style) # s3 = Switch(thumb_radius=11, track_radius=8) # s4 = Switch(thumb_radius=11, track_radius=8) # s4.setEnabled(False) # l = QHBoxLayout() # l.addWidget(s1) # l.addWidget(s2) # l.addWidget(s3) # l.addWidget(s4) # w = QWidget() # w.setLayout(l) # w.show() # app.exec() # if __name__ == '__main__': # main()