Loading config/stylesheet.css +8 −8 Original line number Diff line number Diff line Loading @@ -646,36 +646,36 @@ QScrollBar::handle:vertical:hover,QScrollBar::handle:horizontal:hover { /*纵向滚动条下部分块*/ QScrollBar::add-page:vertical { width: 10px; width: 0px; background: transparent; } /*横向滚动条后面部分块*/ QScrollBar::add-page:horizontal { height: 10px; height: 0px; background: transparent; } /*纵向滚动条上面部分块*/ QScrollBar::sub-page:vertical { width: 10px; width: 0px; background: transparent; } /*横向滚动条左部分块*/ QScrollBar::sub-page:horizontal { height: 10px; height: 0px; background: transparent; } QScrollBar::sub-line { height: 12px; width: 10px; height: 0px; width: 0px; background: transparent; subcontrol-position: top; } /*纵向滚动条顶部三角形位置*/ QScrollBar::add-line { height: 10px; width: 1px; height: 0px; width: 0px; background: transparent; subcontrol-position: top; } Loading ui/canvas.py +11 −3 Original line number Diff line number Diff line Loading @@ -15,7 +15,7 @@ from .misc import ndarray2pixmap from .config_proj import ProjImgTrans from .textitem import TextBlkItem, TextBlock from .texteditshapecontrol import TextBlkShapeControl from .stylewidgets import FadeLabel from .stylewidgets import FadeLabel, ScrollBar from .image_edit import ImageEditMode, DrawingLayer, StrokeImgItem from .page_search_widget import PageSearchWidget from utils import shared as C Loading Loading @@ -80,6 +80,16 @@ class CustomGV(QGraphicsView): ctrl_released = Signal() canvas: QGraphicsScene = None def __init__(self, parent=None): super().__init__(parent) ScrollBar(Qt.Orientation.Horizontal, self, fadeout=True) ScrollBar(Qt.Orientation.Vertical, self, fadeout=True) self.setAlignment(Qt.AlignmentFlag.AlignCenter) self.setDragMode(QGraphicsView.DragMode.ScrollHandDrag) # self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) # self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) def wheelEvent(self, event : QWheelEvent) -> None: # qgraphicsview always scroll content according to wheelevent # which is not desired when scaling img Loading Loading @@ -203,8 +213,6 @@ class Canvas(QGraphicsScene): self.editing_textblkitem: TextBlkItem = None self.gv = CustomGV(self) self.gv.setAlignment(Qt.AlignmentFlag.AlignCenter) self.gv.setDragMode(QGraphicsView.DragMode.ScrollHandDrag) self.gv.scale_down_signal.connect(self.scaleDown) self.gv.scale_up_signal.connect(self.scaleUp) self.gv.scale_with_value.connect(self.scaleBy) Loading ui/scenetext_manager.py +1 −1 Original line number Diff line number Diff line Loading @@ -284,7 +284,7 @@ class TextPanel(Widget): self.formatpanel = FontFormatPanel(app, self) layout.addWidget(self.formatpanel) layout.addWidget(self.textEditList) layout.setContentsMargins(0, 0, 5, 0) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(7) layout.setAlignment(Qt.AlignmentFlag.AlignCenter) Loading ui/stylewidgets.py +421 −9 Original line number Diff line number Diff line from qtpy.QtWidgets import QGraphicsOpacityEffect, QFrame, QWidget, QComboBox, QLabel, QSizePolicy, QDialog, QProgressBar, QMessageBox, QVBoxLayout, QStyle, QSlider, QProxyStyle, QStyle, QStyleOptionSlider, QColorDialog, QPushButton from qtpy.QtCore import Qt, QPropertyAnimation, QEasingCurve, QPointF, QRect, QRectF, Signal, QPoint, Property from qtpy.QtWidgets import QApplication, QAbstractScrollArea, QGraphicsOpacityEffect, QFrame, QWidget, QComboBox, QLabel, QSizePolicy, QDialog, QProgressBar, QMessageBox, QVBoxLayout, QStyle, QSlider, QHBoxLayout, QStyle, QStyleOptionSlider, QColorDialog, QPushButton from qtpy.QtCore import QEvent, Qt, QPropertyAnimation, QEasingCurve, QTimer, QRect, QRectF, Signal, QPoint, Property, QAbstractAnimation from qtpy.QtGui import QFontMetrics, QMouseEvent, QShowEvent, QWheelEvent, QPainter, QFontMetrics, QColor from typing import List, Union, Tuple Loading Loading @@ -290,6 +290,7 @@ class Slider(QSlider): def __init__(self, orientation: Qt.Orientation, parent: QWidget = None): super().__init__(orientation, parent=parent) self.hovering = False self._postInit() def _postInit(self): Loading Loading @@ -349,15 +350,12 @@ class Slider(QSlider): else: self._drawVerticalGroove(painter) if hasattr(self, 'draw_content'): if hasattr(self, 'draw_content') and self.hovering: # its a bad idea to display text like this, but I leave it as it is for now option = QStyleOptionSlider() self.initStyleOption(option) if not option.state & QStyle.State_MouseOver: return rect = self.style().subControlRect( QStyle.CC_Slider, option, QStyle.SC_SliderHandle, self) rect = slider_subcontrol_rect(rect, self) Loading @@ -379,9 +377,7 @@ class Slider(QSlider): dx = dy = 0 dy = self.height() - fm.height() + fm.descent() painter.drawText( dx, dy, value_str, ) painter.drawText(dx, dy, value_str) if self.draw_content is not None: painter.drawText(0, dy, self.draw_content, ) Loading Loading @@ -412,6 +408,16 @@ class Slider(QSlider): def resizeEvent(self, e): self._adjustHandlePos() def enterEvent(self, event) -> None: self.hovering = True self.update() return super().enterEvent(event) def leaveEvent(self, event) -> None: self.hovering = False self.update() return super().leaveEvent(event) class PaintQSlider(Slider): Loading Loading @@ -551,3 +557,409 @@ class TextChecker(QLabel): self.checkStateChanged.emit(self.checked) class ScrollBarGroove(QWidget): """ Scroll bar groove """ def __init__(self, orient: Qt.Orientation, parent): super().__init__(parent=parent) if orient == Qt.Vertical: self.setFixedWidth(12) self.setLayout(QVBoxLayout(self)) self.layout().addStretch(1) self.layout().setContentsMargins(0, 3, 0, 3) else: self.setFixedHeight(12) self.setLayout(QHBoxLayout(self)) self.layout().addStretch(1) self.layout().setContentsMargins(3, 0, 3, 0) self.opacityEffect = QGraphicsOpacityEffect(self) self.opacityAni = QPropertyAnimation(self.opacityEffect, b'opacity', self) self.setGraphicsEffect(self.opacityEffect) self.opacityEffect.setOpacity(0) def fadeIn(self): self.opacityAni.setEndValue(1) self.opacityAni.setDuration(150) self.opacityAni.start() def fadeOut(self): self.opacityAni.setEndValue(0) self.opacityAni.setDuration(150) self.opacityAni.start() def paintEvent(self, e): painter = QPainter(self) painter.setRenderHints(QPainter.Antialiasing) painter.setPen(Qt.NoPen) painter.setBrush(QColor(0, 0, 0, 30)) painter.drawRoundedRect(self.rect(), 6, 6) # ScrollBarHandle and ScrollBar are modified from https://github.com/zhiyiYo/PyQt-Fluent-Widgets/blob/master/qfluentwidgets/components/widgets/scroll_bar.py class ScrollBarHandle(QWidget): """ Scroll bar handle """ def __init__(self, orient: Qt.Orientation, parent=None, fadeout: bool = False): super().__init__(parent) self.orient = orient if fadeout: self.effect = effect = QGraphicsOpacityEffect(self, opacity=1.0) self.setGraphicsEffect(effect) self.fadeAnimation = QPropertyAnimation( self, propertyName=b"opacity", targetObject=effect, duration=300, startValue=1.0, endValue=0., ) # self.fadeAnimation.setEasingCurve(QEasingCurve.Type.InQuint) self.fadeAnimation.finished.connect(self.hide) fixsize = 5 self.anime_timer = QTimer(self) self.anime_timer.setSingleShot(True) self.anime_timer.timeout.connect(self.start_fade_animation) else: fixsize = 3 if orient == Qt.Vertical: self.setFixedWidth(fixsize) else: self.setFixedHeight(fixsize) self.fadeout = fadeout def start_fade_animation(self): self.show() if self.fadeAnimation.state() == QAbstractAnimation.State.Running: self.fadeAnimation.stop() self.fadeAnimation.start() def prepareFadeout(self): self.anime_timer.stop() self.anime_timer.start(700) if self.isHidden(): self.show() if self.fadeAnimation.state() == QAbstractAnimation.State.Running: self.fadeAnimation.stop() if self.effect.opacity() != 1.: self.effect.setOpacity(1.) def stopFadeout(self): if self.fadeAnimation.state() == QAbstractAnimation.State.Running: self.fadeAnimation.stop() self.anime_timer.stop() self.show() if self.effect.opacity() != 1.: self.effect.setOpacity(1.) def paintEvent(self, e): painter = QPainter(self) painter.setRenderHints(QPainter.Antialiasing) painter.setPen(Qt.NoPen) r = self.width() / 2 if self.orient == Qt.Vertical else self.height() / 2 c = QColor(0, 0, 0, 90) painter.setBrush(c) painter.drawRoundedRect(self.rect(), r, r) class ScrollBar(QWidget): """ Fluent scroll bar """ rangeChanged = Signal(tuple) valueChanged = Signal(int) sliderPressed = Signal() sliderReleased = Signal() sliderMoved = Signal() def __init__(self, orient: Qt.Orientation, parent: QAbstractScrollArea, fadeout: bool = False): super().__init__(parent) self.groove = ScrollBarGroove(orient, self) self.handle = ScrollBarHandle(orient, self, fadeout) self.timer = QTimer(self) self.scroll_area = parent self.fadeout = fadeout self._orientation = orient self._singleStep = 1 self._pageStep = 50 self._padding = 0 self._minimum = 0 self._maximum = 0 self._value = 0 self._isPressed = False self.isEnter = False self._isExpanded = False self._pressedPos = QPoint() self._isForceHidden = False if orient == Qt.Vertical: self.partnerBar = parent.verticalScrollBar() QAbstractScrollArea.setVerticalScrollBarPolicy(parent, Qt.ScrollBarAlwaysOff) else: self.partnerBar = parent.horizontalScrollBar() QAbstractScrollArea.setHorizontalScrollBarPolicy(parent, Qt.ScrollBarAlwaysOff) self.__initWidget(parent) def __initWidget(self, parent): self.groove.opacityAni.valueChanged.connect(self._onOpacityAniValueChanged) self.partnerBar.rangeChanged.connect(self.setRange) self.partnerBar.valueChanged.connect(self._onValueChanged) self.valueChanged.connect(self.partnerBar.setValue) parent.installEventFilter(self) self.setRange(self.partnerBar.minimum(), self.partnerBar.maximum()) self.setVisible(self.maximum() > 0 and not self._isForceHidden) self._adjustPos(self.parent().size()) def _onPageUp(self): self.setValue(self.value() - self.pageStep()) def _onPageDown(self): self.setValue(self.value() + self.pageStep()) def _onValueChanged(self, value): self.val = value if self.fadeout and not self.isEnter: self.handle.prepareFadeout() def value(self): return self._value @Property(int, notify=valueChanged) def val(self): return self._value @val.setter def val(self, value: int): if value == self.value(): return value = max(self.minimum(), min(value, self.maximum())) self._value = value self.valueChanged.emit(value) # adjust the position of handle self._adjustHandlePos() def minimum(self): return self._minimum def maximum(self): return self._maximum def orientation(self): return self._orientation def pageStep(self): return self._pageStep def singleStep(self): return self._singleStep def isSliderDown(self): return self._isPressed def setValue(self, value: int): self.val = value def setMinimum(self, min: int): if min == self.minimum(): return self._minimum = min self.rangeChanged.emit((min, self.maximum())) def setMaximum(self, max: int): if max == self.maximum(): return self._maximum = max self.rangeChanged.emit((self.minimum(), max)) def setRange(self, min: int, max: int): if min > max or (min == self.minimum() and max == self.maximum()): return self.setMinimum(min) self.setMaximum(max) self._adjustHandleSize() self._adjustHandlePos() self.setVisible(max > 0 and not self._isForceHidden) self.rangeChanged.emit((min, max)) def setPageStep(self, step: int): if step >= 1: self._pageStep = step def setSingleStep(self, step: int): if step >= 1: self._singleStep = step def setSliderDown(self, isDown: bool): self._isPressed = True if isDown: self.sliderPressed.emit() else: self.sliderReleased.emit() def expand(self): """ expand scroll bar """ if self._isExpanded or not self.isEnter: return self._isExpanded = True self.groove.fadeIn() def collapse(self): """ collapse scroll bar """ if not self._isExpanded or self.isEnter: return self._isExpanded = False self.groove.fadeOut() def enterEvent(self, e): self.isEnter = True self.timer.stop() self.timer.singleShot(200, self.expand) if self.fadeout: self.handle.stopFadeout() def leaveEvent(self, e): self.isEnter = False self.timer.stop() self.timer.singleShot(200, self.collapse) if self.fadeout: self.handle.prepareFadeout() def eventFilter(self, obj, e: QEvent): if obj is not self.parent(): return super().eventFilter(obj, e) # adjust the position of slider if e.type() == QEvent.Resize: self._adjustPos(e.size()) return super().eventFilter(obj, e) def resizeEvent(self, e): self.groove.resize(self.size()) def mousePressEvent(self, e: QMouseEvent): super().mousePressEvent(e) self._isPressed = True self._pressedPos = e.pos() if self.childAt(e.pos()) is self.handle or not self._isSlideResion(e.pos()): return if self.orientation() == Qt.Vertical: if e.pos().y() > self.handle.geometry().bottom(): value = e.pos().y() - self.handle.height() - self._padding else: value = e.pos().y() - self._padding else: if e.pos().x() > self.handle.geometry().right(): value = e.pos().x() - self.handle.width() - self._padding else: value = e.pos().x() - self._padding self.setValue(int(value / max(self._slideLength(), 1) * self.maximum())) self.sliderPressed.emit() def mouseReleaseEvent(self, e): super().mouseReleaseEvent(e) self._isPressed = False self.sliderReleased.emit() def mouseMoveEvent(self, e: QMouseEvent): if self.orientation() == Qt.Vertical: dv = e.pos().y() - self._pressedPos.y() else: dv = e.pos().x() - self._pressedPos.x() # don't use `self.setValue()`, because it could be reimplemented dv = int(dv / max(self._slideLength(), 1) * (self.maximum() - self.minimum())) ScrollBar.setValue(self, self.value() + dv) self._pressedPos = e.pos() self.sliderMoved.emit() def _adjustPos(self, size): if self.orientation() == Qt.Vertical: self.resize(12, size.height() - 2) self.move(size.width() - 13, 1) else: self.resize(size.width() - 2, 12) self.move(1, size.height() - 13) def _adjustHandleSize(self): p = self.parent() if self.orientation() == Qt.Vertical: total = self.maximum() - self.minimum() + p.height() s = int(self._grooveLength() * p.height() / max(total, 1)) self.handle.setFixedHeight(max(30, s)) else: total = self.maximum() - self.minimum() + p.width() s = int(self._grooveLength() * p.width() / max(total, 1)) self.handle.setFixedWidth(max(30, s)) def _adjustHandlePos(self): total = max(self.maximum() - self.minimum(), 1) delta = int(self.value() / total * self._slideLength()) if self.orientation() == Qt.Vertical: x = self.width() - self.handle.width() - 3 self.handle.move(x, self._padding + delta) else: y = self.height() - self.handle.height() - 3 self.handle.move(self._padding + delta, y) def _grooveLength(self): if self.orientation() == Qt.Vertical: return self.height() - 2 * self._padding return self.width() - 2 * self._padding def _slideLength(self): if self.orientation() == Qt.Vertical: return self._grooveLength() - self.handle.height() return self._grooveLength() - self.handle.width() def _isSlideResion(self, pos: QPoint): if self.orientation() == Qt.Vertical: return self._padding <= pos.y() <= self.height() - self._padding return self._padding <= pos.x() <= self.width() - self._padding def _onOpacityAniValueChanged(self): if not self.fadeout: opacity = self.groove.opacityEffect.opacity() if self.orientation() == Qt.Vertical: self.handle.setFixedWidth(int(3 + opacity * 3)) else: self.handle.setFixedHeight(int(3 + opacity * 3)) self._adjustHandlePos() def setForceHidden(self, isHidden: bool): """ whether to force the scrollbar to be hidden """ self._isForceHidden = isHidden self.setVisible(self.maximum() > 0 and not isHidden) def wheelEvent(self, e): QApplication.sendEvent(self.parent().viewport(), e) No newline at end of file ui/textedit_area.py +10 −5 Original line number Diff line number Diff line from typing import List, Union from qtpy.QtWidgets import QMenu, QTextEdit, QScrollArea, QGraphicsDropShadowEffect, QVBoxLayout, QApplication, QHBoxLayout from qtpy.QtWidgets import QTextEdit, QScrollArea, QGraphicsDropShadowEffect, QVBoxLayout, QApplication, QHBoxLayout from qtpy.QtCore import Signal, Qt, QMimeData, QEvent, QPoint from qtpy.QtGui import QColor, QFocusEvent, QInputMethodEvent, QDragEnterEvent, QDragMoveEvent, QDropEvent, QKeyEvent, QTextCursor, QMouseEvent, QDrag, QPixmap, QKeySequence import keyboard import webbrowser import numpy as np from .stylewidgets import Widget, SeparatorWidget, ClickableLabel, IgnoreMouseLabel from .stylewidgets import Widget, SeparatorWidget, ClickableLabel, IgnoreMouseLabel, ScrollBar from .textitem import TextBlock from utils.config import pcfg import webbrowser import numpy as np STYLE_TRANSPAIR_CHECKED = "background-color: rgba(30, 147, 229, 20%);" Loading Loading @@ -401,8 +401,13 @@ class TextEditListScrollArea(QScrollArea): super().__init__(*args, **kwargs) self.scrollContent = Widget() self.setWidget(self.scrollContent) # ScrollBar(Qt.Orientation.Horizontal, self) ScrollBar(Qt.Orientation.Vertical, self) self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) vlayout = QVBoxLayout() vlayout.setContentsMargins(0, 0, 0, 0) vlayout.setContentsMargins(0, 0, 3, 0) vlayout.setAlignment(Qt.AlignmentFlag.AlignTop) vlayout.setSpacing(0) vlayout.addStretch(1) Loading Loading
config/stylesheet.css +8 −8 Original line number Diff line number Diff line Loading @@ -646,36 +646,36 @@ QScrollBar::handle:vertical:hover,QScrollBar::handle:horizontal:hover { /*纵向滚动条下部分块*/ QScrollBar::add-page:vertical { width: 10px; width: 0px; background: transparent; } /*横向滚动条后面部分块*/ QScrollBar::add-page:horizontal { height: 10px; height: 0px; background: transparent; } /*纵向滚动条上面部分块*/ QScrollBar::sub-page:vertical { width: 10px; width: 0px; background: transparent; } /*横向滚动条左部分块*/ QScrollBar::sub-page:horizontal { height: 10px; height: 0px; background: transparent; } QScrollBar::sub-line { height: 12px; width: 10px; height: 0px; width: 0px; background: transparent; subcontrol-position: top; } /*纵向滚动条顶部三角形位置*/ QScrollBar::add-line { height: 10px; width: 1px; height: 0px; width: 0px; background: transparent; subcontrol-position: top; } Loading
ui/canvas.py +11 −3 Original line number Diff line number Diff line Loading @@ -15,7 +15,7 @@ from .misc import ndarray2pixmap from .config_proj import ProjImgTrans from .textitem import TextBlkItem, TextBlock from .texteditshapecontrol import TextBlkShapeControl from .stylewidgets import FadeLabel from .stylewidgets import FadeLabel, ScrollBar from .image_edit import ImageEditMode, DrawingLayer, StrokeImgItem from .page_search_widget import PageSearchWidget from utils import shared as C Loading Loading @@ -80,6 +80,16 @@ class CustomGV(QGraphicsView): ctrl_released = Signal() canvas: QGraphicsScene = None def __init__(self, parent=None): super().__init__(parent) ScrollBar(Qt.Orientation.Horizontal, self, fadeout=True) ScrollBar(Qt.Orientation.Vertical, self, fadeout=True) self.setAlignment(Qt.AlignmentFlag.AlignCenter) self.setDragMode(QGraphicsView.DragMode.ScrollHandDrag) # self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) # self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) def wheelEvent(self, event : QWheelEvent) -> None: # qgraphicsview always scroll content according to wheelevent # which is not desired when scaling img Loading Loading @@ -203,8 +213,6 @@ class Canvas(QGraphicsScene): self.editing_textblkitem: TextBlkItem = None self.gv = CustomGV(self) self.gv.setAlignment(Qt.AlignmentFlag.AlignCenter) self.gv.setDragMode(QGraphicsView.DragMode.ScrollHandDrag) self.gv.scale_down_signal.connect(self.scaleDown) self.gv.scale_up_signal.connect(self.scaleUp) self.gv.scale_with_value.connect(self.scaleBy) Loading
ui/scenetext_manager.py +1 −1 Original line number Diff line number Diff line Loading @@ -284,7 +284,7 @@ class TextPanel(Widget): self.formatpanel = FontFormatPanel(app, self) layout.addWidget(self.formatpanel) layout.addWidget(self.textEditList) layout.setContentsMargins(0, 0, 5, 0) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(7) layout.setAlignment(Qt.AlignmentFlag.AlignCenter) Loading
ui/stylewidgets.py +421 −9 Original line number Diff line number Diff line from qtpy.QtWidgets import QGraphicsOpacityEffect, QFrame, QWidget, QComboBox, QLabel, QSizePolicy, QDialog, QProgressBar, QMessageBox, QVBoxLayout, QStyle, QSlider, QProxyStyle, QStyle, QStyleOptionSlider, QColorDialog, QPushButton from qtpy.QtCore import Qt, QPropertyAnimation, QEasingCurve, QPointF, QRect, QRectF, Signal, QPoint, Property from qtpy.QtWidgets import QApplication, QAbstractScrollArea, QGraphicsOpacityEffect, QFrame, QWidget, QComboBox, QLabel, QSizePolicy, QDialog, QProgressBar, QMessageBox, QVBoxLayout, QStyle, QSlider, QHBoxLayout, QStyle, QStyleOptionSlider, QColorDialog, QPushButton from qtpy.QtCore import QEvent, Qt, QPropertyAnimation, QEasingCurve, QTimer, QRect, QRectF, Signal, QPoint, Property, QAbstractAnimation from qtpy.QtGui import QFontMetrics, QMouseEvent, QShowEvent, QWheelEvent, QPainter, QFontMetrics, QColor from typing import List, Union, Tuple Loading Loading @@ -290,6 +290,7 @@ class Slider(QSlider): def __init__(self, orientation: Qt.Orientation, parent: QWidget = None): super().__init__(orientation, parent=parent) self.hovering = False self._postInit() def _postInit(self): Loading Loading @@ -349,15 +350,12 @@ class Slider(QSlider): else: self._drawVerticalGroove(painter) if hasattr(self, 'draw_content'): if hasattr(self, 'draw_content') and self.hovering: # its a bad idea to display text like this, but I leave it as it is for now option = QStyleOptionSlider() self.initStyleOption(option) if not option.state & QStyle.State_MouseOver: return rect = self.style().subControlRect( QStyle.CC_Slider, option, QStyle.SC_SliderHandle, self) rect = slider_subcontrol_rect(rect, self) Loading @@ -379,9 +377,7 @@ class Slider(QSlider): dx = dy = 0 dy = self.height() - fm.height() + fm.descent() painter.drawText( dx, dy, value_str, ) painter.drawText(dx, dy, value_str) if self.draw_content is not None: painter.drawText(0, dy, self.draw_content, ) Loading Loading @@ -412,6 +408,16 @@ class Slider(QSlider): def resizeEvent(self, e): self._adjustHandlePos() def enterEvent(self, event) -> None: self.hovering = True self.update() return super().enterEvent(event) def leaveEvent(self, event) -> None: self.hovering = False self.update() return super().leaveEvent(event) class PaintQSlider(Slider): Loading Loading @@ -551,3 +557,409 @@ class TextChecker(QLabel): self.checkStateChanged.emit(self.checked) class ScrollBarGroove(QWidget): """ Scroll bar groove """ def __init__(self, orient: Qt.Orientation, parent): super().__init__(parent=parent) if orient == Qt.Vertical: self.setFixedWidth(12) self.setLayout(QVBoxLayout(self)) self.layout().addStretch(1) self.layout().setContentsMargins(0, 3, 0, 3) else: self.setFixedHeight(12) self.setLayout(QHBoxLayout(self)) self.layout().addStretch(1) self.layout().setContentsMargins(3, 0, 3, 0) self.opacityEffect = QGraphicsOpacityEffect(self) self.opacityAni = QPropertyAnimation(self.opacityEffect, b'opacity', self) self.setGraphicsEffect(self.opacityEffect) self.opacityEffect.setOpacity(0) def fadeIn(self): self.opacityAni.setEndValue(1) self.opacityAni.setDuration(150) self.opacityAni.start() def fadeOut(self): self.opacityAni.setEndValue(0) self.opacityAni.setDuration(150) self.opacityAni.start() def paintEvent(self, e): painter = QPainter(self) painter.setRenderHints(QPainter.Antialiasing) painter.setPen(Qt.NoPen) painter.setBrush(QColor(0, 0, 0, 30)) painter.drawRoundedRect(self.rect(), 6, 6) # ScrollBarHandle and ScrollBar are modified from https://github.com/zhiyiYo/PyQt-Fluent-Widgets/blob/master/qfluentwidgets/components/widgets/scroll_bar.py class ScrollBarHandle(QWidget): """ Scroll bar handle """ def __init__(self, orient: Qt.Orientation, parent=None, fadeout: bool = False): super().__init__(parent) self.orient = orient if fadeout: self.effect = effect = QGraphicsOpacityEffect(self, opacity=1.0) self.setGraphicsEffect(effect) self.fadeAnimation = QPropertyAnimation( self, propertyName=b"opacity", targetObject=effect, duration=300, startValue=1.0, endValue=0., ) # self.fadeAnimation.setEasingCurve(QEasingCurve.Type.InQuint) self.fadeAnimation.finished.connect(self.hide) fixsize = 5 self.anime_timer = QTimer(self) self.anime_timer.setSingleShot(True) self.anime_timer.timeout.connect(self.start_fade_animation) else: fixsize = 3 if orient == Qt.Vertical: self.setFixedWidth(fixsize) else: self.setFixedHeight(fixsize) self.fadeout = fadeout def start_fade_animation(self): self.show() if self.fadeAnimation.state() == QAbstractAnimation.State.Running: self.fadeAnimation.stop() self.fadeAnimation.start() def prepareFadeout(self): self.anime_timer.stop() self.anime_timer.start(700) if self.isHidden(): self.show() if self.fadeAnimation.state() == QAbstractAnimation.State.Running: self.fadeAnimation.stop() if self.effect.opacity() != 1.: self.effect.setOpacity(1.) def stopFadeout(self): if self.fadeAnimation.state() == QAbstractAnimation.State.Running: self.fadeAnimation.stop() self.anime_timer.stop() self.show() if self.effect.opacity() != 1.: self.effect.setOpacity(1.) def paintEvent(self, e): painter = QPainter(self) painter.setRenderHints(QPainter.Antialiasing) painter.setPen(Qt.NoPen) r = self.width() / 2 if self.orient == Qt.Vertical else self.height() / 2 c = QColor(0, 0, 0, 90) painter.setBrush(c) painter.drawRoundedRect(self.rect(), r, r) class ScrollBar(QWidget): """ Fluent scroll bar """ rangeChanged = Signal(tuple) valueChanged = Signal(int) sliderPressed = Signal() sliderReleased = Signal() sliderMoved = Signal() def __init__(self, orient: Qt.Orientation, parent: QAbstractScrollArea, fadeout: bool = False): super().__init__(parent) self.groove = ScrollBarGroove(orient, self) self.handle = ScrollBarHandle(orient, self, fadeout) self.timer = QTimer(self) self.scroll_area = parent self.fadeout = fadeout self._orientation = orient self._singleStep = 1 self._pageStep = 50 self._padding = 0 self._minimum = 0 self._maximum = 0 self._value = 0 self._isPressed = False self.isEnter = False self._isExpanded = False self._pressedPos = QPoint() self._isForceHidden = False if orient == Qt.Vertical: self.partnerBar = parent.verticalScrollBar() QAbstractScrollArea.setVerticalScrollBarPolicy(parent, Qt.ScrollBarAlwaysOff) else: self.partnerBar = parent.horizontalScrollBar() QAbstractScrollArea.setHorizontalScrollBarPolicy(parent, Qt.ScrollBarAlwaysOff) self.__initWidget(parent) def __initWidget(self, parent): self.groove.opacityAni.valueChanged.connect(self._onOpacityAniValueChanged) self.partnerBar.rangeChanged.connect(self.setRange) self.partnerBar.valueChanged.connect(self._onValueChanged) self.valueChanged.connect(self.partnerBar.setValue) parent.installEventFilter(self) self.setRange(self.partnerBar.minimum(), self.partnerBar.maximum()) self.setVisible(self.maximum() > 0 and not self._isForceHidden) self._adjustPos(self.parent().size()) def _onPageUp(self): self.setValue(self.value() - self.pageStep()) def _onPageDown(self): self.setValue(self.value() + self.pageStep()) def _onValueChanged(self, value): self.val = value if self.fadeout and not self.isEnter: self.handle.prepareFadeout() def value(self): return self._value @Property(int, notify=valueChanged) def val(self): return self._value @val.setter def val(self, value: int): if value == self.value(): return value = max(self.minimum(), min(value, self.maximum())) self._value = value self.valueChanged.emit(value) # adjust the position of handle self._adjustHandlePos() def minimum(self): return self._minimum def maximum(self): return self._maximum def orientation(self): return self._orientation def pageStep(self): return self._pageStep def singleStep(self): return self._singleStep def isSliderDown(self): return self._isPressed def setValue(self, value: int): self.val = value def setMinimum(self, min: int): if min == self.minimum(): return self._minimum = min self.rangeChanged.emit((min, self.maximum())) def setMaximum(self, max: int): if max == self.maximum(): return self._maximum = max self.rangeChanged.emit((self.minimum(), max)) def setRange(self, min: int, max: int): if min > max or (min == self.minimum() and max == self.maximum()): return self.setMinimum(min) self.setMaximum(max) self._adjustHandleSize() self._adjustHandlePos() self.setVisible(max > 0 and not self._isForceHidden) self.rangeChanged.emit((min, max)) def setPageStep(self, step: int): if step >= 1: self._pageStep = step def setSingleStep(self, step: int): if step >= 1: self._singleStep = step def setSliderDown(self, isDown: bool): self._isPressed = True if isDown: self.sliderPressed.emit() else: self.sliderReleased.emit() def expand(self): """ expand scroll bar """ if self._isExpanded or not self.isEnter: return self._isExpanded = True self.groove.fadeIn() def collapse(self): """ collapse scroll bar """ if not self._isExpanded or self.isEnter: return self._isExpanded = False self.groove.fadeOut() def enterEvent(self, e): self.isEnter = True self.timer.stop() self.timer.singleShot(200, self.expand) if self.fadeout: self.handle.stopFadeout() def leaveEvent(self, e): self.isEnter = False self.timer.stop() self.timer.singleShot(200, self.collapse) if self.fadeout: self.handle.prepareFadeout() def eventFilter(self, obj, e: QEvent): if obj is not self.parent(): return super().eventFilter(obj, e) # adjust the position of slider if e.type() == QEvent.Resize: self._adjustPos(e.size()) return super().eventFilter(obj, e) def resizeEvent(self, e): self.groove.resize(self.size()) def mousePressEvent(self, e: QMouseEvent): super().mousePressEvent(e) self._isPressed = True self._pressedPos = e.pos() if self.childAt(e.pos()) is self.handle or not self._isSlideResion(e.pos()): return if self.orientation() == Qt.Vertical: if e.pos().y() > self.handle.geometry().bottom(): value = e.pos().y() - self.handle.height() - self._padding else: value = e.pos().y() - self._padding else: if e.pos().x() > self.handle.geometry().right(): value = e.pos().x() - self.handle.width() - self._padding else: value = e.pos().x() - self._padding self.setValue(int(value / max(self._slideLength(), 1) * self.maximum())) self.sliderPressed.emit() def mouseReleaseEvent(self, e): super().mouseReleaseEvent(e) self._isPressed = False self.sliderReleased.emit() def mouseMoveEvent(self, e: QMouseEvent): if self.orientation() == Qt.Vertical: dv = e.pos().y() - self._pressedPos.y() else: dv = e.pos().x() - self._pressedPos.x() # don't use `self.setValue()`, because it could be reimplemented dv = int(dv / max(self._slideLength(), 1) * (self.maximum() - self.minimum())) ScrollBar.setValue(self, self.value() + dv) self._pressedPos = e.pos() self.sliderMoved.emit() def _adjustPos(self, size): if self.orientation() == Qt.Vertical: self.resize(12, size.height() - 2) self.move(size.width() - 13, 1) else: self.resize(size.width() - 2, 12) self.move(1, size.height() - 13) def _adjustHandleSize(self): p = self.parent() if self.orientation() == Qt.Vertical: total = self.maximum() - self.minimum() + p.height() s = int(self._grooveLength() * p.height() / max(total, 1)) self.handle.setFixedHeight(max(30, s)) else: total = self.maximum() - self.minimum() + p.width() s = int(self._grooveLength() * p.width() / max(total, 1)) self.handle.setFixedWidth(max(30, s)) def _adjustHandlePos(self): total = max(self.maximum() - self.minimum(), 1) delta = int(self.value() / total * self._slideLength()) if self.orientation() == Qt.Vertical: x = self.width() - self.handle.width() - 3 self.handle.move(x, self._padding + delta) else: y = self.height() - self.handle.height() - 3 self.handle.move(self._padding + delta, y) def _grooveLength(self): if self.orientation() == Qt.Vertical: return self.height() - 2 * self._padding return self.width() - 2 * self._padding def _slideLength(self): if self.orientation() == Qt.Vertical: return self._grooveLength() - self.handle.height() return self._grooveLength() - self.handle.width() def _isSlideResion(self, pos: QPoint): if self.orientation() == Qt.Vertical: return self._padding <= pos.y() <= self.height() - self._padding return self._padding <= pos.x() <= self.width() - self._padding def _onOpacityAniValueChanged(self): if not self.fadeout: opacity = self.groove.opacityEffect.opacity() if self.orientation() == Qt.Vertical: self.handle.setFixedWidth(int(3 + opacity * 3)) else: self.handle.setFixedHeight(int(3 + opacity * 3)) self._adjustHandlePos() def setForceHidden(self, isHidden: bool): """ whether to force the scrollbar to be hidden """ self._isForceHidden = isHidden self.setVisible(self.maximum() > 0 and not isHidden) def wheelEvent(self, e): QApplication.sendEvent(self.parent().viewport(), e) No newline at end of file
ui/textedit_area.py +10 −5 Original line number Diff line number Diff line from typing import List, Union from qtpy.QtWidgets import QMenu, QTextEdit, QScrollArea, QGraphicsDropShadowEffect, QVBoxLayout, QApplication, QHBoxLayout from qtpy.QtWidgets import QTextEdit, QScrollArea, QGraphicsDropShadowEffect, QVBoxLayout, QApplication, QHBoxLayout from qtpy.QtCore import Signal, Qt, QMimeData, QEvent, QPoint from qtpy.QtGui import QColor, QFocusEvent, QInputMethodEvent, QDragEnterEvent, QDragMoveEvent, QDropEvent, QKeyEvent, QTextCursor, QMouseEvent, QDrag, QPixmap, QKeySequence import keyboard import webbrowser import numpy as np from .stylewidgets import Widget, SeparatorWidget, ClickableLabel, IgnoreMouseLabel from .stylewidgets import Widget, SeparatorWidget, ClickableLabel, IgnoreMouseLabel, ScrollBar from .textitem import TextBlock from utils.config import pcfg import webbrowser import numpy as np STYLE_TRANSPAIR_CHECKED = "background-color: rgba(30, 147, 229, 20%);" Loading Loading @@ -401,8 +401,13 @@ class TextEditListScrollArea(QScrollArea): super().__init__(*args, **kwargs) self.scrollContent = Widget() self.setWidget(self.scrollContent) # ScrollBar(Qt.Orientation.Horizontal, self) ScrollBar(Qt.Orientation.Vertical, self) self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) vlayout = QVBoxLayout() vlayout.setContentsMargins(0, 0, 0, 0) vlayout.setContentsMargins(0, 0, 3, 0) vlayout.setAlignment(Qt.AlignmentFlag.AlignTop) vlayout.setSpacing(0) vlayout.addStretch(1) Loading