class MColorPaletteDialog(QtWidgets.QDialog): def __init__(self, init_color, parent=None): super(MColorPaletteDialog, self).__init__(parent) self.setWindowTitle("DAYU Color Palette") self.primary_color = QtGui.QColor(init_color) self.color_chart = MColorChart() self.choose_color_button = QtWidgets.QPushButton() self.choose_color_button.setFixedSize(QtCore.QSize(100, 30)) self.color_label = QtWidgets.QLabel() self.info_label = MLabel() self.info_label.setProperty("error", True) color_lay = QtWidgets.QHBoxLayout() color_lay.addWidget(MLabel("Primary Color:")) color_lay.addWidget(self.choose_color_button) color_lay.addWidget(self.color_label) color_lay.addWidget(self.info_label) color_lay.addStretch() dialog = QtWidgets.QColorDialog(self.primary_color, parent=self) dialog.setWindowFlags(QtCore.Qt.Widget) dialog.setOption(QtWidgets.QColorDialog.NoButtons) dialog.currentColorChanged.connect(self.slot_color_changed) setting_lay = QtWidgets.QVBoxLayout() setting_lay.addLayout(color_lay) setting_lay.addWidget(MDivider()) setting_lay.addWidget(dialog) main_lay = QtWidgets.QHBoxLayout() main_lay.addWidget(self.color_chart) main_lay.addLayout(setting_lay) self.setLayout(main_lay) self.update_color() @QtCore.Slot(QtGui.QColor) def slot_color_changed(self, color): self.primary_color = color light = self.primary_color.lightness() saturation = self.primary_color.saturation() self.info_label.setText("") if light <= 70: self.info_label.setText("亮度建议不低于70(现在 {})".format(light)) if saturation <= 70: self.info_label.setText("饱和度建议不低于70(现在 {})".format(saturation)) self.update_color() def update_color(self): self.choose_color_button.setStyleSheet( "border-radius: 0;border: none;border:1px solid gray;" "background-color:{};".format(self.primary_color.name())) self.color_label.setText(self.primary_color.name()) self.color_chart.set_colors([ utils.generate_color(self.primary_color, index + 1) for index in range(10) ])
def test_label_elide_mode(qtbot, text, elide): """Test MLabel elide mode""" main_widget = QWidget() main_widget.setGeometry(0, 0, 30, 200) main_lay = QVBoxLayout() main_widget.setLayout(main_lay) label_left = MLabel() label_left.set_elide_mode(Qt.ElideLeft) label_left.setText(text) label_right = MLabel() label_right.set_elide_mode(Qt.ElideRight) label_right.setText(text) label_center = MLabel(text) label_center.set_elide_mode(Qt.ElideMiddle) label_center.setText(text) main_lay.addWidget(label_left) main_lay.addWidget(label_right) main_lay.addWidget(label_center) qtbot.addWidget(main_widget) main_widget.show() ellipsis = u'…' if elide: assert label_left.property('text').startswith(ellipsis) assert label_right.property('text').endswith(ellipsis) center_text = label_center.property('text') assert center_text.count(ellipsis) \ and not center_text.endswith(ellipsis) else: assert label_left.property('text') == label_left.text() assert label_right.property('text') == label_right.text() assert label_center.property('text') == label_center.text() assert label_left.get_elide_mode() == Qt.ElideLeft assert label_right.get_elide_mode() == Qt.ElideRight assert label_center.get_elide_mode() == Qt.ElideMiddle
class MSequenceFile(QWidget, MFieldMixin): ''' 这个类必须依赖 DayuPath props: path: six.string_types sequence: bool ''' sig_is_sequence_changed = Signal(bool) def __init__(self, size=None, parent=None): super(MSequenceFile, self).__init__(parent) self.sequence_obj = None size = size or dayu_theme.small self._file_label = MLineEdit() self._file_label.set_dayu_size(size) self._file_label.setReadOnly(True) self._is_sequence_check_box = MCheckBox(self.tr('Sequence')) self._is_sequence_check_box.toggled.connect( functools.partial(self.setProperty, 'sequence')) self._is_sequence_check_box.toggled.connect( self.sig_is_sequence_changed) self._info_label = MLabel().secondary() self._error_label = MLabel().secondary() self._error_label.setProperty('error', True) self._error_label.setMinimumWidth(100) self._error_label.set_elide_mode(Qt.ElideMiddle) seq_lay = QHBoxLayout() seq_lay.addWidget(self._is_sequence_check_box) seq_lay.addWidget(self._info_label) seq_lay.addWidget(self._error_label) seq_lay.setStretchFactor(self._is_sequence_check_box, 0) seq_lay.setStretchFactor(self._info_label, 0) seq_lay.setStretchFactor(self._error_label, 100) self._main_lay = QVBoxLayout() self._main_lay.setContentsMargins(0, 0, 0, 0) self._main_lay.addWidget(self._file_label) self._main_lay.addLayout(seq_lay) self.setLayout(self._main_lay) self.set_sequence(True) def _set_path(self, value): path = DayuPath(value) for seq_obj in path.scan(): self.sequence_obj = seq_obj self._update_info() def set_path(self, value): self.setProperty('path', value) def set_sequence(self, value): assert isinstance(value, bool) self.setProperty('sequence', value) def _set_sequence(self, value): if value != self._is_sequence_check_box.isChecked(): # 更新来自代码 self._is_sequence_check_box.setChecked(value) self.sig_is_sequence_changed.emit(value) self._update_info() def _update_info(self): self._file_label.setProperty( 'text', self.sequence_obj if self.property('sequence') else self.property('path')) if self.sequence_obj: self._info_label.setText(u'Format: {ext} ' u'Total: {count} ' u'Range: {start}-{end}'.format( ext=self.sequence_obj.ext, count=len(self.sequence_obj.frames), start=self.sequence_obj.frames[0] if self.sequence_obj.frames else '/', end=self.sequence_obj.frames[-1] if self.sequence_obj.frames else '/')) error_info = u'Missing: {}'.format( self.sequence_obj.missing) if self.sequence_obj.missing else '' self._error_label.setText(error_info) self._error_label.setToolTip(error_info) self._info_label.setVisible(self.property('sequence')) self._error_label.setVisible(self.property('sequence'))
def _init_ui(self): title_lay = QtWidgets.QGridLayout() title_lay.addWidget(MLabel("一级标题").h1(), 0, 0) title_lay.addWidget(MLabel("二级标题").h2(), 1, 0) title_lay.addWidget(MLabel("三级标题").h3(), 2, 0) title_lay.addWidget(MLabel("四级标题").h4(), 3, 0) title_lay.addWidget(MLabel("h1 Level").h1(), 0, 1) title_lay.addWidget(MLabel("h2 Level").h2(), 1, 1) title_lay.addWidget(MLabel("h3 Level").h3(), 2, 1) title_lay.addWidget(MLabel("h4 Level").h4(), 3, 1) text_type_lay = QtWidgets.QHBoxLayout() text_type_lay.addWidget(MLabel("MLabel: Normal")) text_type_lay.addWidget(MLabel("MLabel: Secondary").secondary()) text_type_lay.addWidget(MLabel("MLabel: Warning").warning()) text_type_lay.addWidget(MLabel("MLabel: Danger").danger()) disable_text = MLabel("MLabel: Disabled") disable_text.setEnabled(False) text_type_lay.addWidget(disable_text) text_attr_lay = QtWidgets.QHBoxLayout() text_attr_lay.addWidget(MLabel("MLabel: Mark").mark()) text_attr_lay.addWidget(MLabel("MLabel: Code").code()) text_attr_lay.addWidget(MLabel("MLabel: Underline").underline()) text_attr_lay.addWidget(MLabel("MLabel: Delete").delete()) text_attr_lay.addWidget(MLabel("MLabel: Strong").strong()) text_mix_lay = QtWidgets.QHBoxLayout() text_mix_lay.addWidget( MLabel("MLabel: Strong & Underline").strong().underline() ) text_mix_lay.addWidget(MLabel("MLabel: Danger & Delete").danger().delete()) text_mix_lay.addWidget(MLabel("MLabel: Warning & Strong").warning().strong()) text_mix_lay.addWidget(MLabel("MLabel: H4 & Mark").h4().mark()) data_bind_lay = QtWidgets.QHBoxLayout() data_bind_label = MLabel() button = MPushButton(text="Random An Animal").primary() button.clicked.connect(self.slot_change_text) data_bind_lay.addWidget(data_bind_label) data_bind_lay.addWidget(button) data_bind_lay.addStretch() self.register_field("show_text", "Guess") self.bind("show_text", data_bind_label, "text") lay_elide = QtWidgets.QVBoxLayout() label_none = MLabel( "This is a elide NONE mode label. " "Ellipsis should NOT appear in the text." ) label_left = MLabel( "This is a elide LEFT mode label. " "The ellipsis should appear at the beginning of the text. " "xiao mao xiao gou xiao ci wei" ) label_left.set_elide_mode(QtCore.Qt.ElideLeft) label_middle = MLabel( "This is a elide MIDDLE mode label. " "The ellipsis should appear in the middle of the text. " "xiao mao xiao gou xiao ci wei" ) label_middle.set_elide_mode(QtCore.Qt.ElideMiddle) label_right = MLabel() label_right.setText( "This is a elide RIGHT mode label. " "The ellipsis should appear at the end of the text. " "Some text to fill the line bala bala bala." ) label_right.set_elide_mode(QtCore.Qt.ElideRight) lay_elide.addWidget(label_none) lay_elide.addWidget(label_left) lay_elide.addWidget(label_middle) lay_elide.addWidget(label_right) hyper_label_1 = MLabel() hyper_label_1.set_link("https://baidu.com", text="baidu") hyper_label_2 = MLabel() hyper_label_2.set_link("https://baidu.com") hyper_label_3 = MLabel() hyper_label_3.set_link( "https://github.com/phenom-films/dayu_widgets", text="Dayu Widgets" ) hyperlink_lay = QtWidgets.QVBoxLayout() hyperlink_lay.addWidget(hyper_label_1) hyperlink_lay.addWidget(hyper_label_2) hyperlink_lay.addWidget(hyper_label_3) # hyperlink_lay.addWidget() main_lay = QtWidgets.QVBoxLayout() main_lay.addWidget(MDivider("different level")) main_lay.addLayout(title_lay) main_lay.addWidget(MDivider("different type")) main_lay.addLayout(text_type_lay) main_lay.addWidget(MDivider("different property")) main_lay.addLayout(text_attr_lay) main_lay.addWidget(MDivider("mix")) main_lay.addLayout(text_mix_lay) # main_lay.addWidget(MDivider('data bind')) # main_lay.addLayout(data_bind_lay) main_lay.addWidget(MDivider("elide mode")) main_lay.addLayout(lay_elide) main_lay.addWidget(MDivider("hyperlink")) main_lay.addLayout(hyperlink_lay) main_lay.addStretch() self.setLayout(main_lay)
class MProgressCircle(QProgressBar): """ MProgressCircle: Display the current progress of an operation flow. When you need to display the completion percentage of an operation. Property: dayu_width: int dayu_color: str """ def __init__(self, dashboard=False, parent=None): super(MProgressCircle, self).__init__(parent) self._main_lay = QHBoxLayout() self._default_label = MLabel().h3() self._default_label.setAlignment(Qt.AlignCenter) self._main_lay.addWidget(self._default_label) self.setLayout(self._main_lay) self._color = None self._width = None self._start_angle = 90 * 16 self._max_delta_angle = 360 * 16 self._height_factor = 1.0 self._width_factor = 1.0 if dashboard: self._start_angle = 225 * 16 self._max_delta_angle = 270 * 16 self._height_factor = (2 + pow(2, 0.5)) / 4 + 0.03 self.set_dayu_width(120) self.set_dayu_color(dayu_theme.primary_color) def set_widget(self, widget): """ Set a custom widget to show on the circle's inner center and replace the default percent label :param widget: QWidget :return: None """ self.setTextVisible(False) self._main_lay.addWidget(widget) def get_dayu_width(self): """ Get current circle fixed width :return: int """ return self._width def set_dayu_width(self, value): """ Set current circle fixed width :param value: int :return: None """ self._width = value self.setFixedSize(QSize(self._width * self._width_factor, self._width * self._height_factor)) def get_dayu_color(self): """ Get current circle foreground color :return: str """ return self._color def set_dayu_color(self, value): """ Set current circle's foreground color :param value: str :return: """ self._color = value self.update() dayu_color = Property(str, get_dayu_color, set_dayu_color) dayu_width = Property(int, get_dayu_width, set_dayu_width) def paintEvent(self, event): """Override QProgressBar's paintEvent.""" if self.text() != self._default_label.text(): self._default_label.setText(self.text()) if self.isTextVisible() != self._default_label.isVisible(): self._default_label.setVisible(self.isTextVisible()) percent = utils.get_percent(self.value(), self.minimum(), self.maximum()) total_width = self.get_dayu_width() pen_width = int(3 * total_width / 50.0) radius = total_width - pen_width - 1 painter = QPainter(self) painter.setRenderHints(QPainter.Antialiasing) # draw background circle pen_background = QPen() pen_background.setWidth(pen_width) pen_background.setColor(dayu_theme.background_selected_color) pen_background.setCapStyle(Qt.RoundCap) painter.setPen(pen_background) painter.drawArc(pen_width / 2.0 + 1, pen_width / 2.0 + 1, radius, radius, self._start_angle, -self._max_delta_angle) # draw foreground circle pen_foreground = QPen() pen_foreground.setWidth(pen_width) pen_foreground.setColor(self._color) pen_foreground.setCapStyle(Qt.RoundCap) painter.setPen(pen_foreground) painter.drawArc(pen_width / 2.0 + 1, pen_width / 2.0 + 1, radius, radius, self._start_angle, -percent * 0.01 * self._max_delta_angle) painter.end() @classmethod def dashboard(cls, parent=None): """Create a dashboard style MCircle""" return MProgressCircle(dashboard=True, parent=parent)
def __init__(self, text, duration=None, dayu_type=None, parent=None): super(MToast, self).__init__(parent) self.setWindowFlags(Qt.FramelessWindowHint | Qt.Dialog | Qt.WA_TranslucentBackground | Qt.WA_DeleteOnClose) self.setAttribute(Qt.WA_StyledBackground) _icon_lay = QHBoxLayout() _icon_lay.addStretch() if dayu_type == MToast.LoadingType: _icon_lay.addWidget( MLoading(size=dayu_theme.huge, color=dayu_theme.text_color_inverse)) else: _icon_label = MAvatar() _icon_label.set_dayu_size(60) _icon_label.set_dayu_image( MPixmap('{}_line.svg'.format(dayu_type or MToast.InfoType), dayu_theme.text_color_inverse)) _icon_lay.addWidget(_icon_label) _icon_lay.addStretch() _content_label = MLabel() _content_label.setText(text) _content_label.setAlignment(Qt.AlignCenter) _main_lay = QVBoxLayout() _main_lay.setContentsMargins(0, 0, 0, 0) _main_lay.addStretch() _main_lay.addLayout(_icon_lay) _main_lay.addSpacing(10) _main_lay.addWidget(_content_label) _main_lay.addStretch() self.setLayout(_main_lay) self.setFixedSize(QSize(120, 120)) _close_timer = QTimer(self) _close_timer.setSingleShot(True) _close_timer.timeout.connect(self.close) _close_timer.timeout.connect(self.sig_closed) _close_timer.setInterval( (duration or self.default_config.get('duration')) * 1000) _ani_timer = QTimer(self) _ani_timer.timeout.connect(self._fade_out) _ani_timer.setInterval( (duration or self.default_config.get('duration')) * 1000 - 300) _close_timer.start() _ani_timer.start() self._opacity_ani = QPropertyAnimation() self._opacity_ani.setTargetObject(self) self._opacity_ani.setDuration(300) self._opacity_ani.setEasingCurve(QEasingCurve.OutCubic) self._opacity_ani.setPropertyName(b'windowOpacity') self._opacity_ani.setStartValue(0.0) self._opacity_ani.setEndValue(0.9) self._get_center_position(parent) self._fade_int()
class MMessage(QWidget): """ Display global messages as feedback in response to user operations. """ InfoType = 'info' SuccessType = 'success' WarningType = 'warning' ErrorType = 'error' LoadingType = 'loading' default_config = {'duration': 2, 'top': 24} sig_closed = Signal() def __init__(self, text, duration=None, dayu_type=None, closable=False, parent=None): super(MMessage, self).__init__(parent) self.setObjectName('message') self.setWindowFlags(Qt.FramelessWindowHint | Qt.Dialog | Qt.WA_TranslucentBackground | Qt.WA_DeleteOnClose) self.setAttribute(Qt.WA_StyledBackground) if dayu_type == MMessage.LoadingType: _icon_label = MLoading.tiny() else: _icon_label = MAvatar.tiny() current_type = dayu_type or MMessage.InfoType _icon_label.set_dayu_image( MPixmap('{}_fill.svg'.format(current_type), vars(dayu_theme).get(current_type + '_color'))) self._content_label = MLabel(parent=self) # self._content_label.set_elide_mode(Qt.ElideMiddle) self._content_label.setText(text) self._close_button = MToolButton( parent=self).icon_only().svg('close_line.svg').tiny() self._close_button.clicked.connect(self.close) self._close_button.setVisible(closable or False) self._main_lay = QHBoxLayout() self._main_lay.addWidget(_icon_label) self._main_lay.addWidget(self._content_label) self._main_lay.addStretch() self._main_lay.addWidget(self._close_button) self.setLayout(self._main_lay) _close_timer = QTimer(self) _close_timer.setSingleShot(True) _close_timer.timeout.connect(self.close) _close_timer.timeout.connect(self.sig_closed) _close_timer.setInterval( (duration or self.default_config.get('duration')) * 1000) _ani_timer = QTimer(self) _ani_timer.timeout.connect(self._fade_out) _ani_timer.setInterval( (duration or self.default_config.get('duration')) * 1000 - 300) _close_timer.start() _ani_timer.start() self._pos_ani = QPropertyAnimation(self) self._pos_ani.setTargetObject(self) self._pos_ani.setEasingCurve(QEasingCurve.OutCubic) self._pos_ani.setDuration(300) self._pos_ani.setPropertyName(b'pos') self._opacity_ani = QPropertyAnimation() self._opacity_ani.setTargetObject(self) self._opacity_ani.setDuration(300) self._opacity_ani.setEasingCurve(QEasingCurve.OutCubic) self._opacity_ani.setPropertyName(b'windowOpacity') self._opacity_ani.setStartValue(0.0) self._opacity_ani.setEndValue(1.0) self._set_proper_position(parent) self._fade_int() def _fade_out(self): self._pos_ani.setDirection(QAbstractAnimation.Backward) self._pos_ani.start() self._opacity_ani.setDirection(QAbstractAnimation.Backward) self._opacity_ani.start() def _fade_int(self): self._pos_ani.start() self._opacity_ani.start() def _set_proper_position(self, parent): parent_geo = parent.geometry() pos = parent_geo.topLeft( ) if parent.parent() is None else parent.mapToGlobal( parent_geo.topLeft()) offset = 0 for child in parent.children(): if isinstance(child, MMessage) and child.isVisible(): offset = max(offset, child.y()) base = pos.y() + MMessage.default_config.get('top') target_x = pos.x() + parent_geo.width() / 2 - 100 target_y = (offset + 50) if offset else base self._pos_ani.setStartValue(QPoint(target_x, target_y - 40)) self._pos_ani.setEndValue(QPoint(target_x, target_y)) @classmethod def info(cls, text, parent, duration=None, closable=None): """Show a normal message""" inst = cls(text, dayu_type=MMessage.InfoType, duration=duration, closable=closable, parent=parent) inst.show() return inst @classmethod def success(cls, text, parent, duration=None, closable=None): """Show a success message""" inst = cls(text, dayu_type=MMessage.SuccessType, duration=duration, closable=closable, parent=parent) inst.show() return inst @classmethod def warning(cls, text, parent, duration=None, closable=None): """Show a warning message""" inst = cls(text, dayu_type=MMessage.WarningType, duration=duration, closable=closable, parent=parent) inst.show() return inst @classmethod def error(cls, text, parent, duration=None, closable=None): """Show an error message""" inst = cls(text, dayu_type=MMessage.ErrorType, duration=duration, closable=closable, parent=parent) inst.show() return inst @classmethod def loading(cls, text, parent): """Show a message with loading animation""" inst = cls(text, dayu_type=MMessage.LoadingType, parent=parent) inst.show() return inst @classmethod def config(cls, duration=None, top=None): """ Config the global MMessage duration and top setting. :param duration: int (unit is second) :param top: int (unit is px) :return: None """ if duration is not None: cls.default_config['duration'] = duration if top is not None: cls.default_config['top'] = top
class MAlert(QWidget): """ Alert component for feedback. Property: dayu_type: The feedback type with different color container. dayu_text: The feedback string showed in container. """ InfoType = 'info' SuccessType = 'success' WarningType = 'warning' ErrorType = 'error' def __init__(self, text='', parent=None, flags=0): super(MAlert, self).__init__(parent, flags) self.setAttribute(Qt.WA_StyledBackground) self._icon_label = MAvatar() self._icon_label.set_dayu_size(dayu_theme.tiny) self._content_label = MLabel().secondary() self._close_button = MToolButton().svg( 'close_line.svg').tiny().icon_only() self._close_button.clicked.connect( functools.partial(self.setVisible, False)) self._main_lay = QHBoxLayout() self._main_lay.setContentsMargins(8, 8, 8, 8) self._main_lay.addWidget(self._icon_label) self._main_lay.addWidget(self._content_label) self._main_lay.addStretch() self._main_lay.addWidget(self._close_button) self.setLayout(self._main_lay) self.set_show_icon(True) self.set_closeable(False) self._dayu_type = None self._dayu_text = None self.set_dayu_type(MAlert.InfoType) self.set_dayu_text(text) def set_closeable(self, closeable): """Display the close icon button or not.""" self._close_button.setVisible(closeable) def set_show_icon(self, show_icon): """Display the information type icon or not.""" self._icon_label.setVisible(show_icon) def _set_dayu_text(self): self._content_label.setText(self._dayu_text) self.setVisible(bool(self._dayu_text)) def set_dayu_text(self, value): """Set the feedback content.""" if isinstance(value, basestring): self._dayu_text = value else: raise TypeError("Input argument 'value' should be string type, " "but get {}".format(type(value))) self._set_dayu_text() def _set_dayu_type(self): self._icon_label.set_dayu_image( MPixmap('{}_fill.svg'.format(self._dayu_type), vars(dayu_theme).get(self._dayu_type + '_color'))) self.style().polish(self) def set_dayu_type(self, value): """Set feedback type.""" if value in [ MAlert.InfoType, MAlert.SuccessType, MAlert.WarningType, MAlert.ErrorType ]: self._dayu_type = value else: raise ValueError("Input argument 'value' should be one of " "info/success/warning/error string.") self._set_dayu_type() def get_dayu_type(self): """ Get MAlert feedback type. :return: str """ return self._dayu_type def get_dayu_text(self): """ Get MAlert feedback message. :return: basestring """ return self._dayu_text dayu_text = Property(unicode, get_dayu_text, set_dayu_text) dayu_type = Property(str, get_dayu_type, set_dayu_type) def info(self): """Set MAlert to InfoType""" self.set_dayu_type(MAlert.InfoType) return self def success(self): """Set MAlert to SuccessType""" self.set_dayu_type(MAlert.SuccessType) return self def warning(self): """Set MAlert to WarningType""" self.set_dayu_type(MAlert.WarningType) return self def error(self): """Set MAlert to ErrorType""" self.set_dayu_type(MAlert.ErrorType) return self def closable(self): """Set MAlert closebale is True""" self.set_closeable(True) return self
class MSectionItem(QWidget): sig_context_menu = Signal(object) def __init__(self, title='', expand=False, widget=None, closeable=False, parent=None): super(MSectionItem, self).__init__(parent) self._central_widget = None self.setAttribute(Qt.WA_StyledBackground) self.title_label = MLabel(parent=self) self.expand_icon = MLabel(parent=self) self.expand_icon.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self._close_button = MToolButton().icon_only().tiny().svg( 'close_line.svg') self._close_button.clicked.connect(self.close) header_lay = QHBoxLayout() header_lay.addWidget(self.expand_icon) header_lay.addWidget(self.title_label) header_lay.addStretch() header_lay.addWidget(self._close_button) self.header_widget = QWidget(parent=self) self.header_widget.setAttribute(Qt.WA_StyledBackground) self.header_widget.setObjectName('title') self.header_widget.setLayout(header_lay) self.header_widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.header_widget.setCursor(Qt.PointingHandCursor) self.title_label.setCursor(Qt.PointingHandCursor) self.header_widget.installEventFilter(self) self.title_label.installEventFilter(self) self.content_widget = QWidget(parent=self) self.content_layout = QHBoxLayout() self.content_widget.setLayout(self.content_layout) self.main_lay = QVBoxLayout() self.main_lay.setContentsMargins(0, 0, 0, 0) self.main_lay.setSpacing(0) self.main_lay.addWidget(self.header_widget) self.main_lay.addWidget(self.content_widget) self.setLayout(self.main_lay) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) self.setMouseTracking(True) self.set_title(title) self.set_closeable(closeable) if widget: self.set_content(widget) self.set_expand(expand) def set_content(self, widget): if self._central_widget: self.content_layout.removeWidget(self._central_widget) self._central_widget.close() self.content_layout.addWidget(widget) self._central_widget = widget def get_content(self): return self._central_widget def set_closeable(self, value): self.setProperty('closeable', value) def _set_closeable(self, value): self.content_widget.setVisible(value) self._close_button.setVisible(value) def set_expand(self, value): self.setProperty('expand', value) def _set_expand(self, value): self.content_widget.setVisible(value) self.expand_icon.setPixmap( MPixmap('down_line.svg' if value else 'right_line.svg'). scaledToHeight(12)) def set_title(self, value): self.setProperty('title', value) def _set_title(self, value): self.title_label.setText(value) def eventFilter(self, widget, event): if widget in [self.header_widget, self.title_label]: if event.type() == QEvent.MouseButtonRelease: self.set_expand(not self.property('expand')) return super(QWidget, self).eventFilter(widget, event)
class MDrawer(QWidget): """ A panel which slides in from the edge of the screen. """ LeftPos = 'left' RightPos = 'right' TopPos = 'top' BottomPos = 'bottom' sig_closed = Signal() def __init__(self, title, position='right', closable=True, parent=None): super(MDrawer, self).__init__(parent) self.setObjectName('message') # self.setWindowFlags(Qt.Popup ) # self.setWindowFlags( # Qt.FramelessWindowHint | Qt.Popup | Qt.WA_TranslucentBackground) self.setAttribute(Qt.WA_StyledBackground) self._title_label = MLabel(parent=self).h4() # self._title_label.set_elide_mode(Qt.ElideRight) self._title_label.setText(title) self._close_button = MToolButton( parent=self).icon_only().svg('close_line.svg').small() self._close_button.clicked.connect(self.close) self._close_button.setVisible(closable or False) _title_lay = QHBoxLayout() _title_lay.addWidget(self._title_label) _title_lay.addStretch() _title_lay.addWidget(self._close_button) self._button_lay = QHBoxLayout() self._button_lay.addStretch() self._scroll_area = QScrollArea() self._main_lay = QVBoxLayout() self._main_lay.addLayout(_title_lay) self._main_lay.addWidget(MDivider()) self._main_lay.addWidget(self._scroll_area) self._main_lay.addWidget(MDivider()) self._main_lay.addLayout(self._button_lay) self.setLayout(self._main_lay) self._position = position self._close_timer = QTimer(self) self._close_timer.setSingleShot(True) self._close_timer.timeout.connect(self.close) self._close_timer.timeout.connect(self.sig_closed) self._close_timer.setInterval(300) self._is_first_close = True self._pos_ani = QPropertyAnimation(self) self._pos_ani.setTargetObject(self) self._pos_ani.setEasingCurve(QEasingCurve.OutCubic) self._pos_ani.setDuration(300) self._pos_ani.setPropertyName('pos') self._opacity_ani = QPropertyAnimation() self._opacity_ani.setTargetObject(self) self._opacity_ani.setDuration(300) self._opacity_ani.setEasingCurve(QEasingCurve.OutCubic) self._opacity_ani.setPropertyName('windowOpacity') self._opacity_ani.setStartValue(0.0) self._opacity_ani.setEndValue(1.0) # self._shadow_effect = QGraphicsDropShadowEffect(self) # color = dayu_theme.red # self._shadow_effect.setColor(color) # self._shadow_effect.setOffset(0, 0) # self._shadow_effect.setBlurRadius(5) # self._shadow_effect.setEnabled(False) # self.setGraphicsEffect(self._shadow_effect) self.app = QApplication.instance() self.app.installEventFilter(self) self.protect_time = time.time() def retrieveChildren(self, parent, receiver): if parent is receiver: return True if not hasattr(parent, "children"): return for child in parent.children(): ret = self.retrieveChildren(child, receiver) if ret: return ret def eventFilter(self, receiver, event): # Note QEvent.Type.MouseButtonPress 为 2 if event.type() == 2: if self.retrieveChildren(self, receiver): self.protect_time = time.time() # NOTE 如果点击多次触发,通过时间进行保护 if (time.time() - self.protect_time) > .1: self.close() elif event.type() == QEvent.Type.Resize and receiver is self.window(): self.close() return False def set_widget(self, widget): self._scroll_area.setWidget(widget) def add_button(self, button): self._button_lay.addWidget(button) def _fade_out(self): self._pos_ani.setDirection(QAbstractAnimation.Backward) self._pos_ani.start() self._opacity_ani.setDirection(QAbstractAnimation.Backward) self._opacity_ani.start() def _fade_int(self): self._pos_ani.start() self._opacity_ani.start() def _set_proper_position(self): parent = self.parent() parent_geo = parent.geometry() if self._position == MDrawer.LeftPos: pos = parent_geo.topLeft( ) if parent.parent() is None else parent.mapToGlobal( parent_geo.topLeft()) pos -= self.window().geometry().topLeft() target_x = pos.x() target_y = pos.y() self.setFixedHeight(parent_geo.height()) self._pos_ani.setStartValue( QPoint(target_x - self.width(), target_y)) self._pos_ani.setEndValue(QPoint(target_x, target_y)) if self._position == MDrawer.RightPos: pos = parent_geo.topRight( ) if parent.parent() is None else parent.mapToGlobal( parent_geo.topRight()) pos -= self.window().geometry().topLeft() self.setFixedHeight(parent_geo.height()) target_x = pos.x() - self.width() target_y = pos.y() self._pos_ani.setStartValue( QPoint(target_x + self.width(), target_y)) self._pos_ani.setEndValue(QPoint(target_x, target_y)) if self._position == MDrawer.TopPos: pos = parent_geo.topLeft( ) if parent.parent() is None else parent.mapToGlobal( parent_geo.topLeft()) pos -= self.window().geometry().topLeft() self.setFixedWidth(parent_geo.width()) target_x = pos.x() target_y = pos.y() self._pos_ani.setStartValue( QPoint(target_x, target_y - self.height())) self._pos_ani.setEndValue(QPoint(target_x, target_y)) if self._position == MDrawer.BottomPos: pos = parent_geo.bottomLeft( ) if parent.parent() is None else parent.mapToGlobal( parent_geo.bottomLeft()) pos -= self.window().geometry().topLeft() self.setFixedWidth(parent_geo.width()) target_x = pos.x() target_y = pos.y() - self.height() self._pos_ani.setStartValue( QPoint(target_x, target_y + self.height())) self._pos_ani.setEndValue(QPoint(target_x, target_y)) def set_dayu_position(self, value): """ Set the placement of the MDrawer. top/right/bottom/left, default is right :param value: str :return: None """ self._position = value if value in [MDrawer.BottomPos, MDrawer.TopPos]: self.setFixedHeight(200) else: self.setFixedWidth(200) def get_dayu_position(self): """ Get the placement of the MDrawer :return: str """ return self._position dayu_position = Property(str, get_dayu_position, set_dayu_position) def left(self): """Set drawer's placement to left""" self.set_dayu_position(MDrawer.LeftPos) return self def right(self): """Set drawer's placement to right""" self.set_dayu_position(MDrawer.RightPos) return self def top(self): """Set drawer's placement to top""" self.set_dayu_position(MDrawer.TopPos) return self def bottom(self): """Set drawer's placement to bottom""" self.set_dayu_position(MDrawer.BottomPos) return self def show(self): self._set_proper_position() self._fade_int() return super(MDrawer, self).show() def closeEvent(self, event): self.app.removeEventFilter(self) if self._is_first_close: self._is_first_close = False self._close_timer.start() self._fade_out() event.ignore() else: event.accept()
class MMeta(QWidget): def __init__(self, cover=None, avatar=None, title=None, description=None, extra=False, parent=None): super(MMeta, self).__init__(parent) self.setAttribute(Qt.WA_StyledBackground) self._cover_label = QLabel() self._avatar = MAvatar() self._title_label = MLabel().h4() self._description_label = MLabel().secondary() self._description_label.setWordWrap(True) self._description_label.set_elide_mode(Qt.ElideRight) self._title_layout = QHBoxLayout() self._title_layout.addWidget(self._title_label) self._title_layout.addStretch() self._extra_button = MToolButton( parent=self).icon_only().svg('more.svg') self._title_layout.addWidget(self._extra_button) self._extra_button.setVisible(extra) content_lay = QFormLayout() content_lay.setContentsMargins(5, 5, 5, 5) content_lay.addRow(self._avatar, self._title_layout) content_lay.addRow(self._description_label) self._button_layout = QHBoxLayout() main_lay = QVBoxLayout() main_lay.setSpacing(0) main_lay.setContentsMargins(1, 1, 1, 1) main_lay.addWidget(self._cover_label) main_lay.addLayout(content_lay) main_lay.addLayout(self._button_layout) main_lay.addStretch() self.setLayout(main_lay) self._cover_label.setFixedSize(QSize(200, 200)) # self.setFixedWidth(200) def get_more_button(self): return self._extra_button def setup_data(self, data_dict): if data_dict.get('title'): self._title_label.setText(data_dict.get('title')) self._title_label.setVisible(True) else: self._title_label.setVisible(False) if data_dict.get('description'): self._description_label.setText(data_dict.get('description')) self._description_label.setVisible(True) else: self._description_label.setVisible(False) if data_dict.get('avatar'): self._avatar.set_dayu_image(data_dict.get('avatar')) self._avatar.setVisible(True) else: self._avatar.setVisible(False) if data_dict.get('cover'): fixed_height = self._cover_label.width() self._cover_label.setPixmap( data_dict.get('cover').scaledToWidth(fixed_height, Qt.SmoothTransformation)) self._cover_label.setVisible(True) else: self._cover_label.setVisible(False)
class MDivider(QtWidgets.QWidget): """ A divider line separates different content. Property: dayu_text: six.string_types """ _alignment_map = { QtCore.Qt.AlignCenter: 50, QtCore.Qt.AlignLeft: 20, QtCore.Qt.AlignRight: 80, } def __init__( self, text="", orientation=QtCore.Qt.Horizontal, alignment=QtCore.Qt.AlignCenter, parent=None, ): super(MDivider, self).__init__(parent) self._orient = orientation self._text_label = MLabel().secondary() self._left_frame = QtWidgets.QFrame() self._right_frame = QtWidgets.QFrame() self._main_lay = QtWidgets.QHBoxLayout() self._main_lay.setContentsMargins(0, 0, 0, 0) self._main_lay.setSpacing(0) self._main_lay.addWidget(self._left_frame) self._main_lay.addWidget(self._text_label) self._main_lay.addWidget(self._right_frame) self.setLayout(self._main_lay) if orientation == QtCore.Qt.Horizontal: self._left_frame.setFrameShape(QtWidgets.QFrame.HLine) self._left_frame.setFrameShadow(QtWidgets.QFrame.Sunken) self._right_frame.setFrameShape(QtWidgets.QFrame.HLine) self._right_frame.setFrameShadow(QtWidgets.QFrame.Sunken) else: self._text_label.setVisible(False) self._right_frame.setVisible(False) self._left_frame.setFrameShape(QtWidgets.QFrame.VLine) self._left_frame.setFrameShadow(QtWidgets.QFrame.Plain) self.setFixedWidth(2) self._main_lay.setStretchFactor(self._left_frame, self._alignment_map.get(alignment, 50)) self._main_lay.setStretchFactor( self._right_frame, 100 - self._alignment_map.get(alignment, 50)) self._text = None self.set_dayu_text(text) def set_dayu_text(self, value): """ Set the divider's text. When text is empty, hide the text_label and right_frame to ensure the divider not has a gap. :param value: six.string_types :return: None """ self._text = value self._text_label.setText(value) if self._orient == QtCore.Qt.Horizontal: self._text_label.setVisible(bool(value)) self._right_frame.setVisible(bool(value)) def get_dayu_text(self): """ Get current text :return: six.string_types """ return self._text dayu_text = QtCore.Property(six.string_types[0], get_dayu_text, set_dayu_text) @classmethod def left(cls, text=""): """Create a horizontal divider with text at left.""" return cls(text, alignment=QtCore.Qt.AlignLeft) @classmethod def right(cls, text=""): """Create a horizontal divider with text at right.""" return cls(text, alignment=QtCore.Qt.AlignRight) @classmethod def center(cls, text=""): """Create a horizontal divider with text at center.""" return cls(text, alignment=QtCore.Qt.AlignCenter) @classmethod def vertical(cls): """Create a vertical divider""" return cls(orientation=QtCore.Qt.Vertical)
def _init_ui(self): title_lay = QGridLayout() title_lay.addWidget(MLabel(u'一级标题').h1(), 0, 0) title_lay.addWidget(MLabel(u'二级标题').h2(), 1, 0) title_lay.addWidget(MLabel(u'三级标题').h3(), 2, 0) title_lay.addWidget(MLabel(u'四级标题').h4(), 3, 0) title_lay.addWidget(MLabel('h1 Level').h1(), 0, 1) title_lay.addWidget(MLabel('h2 Level').h2(), 1, 1) title_lay.addWidget(MLabel('h3 Level').h3(), 2, 1) title_lay.addWidget(MLabel('h4 Level').h4(), 3, 1) text_type_lay = QHBoxLayout() text_type_lay.addWidget(MLabel('MLabel: Normal')) text_type_lay.addWidget(MLabel('MLabel: Secondary').secondary()) text_type_lay.addWidget(MLabel('MLabel: Warning').warning()) text_type_lay.addWidget(MLabel('MLabel: Danger').danger()) disable_text = MLabel('MLabel: Disabled') disable_text.setEnabled(False) text_type_lay.addWidget(disable_text) text_attr_lay = QHBoxLayout() text_attr_lay.addWidget(MLabel('MLabel: Mark').mark()) text_attr_lay.addWidget(MLabel('MLabel: Code').code()) text_attr_lay.addWidget(MLabel('MLabel: Underline').underline()) text_attr_lay.addWidget(MLabel('MLabel: Delete').delete()) text_attr_lay.addWidget(MLabel('MLabel: Strong').strong()) text_mix_lay = QHBoxLayout() text_mix_lay.addWidget( MLabel('MLabel: Strong & Underline').strong().underline()) text_mix_lay.addWidget( MLabel('MLabel: Danger & Delete').danger().delete()) text_mix_lay.addWidget( MLabel('MLabel: Warning & Strong').warning().strong()) text_mix_lay.addWidget(MLabel('MLabel: H4 & Mark').h4().mark()) data_bind_lay = QHBoxLayout() data_bind_label = MLabel() button = MPushButton(text='Random An Animal').primary() button.clicked.connect(self.slot_change_text) data_bind_lay.addWidget(data_bind_label) data_bind_lay.addWidget(button) data_bind_lay.addStretch() self.register_field('show_text', 'Guess') self.bind('show_text', data_bind_label, 'text') lay_elide = QVBoxLayout() label_none = MLabel('This is a elide NONE mode label. ' 'Ellipsis should NOT appear in the text.') label_left = MLabel( 'This is a elide LEFT mode label. ' 'The ellipsis should appear at the beginning of the text. ' 'xiao mao xiao gou xiao ci wei') label_left.set_elide_mode(Qt.ElideLeft) label_middle = MLabel( 'This is a elide MIDDLE mode label. ' 'The ellipsis should appear in the middle of the text. ' 'xiao mao xiao gou xiao ci wei') label_middle.set_elide_mode(Qt.ElideMiddle) label_right = MLabel() label_right.setText( 'This is a elide RIGHT mode label. ' 'The ellipsis should appear at the end of the text. ' 'Some text to fill the line bala bala bala.') label_right.set_elide_mode(Qt.ElideRight) lay_elide.addWidget(label_none) lay_elide.addWidget(label_left) lay_elide.addWidget(label_middle) lay_elide.addWidget(label_right) main_lay = QVBoxLayout() main_lay.addWidget(MDivider('different level')) main_lay.addLayout(title_lay) main_lay.addWidget(MDivider('different type')) main_lay.addLayout(text_type_lay) main_lay.addWidget(MDivider('different property')) main_lay.addLayout(text_attr_lay) main_lay.addWidget(MDivider('mix')) main_lay.addLayout(text_mix_lay) # main_lay.addWidget(MDivider('data bind')) # main_lay.addLayout(data_bind_lay) main_lay.addWidget(MDivider('elide mode')) main_lay.addLayout(lay_elide) main_lay.addStretch() self.setLayout(main_lay)
class MDrawer(QtWidgets.QWidget): """ A panel which slides in from the edge of the screen. """ LeftPos = "left" RightPos = "right" TopPos = "top" BottomPos = "bottom" sig_closed = QtCore.Signal() def __init__(self, title, position="right", closable=True, parent=None): super(MDrawer, self).__init__(parent) self.setObjectName("message") self.setWindowFlags(QtCore.Qt.Popup) # self.setWindowFlags( # Qt.FramelessWindowHint | Qt.Popup | Qt.WA_TranslucentBackground) self.setAttribute(QtCore.Qt.WA_StyledBackground) self._title_label = MLabel(parent=self).h4() # self._title_label.set_elide_mode(Qt.ElideRight) self._title_label.setText(title) self._close_button = (MToolButton( parent=self).icon_only().svg("close_line.svg").small()) self._close_button.clicked.connect(self.close) self._close_button.setVisible(closable or False) self._title_extra_lay = QtWidgets.QHBoxLayout() _title_lay = QtWidgets.QHBoxLayout() _title_lay.addWidget(self._title_label) _title_lay.addStretch() _title_lay.addLayout(self._title_extra_lay) _title_lay.addWidget(self._close_button) self._bottom_lay = QtWidgets.QHBoxLayout() self._bottom_lay.addStretch() self._scroll_area = QtWidgets.QScrollArea() self._scroll_area.setWidgetResizable(True) self._main_lay = QtWidgets.QVBoxLayout() self._main_lay.addLayout(_title_lay) self._main_lay.addWidget(MDivider()) self._main_lay.addWidget(self._scroll_area) self._main_lay.addWidget(MDivider()) self._main_lay.addLayout(self._bottom_lay) self.setLayout(self._main_lay) self._position = position self._close_timer = QtCore.QTimer(self) self._close_timer.setSingleShot(True) self._close_timer.timeout.connect(self.close) self._close_timer.timeout.connect(self.sig_closed) self._close_timer.setInterval(300) self._is_first_close = True self._pos_ani = QtCore.QPropertyAnimation(self) self._pos_ani.setTargetObject(self) self._pos_ani.setEasingCurve(QtCore.QEasingCurve.OutCubic) self._pos_ani.setDuration(300) self._pos_ani.setPropertyName(b"pos") self._opacity_ani = QtCore.QPropertyAnimation() self._opacity_ani.setTargetObject(self) self._opacity_ani.setDuration(300) self._opacity_ani.setEasingCurve(QtCore.QEasingCurve.OutCubic) self._opacity_ani.setPropertyName(b"windowOpacity") self._opacity_ani.setStartValue(0.0) self._opacity_ani.setEndValue(1.0) def set_widget(self, widget): self._scroll_area.setWidget(widget) def add_widget_to_bottom(self, button): self._bottom_lay.addWidget(button) def add_widget_to_top(self, button): self._title_extra_lay.addWidget(button) def _fade_out(self): self._pos_ani.setDirection(QtCore.QAbstractAnimation.Backward) self._pos_ani.start() self._opacity_ani.setDirection(QtCore.QAbstractAnimation.Backward) self._opacity_ani.start() def _fade_int(self): self._pos_ani.start() self._opacity_ani.start() def _set_proper_position(self): parent = self.parent() parent_geo = parent.geometry() if self._position == MDrawer.LeftPos: pos = (parent_geo.topLeft() if parent.parent() is None else parent.mapToGlobal(parent_geo.topLeft())) target_x = pos.x() target_y = pos.y() self.setFixedHeight(parent_geo.height()) self._pos_ani.setStartValue( QtCore.QPoint(target_x - self.width(), target_y)) self._pos_ani.setEndValue(QtCore.QPoint(target_x, target_y)) if self._position == MDrawer.RightPos: pos = (parent_geo.topRight() if parent.parent() is None else parent.mapToGlobal(parent_geo.topRight())) self.setFixedHeight(parent_geo.height()) target_x = pos.x() - self.width() target_y = pos.y() self._pos_ani.setStartValue( QtCore.QPoint(target_x + self.width(), target_y)) self._pos_ani.setEndValue(QtCore.QPoint(target_x, target_y)) if self._position == MDrawer.TopPos: pos = (parent_geo.topLeft() if parent.parent() is None else parent.mapToGlobal(parent_geo.topLeft())) self.setFixedWidth(parent_geo.width()) target_x = pos.x() target_y = pos.y() self._pos_ani.setStartValue( QtCore.QPoint(target_x, target_y - self.height())) self._pos_ani.setEndValue(QtCore.QPoint(target_x, target_y)) if self._position == MDrawer.BottomPos: pos = (parent_geo.bottomLeft() if parent.parent() is None else parent.mapToGlobal(parent_geo.bottomLeft())) self.setFixedWidth(parent_geo.width()) target_x = pos.x() target_y = pos.y() - self.height() self._pos_ani.setStartValue( QtCore.QPoint(target_x, target_y + self.height())) self._pos_ani.setEndValue(QtCore.QPoint(target_x, target_y)) def set_dayu_position(self, value): """ Set the placement of the MDrawer. top/right/bottom/left, default is right :param value: str :return: None """ self._position = value scale_x, _ = get_scale_factor() if value in [MDrawer.BottomPos, MDrawer.TopPos]: self.setFixedHeight(200 * scale_x) else: self.setFixedWidth(200 * scale_x) def get_dayu_position(self): """ Get the placement of the MDrawer :return: str """ return self._position dayu_position = QtCore.Property(str, get_dayu_position, set_dayu_position) def left(self): """Set drawer's placement to left""" self.set_dayu_position(MDrawer.LeftPos) return self def right(self): """Set drawer's placement to right""" self.set_dayu_position(MDrawer.RightPos) return self def top(self): """Set drawer's placement to top""" self.set_dayu_position(MDrawer.TopPos) return self def bottom(self): """Set drawer's placement to bottom""" self.set_dayu_position(MDrawer.BottomPos) return self def show(self): self._set_proper_position() self._fade_int() super(MDrawer, self).show() # NOTES(timmyliang): for chinese input self.activateWindow() def closeEvent(self, event): if self._is_first_close: self._is_first_close = False self._close_timer.start() self._fade_out() event.ignore() else: event.accept()