def overlay_text( self, message: str, color: int, size: int, x: int, y: int, timeout: int, font_name: str, centered: bool, shadow: bool, ): gfx = QGraphicsTextItem(message) gfx.setDefaultTextColor(decode_color(color)) font = QFont(font_name, min(50, size)) font.setStyleHint(QFont.SansSerif) gfx.setFont(font) if shadow: effect = QGraphicsDropShadowEffect(gfx) effect.setBlurRadius(0) effect.setColor(Qt.GlobalColor.black) effect.setOffset(1, 1) gfx.setGraphicsEffect(effect) if centered: # The provided x, y is at the center of the text bound = gfx.boundingRect() gfx.setPos(x - (bound.width() / 2), y - (bound.height() / 2)) else: gfx.setPos(x, y) self._finalize_gfx(gfx, timeout)
def css_button(qtwindow, button, color=None, shadow=True): ''' apply style to a button widget ''' # default bg color is gray 80 if color == 'red': bg_color = 'rgb(160, 20, 20)' # red/white tx_color = 'rgb(255, 255, 255)' elif color == 'disabled': bg_color = 'rgb(80, 80, 80)' # gray/gray tx_color = 'rgb(180, 180, 180)' elif color == 'blue': bg_color = 'rgb(46, 134, 193)' # blue arcane/white tx_color = 'rgb(230, 230, 230)' else: bg_color = 'rgb(80, 80, 80)' # gray tx_color = 'rgb(230, 230, 230)' css = "border-radius:3px;color:{};background:{};font-size:12px;font-family:Segoe UI;".format( tx_color, bg_color) button.setStyleSheet(css) if shadow: shadow = QGraphicsDropShadowEffect(qtwindow) shadow.setBlurRadius(6) shadow.setOffset(4) shadow.setColor(QColor(20, 20, 20, 200)) button.setGraphicsEffect(shadow)
def card_shadow(self): effect = QGraphicsDropShadowEffect(self.main_widget) effect.setColor(QColor(0, 0, 0, 100)) effect.setXOffset(2) effect.setYOffset(2) effect.setBlurRadius(5) return effect
def button_shadow(self): effect = QGraphicsDropShadowEffect(self.window) effect.setColor(QColor(0, 0, 0, 100)) effect.setXOffset(1) effect.setYOffset(1) effect.setBlurRadius(5) return effect
def __init__(self, text: str, object_name: str = "blue"): super().__init__(text) self.setCursor(Qt.PointingHandCursor) if object_name: self.setObjectName(object_name) effect = QGraphicsDropShadowEffect(self) effect.setColor(QColor(0, 0, 0, 0.25 * 255)) effect.setOffset(2, 4) effect.setBlurRadius(4) self.setGraphicsEffect(effect)
def add_window_drop_shadow() -> None: """Adds a drop-shadow behind the window""" if self.__use_shadow: self.layout().setMargin(self.__style.window.SHADOW_RADIUS_PX) drop_shadow_effect = QGraphicsDropShadowEffect(self) drop_shadow_effect.setEnabled(True) drop_shadow_effect.setBlurRadius( self.__style.window.SHADOW_RADIUS_PX) color = QColor(self.__style.window.SHADOW_COLOR_RGB) color.setAlpha(self.__style.window.SHADOW_OPACITY_HEX) drop_shadow_effect.setColor(color) drop_shadow_effect.setOffset(0) self.setGraphicsEffect(drop_shadow_effect)
class MainWindow(QtWidgets.QMainWindow): role_value = None DATA_PATH = 'D:\\SASTRA\Code\\Dean Mam Project\\assignment\\Data\\Medical_Records.csv' def __init__(self): print('Main init') QtWidgets.QMainWindow.__init__(self) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.ui.stackedWidget.setCurrentIndex(0) for i in RBACClass.permission_dict['Administrator']: self.ui.tableWidget.setColumnHidden(i, True) ## ==> MAXIMIZE RESTORE FUNCTION def maximize_restore(): global GLOBAL_STATE status = GLOBAL_STATE # IF NOT MAXIMIZED if status == 0: self.showMaximized() # SET GLOBAL TO 1 GLOBAL_STATE = 1 # IF MAXIMIZED REMOVE MARGINS AND BORDER RADIUS self.ui.drop_shadow_frame.setContentsMargins(0, 0, 0, 0) self.ui.drop_shadow_frame.setStyleSheet( "background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:1, stop:0 rgba(42, 44, 111, 255), stop:0.521368 rgba(28, 29, 73, 255)); border-radius: 0px;" ) self.ui.maximize_btn.setToolTip("Restore") else: GLOBAL_STATE = 0 self.showNormal() self.resize(self.width() + 1, self.height() + 1) self.ui.drop_shadow_frame.setContentsMargins(10, 10, 10, 10) self.ui.drop_shadow_frame.setStyleSheet( "background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:1, stop:0 rgba(42, 44, 111, 255), stop:0.521368 rgba(28, 29, 73, 255)); border-radius: 10px;" ) self.ui.maximize_btn.setToolTip("Maximize") ## ==> UI DEFINITIONS def uiDefinitions(): # REMOVE TITLE BAR self.setWindowFlag(QtCore.Qt.FramelessWindowHint) self.setAttribute(QtCore.Qt.WA_TranslucentBackground) # SET DROPSHADOW WINDOW self.shadow = QGraphicsDropShadowEffect(self) self.shadow.setBlurRadius(20) self.shadow.setXOffset(0) self.shadow.setYOffset(0) self.shadow.setColor(QColor(0, 0, 0, 100)) # APPLY DROPSHADOW TO FRAME self.ui.drop_shadow_frame.setGraphicsEffect(self.shadow) # MAXIMIZE / RESTORE self.ui.maximize_btn.clicked.connect(lambda: maximize_restore()) # MINIMIZE self.ui.minimize_btn.clicked.connect(lambda: self.showMinimized()) # CLOSE self.ui.close_btn.clicked.connect(lambda: self.close()) ## ==> CREATE SIZE GRIP TO RESIZE WINDOW self.sizegrip = QSizeGrip(self.ui.frame_grip) self.sizegrip.setStyleSheet( "QSizeGrip { width: 10px; height: 10px; margin: 5px } QSizeGrip:hover { background-color: rgb(50, 42, 94) }" ) self.sizegrip.setToolTip("Resize Window") def rolePermission(role): for i in role: self.ui.tableWidget.setColumnHidden(i, False) # MOVE WINDOW def moveWindowMain(event): # RESTORE BEFORE MOVE if self.returnStatus() == 1: self.maximize_restore(self) # IF LEFT CLICK MOVE WINDOW if event.buttons() == Qt.LeftButton: self.move(self.pos() + event.globalPos() - self.dragPos) self.dragPos = event.globalPos() event.accept() def view_details(): patient_id = self.ui.patientIdLineEdit_2.text() print(patient_id) self.ui.stackedWidget.setCurrentIndex(1) with open(MainWindow.DATA_PATH, 'r') as datafile: print('inside file') readData = csv.reader(datafile) rowValue = 0 for row in readData: print('inside row') if row[1] == patient_id: self.ui.tableWidget.insertRow(rowValue) print(row[1], type(row[1])) for i in MainWindow.role_value: print('set') self.ui.tableWidget.setItem( rowValue, i, QTableWidgetItem(row[i])) rowValue = rowValue + 1 def go_back(): self.ui.stackedWidget.setCurrentIndex(0) ## ==> SET UI DEFINITIONS uiDefinitions() print(MainWindow.role_value, "rolePerm") rolePermission(MainWindow.role_value) self.ui.viewDetailsPushButton_2.clicked.connect(view_details) self.ui.back_btn.clicked.connect(go_back) # SET TITLE BAR self.ui.title_bar_2.mouseMoveEvent = moveWindowMain ## SHOW ==> MAIN WINDOW ######################################################################## self.show() ## RETURN STATUS IF WINDOWS IS MAXIMIZE OR RESTAURED def returnStatus(self): return GLOBAL_STATE ## APP EVENTS ######################################################################## def mousePressEvent(self, event): self.dragPos = event.globalPos()
class LoginWindow(QtWidgets.QWidget): def __init__(self): QtWidgets.QWidget.__init__(self) self.ui = Ui_Form() self.ui.setupUi(self) def uiDefinition(): # REMOVE TITLE BAR self.shadow = QGraphicsDropShadowEffect(self) self.setWindowFlag(QtCore.Qt.FramelessWindowHint) self.setAttribute(QtCore.Qt.WA_TranslucentBackground) # SET DROPSHADOW WINDOW self.shadow.setBlurRadius(20) self.shadow.setXOffset(0) self.shadow.setYOffset(0) self.shadow.setColor(QColor(0, 0, 0, 100)) # APPLY DROPSHADOW TO FRAME self.ui.login_frame.setGraphicsEffect(self.shadow) # CLOSE self.ui.closePushButton.clicked.connect(lambda: self.close()) # MOVE WINDOW def moveWindowLogin(event): # IF LEFT CLICK MOVE WINDOW if event.buttons() == Qt.LeftButton(): self.move(self.pos() + event.globalPos() - self.dragPos) self.dragPos = event.globalPos() event.accept() def show_new_window(): print(self.ui.userNameLineEdit.text(), self.ui.passwordLineEdit.text()) r = RBACClass() MainWindow.role_value = r.logon( UserName=self.ui.userNameLineEdit.text(), Password=self.ui.passwordLineEdit.text()) print(MainWindow.role_value, "show") if MainWindow.role_value != None: print('here') self.close() w = MainWindow() w.show() else: print('no') self.ui.userNameLineEdit.setPlaceholderText('Try Again') self.ui.passwordLineEdit.setPlaceholderText('Try Again') # SET TITLE BAR self.ui.login_frame.mouseMoveEvent = moveWindowLogin self.ui.loginPushButton.clicked.connect(show_new_window) ## ==> SET UI DEFINITIONS uiDefinition() ## SHOW ==> MAIN WINDOW ######################################################################## self.show() ## APP EVENTS ######################################################################## def mousePressEvent(self, event): self.dragPos = event.globalPos()
class NodeInstance(QGraphicsItem): def __init__(self, params): super(NodeInstance, self).__init__() self.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemSendsScenePositionChanges) self.setAcceptHoverEvents(True) # GENERAL ATTRIBUTES # the constructor parameters are stored in a tuple to make the source code of custom NIs cleaner parent_node, flow, config = params self.parent_node = parent_node self.flow = flow self.movement_state = None self.movement_pos_from = None self.painted_once = False self.inputs = [] self.outputs = [] self.color = self.parent_node.color # manipulated by self.animator # self.node_instance_painter = NodeInstancePainter(self) self.default_actions = { 'remove': { 'method': self.action_remove }, 'update shape': { 'method': self.update_shape } } # for context menus self.special_actions = { } # only gets written in custom NodeInstance-subclasses self.personal_logs = [] # 'initializing' will be set to False below. It's needed for the ports setup, to prevent shape updating stuff self.initializing = True self.temp_state_data = None self.init_config = config # UI self.shadow_effect = None self.width = -1 self.height = -1 self.title_label = TitleLabel(self) self.animator = NodeInstanceAnimator(self) # needs self.title_label self.main_widget = None self.main_widget_proxy: FlowProxyWidget = None if self.parent_node.has_main_widget: self.main_widget = self.parent_node.main_widget_class(self) self.main_widget_proxy = FlowProxyWidget(self.flow) self.main_widget_proxy.setWidget(self.main_widget) # LOADING UI self.body_layout: QGraphicsLinearLayout = None self.inputs_layout: QGraphicsLinearLayout = None self.outputs_layout: QGraphicsLinearLayout = None self.layout: QGraphicsLinearLayout = self.setup_ui() self.widget = QGraphicsWidget(self) self.widget.setLayout(self.layout) # TOOLTIP if self.parent_node.description != '': self.setToolTip('<html><head/><body><p>' + self.parent_node.description + '</p></body></html>') self.setCursor(Qt.SizeAllCursor) # DESIGN THEME Design.flow_theme_changed.connect(self.theme_changed) def initialized(self): """All ports and the main widget get finally created here.""" # LOADING CONFIG if self.init_config is not None: # self.setPos(config['position x'], config['position y']) self.setup_ports(self.init_config['inputs'], self.init_config['outputs']) if self.main_widget: try: self.main_widget.set_data( self.init_config['main widget data']) except Exception as e: print('Exception while setting data in', self.parent_node.title, 'NodeInstance\'s main widget:', e, ' (was this intended?)') self.special_actions = self.set_special_actions_data( self.init_config['special actions']) self.temp_state_data = self.init_config['state data'] else: self.setup_ports() # LOADING DATA if self.temp_state_data is not None: try: self.set_data(self.temp_state_data) except Exception as e: print('Exception while setting data in', self.parent_node.title, 'NodeInstance:', e, ' (was this intended?)') self.initializing = False # No self.update_shape() here because for some reason, the bounding rect hasn't been initialized yet, so # self.update_shape() gets called when the item is being drawn the first time (see paint event in NI painter) # TODO: change that ^ once there is a solution for this: https://forum.qt.io/topic/117179/force-qgraphicsitem-to-update-immediately-wait-for-update-event self.update_design() # load current design, update QGraphicsItem self.update() # and finally update the NodeInstance once def setup_ui(self): """Creates the empty layouts for the NI's widget.""" # main layout layout = QGraphicsLinearLayout(Qt.Vertical) layout.setSpacing(10) if self.parent_node.design_style == 'extended': layout.addItem(self.title_label) layout.setAlignment(self.title_label, Qt.AlignTop) # inputs self.inputs_layout = QGraphicsLinearLayout(Qt.Vertical) self.inputs_layout.setSpacing(2) # outputs self.outputs_layout = QGraphicsLinearLayout(Qt.Vertical) self.outputs_layout.setSpacing(2) # body self.body_layout = QGraphicsLinearLayout(Qt.Horizontal) self.body_layout.setSpacing(4) self.body_layout.addItem(self.inputs_layout) self.body_layout.setAlignment(self.inputs_layout, Qt.AlignVCenter | Qt.AlignLeft) self.body_layout.addStretch() self.body_layout.addItem(self.outputs_layout) self.body_layout.setAlignment(self.outputs_layout, Qt.AlignVCenter | Qt.AlignRight) if self.main_widget is not None: if self.parent_node.main_widget_pos == 'between ports': self.body_layout.insertItem(1, self.main_widget_proxy) self.body_layout.insertStretch(2) layout.addItem(self.body_layout) elif self.parent_node.main_widget_pos == 'under ports': layout.addItem(self.body_layout) layout.addItem(self.main_widget_proxy) layout.setAlignment(self.main_widget_proxy, Qt.AlignHCenter) else: layout.addItem(self.body_layout) return layout def rebuild_ui(self): """Due to some really strange and annoying behaviour of these QGraphicsWidgets, they don't want to shrink automatically when content is removed, they just stay large, even with a Minimum SizePolicy. I didn't find a way around that yet, so for now I have to recreate the whole layout and make sure the widget uses the smallest size possible.""" # if I don't manually remove the ports from the layouts, # they get deleted when setting the widget's layout to None below for inp in self.inputs: self.inputs_layout.removeAt(0) for out in self.outputs: self.outputs_layout.removeAt(0) self.layout = self.setup_ui() # recreate layout # forcefully making the widget shrink self.widget.setLayout(None) self.widget.resize(self.widget.minimumSize()) self.widget.setLayout(self.layout) # add inputs to new layout for inp in self.inputs: self.add_input_to_layout(inp) for out in self.outputs: self.add_output_to_layout(out) # __ _ __ __ # ____ _ / / ____ _ ____ _____ (_) / /_ / /_ ____ ___ # / __ `/ / / / __ `/ / __ \ / ___/ / / / __/ / __ \ / __ `__ \ # / /_/ / / / / /_/ / / /_/ / / / / / / /_ / / / / / / / / / / # \__,_/ /_/ \__, / \____/ /_/ /_/ \__/ /_/ /_/ /_/ /_/ /_/ # /____/ def update(self, input_called=-1, output_called=-1): """This is the method used to activate a NodeInstance. Note that this signature shadows the update() method from QGraphicsItem used to graphically update a QGraphicsItem which can be accessed via QGraphicsItem.update(self).""" if Design.animations_enabled: self.animator.start() Debugger.debug('update in', self.parent_node.title, 'on input', input_called) try: self.update_event(input_called) except Exception as e: Debugger.debug('EXCEPTION IN', self.parent_node.title, 'NI:', e) def update_event(self, input_called=-1): """Gets called when an input received a signal. This is where the magic begins in subclasses.""" pass def input(self, index): """Returns the value of a data input. If the input is connected, the value of the connected output is used: If not, the value of the widget is used.""" Debugger.debug('input called in', self.parent_node.title, 'NI:', index) return self.inputs[index].get_val() def exec_output(self, index): """Executes an execution output, sending a signal to all connected execution inputs causing the connected NIs to update.""" self.outputs[index].exec() def set_output_val(self, index, val): """Sets the value of a data output. self.data_outputs_updated() has to be called manually after all values are set.""" if not self.flow.viewport_update_mode.sync: # asynchronous viewport updates vp = self.flow.viewport() vp.repaint(self.flow.mapFromScene(self.sceneBoundingRect())) self.outputs[index].set_val(val) def data_outputs_updated(self): """(outdated!) Sends update signals to all data outputs causing connected NIs to update.""" Debugger.debug('updating data outputs in', self.parent_node.title) for o in self.outputs: if o.type_ == 'data': o.updated_val() Debugger.debug('data outputs in', self.parent_node.title, 'updated') def remove_event(self): """Method to stop all threads in hold of the NI itself.""" pass # _ # ____ _ ____ (_) # / __ `/ / __ \ / / # / /_/ / / /_/ / / / # \__,_/ / .___/ /_/ # /_/ # # all algorithm-unrelated api methods: # LOGGING def new_log(self, title): """Requesting a new personal Log. Handy method for subclasses.""" new_log = self.flow.parent_script.logger.new_log(self, title) self.personal_logs.append(new_log) return new_log def disable_personal_logs(self): """Disables personal Logs. They remain visible unless the user closes them via the appearing button.""" for log in self.personal_logs: log.disable() def enable_personal_logs(self): """Resets personal Logs to normal state (hiding close button, changing style sheet).""" for log in self.personal_logs: log.enable() def log_message(self, message: str, target='global'): """Access to global_tools Script Logs ('global' or 'error').""" self.flow.parent_script.logger.log_message(message, target) # SHAPE def update_shape(self): """Causes recompilation of the whole shape.""" # if not self.initializing: # just to make sure # self.rebuild_ui() # (hopefully) temporary fix -> see rebuild_ui() docstring if self.main_widget is not None: # maybe the main_widget got resized self.main_widget_proxy.setMaximumSize(self.main_widget.size()) self.widget.adjustSize() self.widget.adjustSize() self.body_layout.invalidate() self.layout.invalidate() self.layout.activate() # very essential; repositions everything in case content has changed (inputs/outputs/widget) if self.parent_node.design_style == 'minimalistic': # making it recompute its true minimumWidth here self.widget.adjustSize() if self.layout.minimumWidth() < self.title_label.width + 15: self.layout.setMinimumWidth(self.title_label.width + 15) self.layout.activate() self.width = self.boundingRect().width() self.height = self.boundingRect().height() rect = QRectF(QPointF(-self.width / 2, -self.height / 2), QPointF(self.width / 2, self.height / 2)) self.widget.setPos(rect.left(), rect.top()) if not self.parent_node.design_style == 'extended': self.title_label.setPos( QPointF(-self.title_label.boundingRect().width() / 2, -self.title_label.boundingRect().height() / 2)) self.flow.viewport().update() # PORTS def create_new_input(self, type_, label, widget_name=None, widget_pos='under', pos=-1, config=None): """Creates and adds a new input. Handy for subclasses.""" Debugger.debug('create_new_input called') pi = InputPortInstance(self, type_, label, config_data=config, widget_name=widget_name, widget_pos=widget_pos) if pos < -1: pos += len(self.inputs) if pos == -1: self.inputs.append(pi) self.add_input_to_layout(pi) else: self.inputs.insert(pos, pi) self.insert_input_into_layout(pos, pi) if not self.initializing: self.update_shape() self.update() def add_input_to_layout(self, i): if self.inputs_layout.count() > 0: self.inputs_layout.addStretch() self.inputs_layout.addItem(i) self.inputs_layout.setAlignment(i, Qt.AlignLeft) def insert_input_into_layout(self, index, i): self.inputs_layout.insertItem(index * 2, i) # *2 because of the stretches self.inputs_layout.setAlignment(i, Qt.AlignLeft) if len(self.inputs) > 1: self.inputs_layout.insertStretch( index * 2 + 1) # *2+1 because of the stretches, too def delete_input(self, i): """Disconnects and removes input. Handy for subclasses.""" inp: InputPortInstance = None if type(i) == int: inp = self.inputs[i] elif type(i) == InputPortInstance: inp = i for cpi in inp.connected_port_instances: self.flow.connect_gates(inp.gate, cpi.gate) # for some reason, I have to remove all widget items manually from the scene too. setting the items to # ownedByLayout(True) does not work, I don't know why. self.scene().removeItem(inp.gate) self.scene().removeItem(inp.label) if inp.proxy is not None: self.scene().removeItem(inp.proxy) self.inputs_layout.removeItem(inp) self.inputs.remove(inp) # just a temporary workaround for the issues discussed here: # https://forum.qt.io/topic/116268/qgraphicslayout-not-properly-resizing-to-change-of-content self.rebuild_ui() if not self.initializing: self.update_shape() self.update() def create_new_output(self, type_, label, pos=-1): """Creates and adds a new output. Handy for subclasses.""" pi = OutputPortInstance(self, type_, label) if pos < -1: pos += len(self.outputs) if pos == -1: self.outputs.append(pi) self.add_output_to_layout(pi) else: self.outputs.insert(pos, pi) self.insert_output_into_layout(pos, pi) if not self.initializing: self.update_shape() self.update() def add_output_to_layout(self, o): if self.outputs_layout.count() > 0: self.outputs_layout.addStretch() self.outputs_layout.addItem(o) self.outputs_layout.setAlignment(o, Qt.AlignRight) def insert_output_into_layout(self, index, o): self.outputs_layout.insertItem(index * 2, o) # *2 because of the stretches self.outputs_layout.setAlignment(o, Qt.AlignRight) if len(self.outputs) > 1: self.outputs_layout.insertStretch( index * 2 + 1) # *2+1 because of the stretches, too def delete_output(self, o): """Disconnects and removes output. Handy for subclasses.""" out: OutputPortInstance = None if type(o) == int: out = self.outputs[o] elif type(o) == OutputPortInstance: out = o for cpi in out.connected_port_instances: self.flow.connect_gates(out.gate, cpi.gate) # see delete_input() for info! self.scene().removeItem(out.gate) self.scene().removeItem(out.label) self.outputs_layout.removeItem(out) self.outputs.remove(out) # just a temporary workaround for the issues discussed here: # https://forum.qt.io/topic/116268/qgraphicslayout-not-properly-resizing-to-change-of-content self.rebuild_ui() if not self.initializing: self.update_shape() self.update() # GET, SET DATA def get_data(self): """ This method gets subclassed and specified. If the NI has states (so, the behavior depends on certain values), all these values must be stored in JSON-able format in a dict here. This dictionary will be used to reload the node's state when loading a project or pasting copied/cut nodes in the Flow (the states get copied too), see self.set_data(self, data) below. Unfortunately, I can't use pickle or something like that due to PySide2 which runs on C++, not Python. :return: Dictionary representing all values necessary to determine the NI's current state """ return {} def set_data(self, data): """ If the NI has states, it's state should get reloaded here according to what was previously provided by the same class in get_data(), see above. :param data: Dictionary representing all values necessary to determine the NI's current state """ pass @staticmethod def get_default_stylesheet(): """Handy method for subclasses to access the application window's stylesheet for UI content.""" return Design.ryven_stylesheet # VARIABLES def get_vars_handler(self): return self.flow.parent_script.variables_handler def get_var_val(self, name): return self.get_vars_handler().get_var_val(name) def set_var_val(self, name, val): return self.get_vars_handler().set_var(name, val) def register_var_receiver(self, name, method): self.get_vars_handler().register_receiver(self, name, method) def unregister_var_receiver(self, name): self.get_vars_handler().unregister_receiver(self, name) # -------------------------------------------------------------------------------------- # UI STUFF ---------------------------------------- def theme_changed(self, new_theme): self.title_label.theme_changed(new_theme) self.update_design() def update_design(self): """Loads the shadow effect option and causes redraw with active theme.""" if Design.node_instance_shadows_shown: self.shadow_effect = QGraphicsDropShadowEffect() self.shadow_effect.setXOffset(12) self.shadow_effect.setYOffset(12) self.shadow_effect.setBlurRadius(20) self.shadow_effect.setColor(QColor('#2b2b2b')) self.setGraphicsEffect(self.shadow_effect) else: self.setGraphicsEffect(None) self.title_label.update_design() # print(self.title_label.color) self.animator.reload_values() QGraphicsItem.update(self) def boundingRect(self): # remember: (0, 0) shall be the NI's center! rect = QRectF() w = self.layout.geometry().width() h = self.layout.geometry().height() rect.setLeft(-w / 2) rect.setTop(-h / 2) rect.setWidth(w) rect.setHeight(h) return rect # PAINTING def paint(self, painter, option, widget=None): """All painting is done by NodeInstancePainter""" # in order to access a meaningful geometry of GraphicsWidget contents in update_shape(), the paint event # has to be called once. See here: # https://forum.qt.io/topic/117179/force-qgraphicsitem-to-update-immediately-wait-for-update-event/4 if not self.painted_once: self.title_label.update_design() # also necessary self.update_shape() # self.node_instance_painter.paint(painter, option, self.color, self.width, self.height, self.boundingRect(), # Design.flow_theme, widget) Design.flow_theme.node_inst_painter.paint_NI( design_style=self.parent_node.design_style, painter=painter, option=option, c=self.color, w=self.width, h=self.height, bounding_rect=self.boundingRect(), title_rect=self.title_label.boundingRect()) self.painted_once = True def get_context_menu(self): menu = QMenu(self.flow) for a in self.get_actions(self.get_extended_default_actions(), menu): # menu needed for 'parent' if type(a) == NodeInstanceAction: menu.addAction(a) elif type(a) == QMenu: menu.addMenu(a) menu.addSeparator() actions = self.get_actions(self.special_actions, menu) for a in actions: # menu needed for 'parent' if type(a) == NodeInstanceAction: menu.addAction(a) elif type(a) == QMenu: menu.addMenu(a) return menu def itemChange(self, change, value): """This method ensures that all connections, selection borders etc. that get drawn in the Flow are constantly redrawn during a NI drag. Should get disabled when running in performance mode - not implemented yet.""" if change == QGraphicsItem.ItemPositionChange: if Design.performance_mode == 'pretty': self.flow.viewport().update() if self.movement_state == MovementEnum.mouse_clicked: self.movement_state = MovementEnum.position_changed return QGraphicsItem.itemChange(self, change, value) def hoverEnterEvent(self, event): self.title_label.set_NI_hover_state(hovering=True) QGraphicsItem.hoverEnterEvent(self, event) def hoverLeaveEvent(self, event): self.title_label.set_NI_hover_state(hovering=False) QGraphicsItem.hoverLeaveEvent(self, event) def mousePressEvent(self, event): """Used for Moving-Commands in Flow - may be replaced later with a nicer determination of a moving action.""" self.movement_state = MovementEnum.mouse_clicked self.movement_pos_from = self.pos() return QGraphicsItem.mousePressEvent(self, event) def mouseReleaseEvent(self, event): """Used for Moving-Commands in Flow - may be replaced later with a nicer determination of a moving action.""" if self.movement_state == MovementEnum.position_changed: self.flow.selected_components_moved(self.pos() - self.movement_pos_from) self.movement_state = None return QGraphicsItem.mouseReleaseEvent(self, event) # ACTIONS def get_extended_default_actions(self): actions_dict = self.default_actions.copy() for index in range(len(self.inputs)): inp = self.inputs[index] if inp.type_ == 'exec': actions_dict['exec input ' + str(index)] = { 'method': self.action_exec_input, 'data': { 'input index': index } } return actions_dict def action_exec_input(self, data): self.update(data['input index']) def get_actions(self, actions_dict, menu): actions = [] for k in actions_dict: v_dict = actions_dict[k] try: method = v_dict['method'] data = None try: data = v_dict['data'] except KeyError: pass action = NodeInstanceAction(k, menu, data) action.triggered_with_data.connect( method) # see NodeInstanceAction for explanation action.triggered_without_data.connect( method) # see NodeInstanceAction for explanation actions.append(action) except KeyError: action_menu = QMenu(k, menu) sub_actions = self.get_actions(v_dict, action_menu) for a in sub_actions: action_menu.addAction(a) actions.append(action_menu) return actions def action_remove(self): self.flow.remove_node_instance_triggered(self) def get_special_actions_data(self, actions): cleaned_actions = actions.copy() for key in cleaned_actions: v = cleaned_actions[key] if type(v) == M: # callable(v): cleaned_actions[key] = v.method_name elif callable(v): cleaned_actions[key] = v.__name__ elif type(v) == dict: cleaned_actions[key] = self.get_special_actions_data(v) else: cleaned_actions[key] = v return cleaned_actions def set_special_actions_data(self, actions_data): actions = {} for key in actions_data: if type(actions_data[key]) != dict: if key == 'method': try: actions['method'] = M(getattr(self, actions_data[key])) except AttributeError: # outdated method referenced pass elif key == 'data': actions['data'] = actions_data[key] else: actions[key] = self.set_special_actions_data(actions_data[key]) return actions # PORTS def setup_ports(self, inputs_config=None, outputs_config=None): if not inputs_config and not outputs_config: for i in range(len(self.parent_node.inputs)): inp = self.parent_node.inputs[i] self.create_new_input( inp.type_, inp.label, widget_name=self.parent_node.inputs[i].widget_name, widget_pos=self.parent_node.inputs[i].widget_pos) for o in range(len(self.parent_node.outputs)): out = self.parent_node.outputs[o] self.create_new_output(out.type_, out.label) else: # when loading saved NIs, the port instances might not be synchronised to the parent's ports anymore for inp in inputs_config: has_widget = inp['has widget'] self.create_new_input( inp['type'], inp['label'], widget_name=inp['widget name'] if has_widget else None, widget_pos=inp['widget position'] if has_widget else None, config=inp['widget data'] if has_widget else None) for out in outputs_config: self.create_new_output(out['type'], out['label']) def get_input_widget_class(self, widget_name): """Returns a reference to the widget class of a given name for instantiation.""" custom_node_input_widget_classes = self.flow.parent_script.main_window.custom_node_input_widget_classes widget_class = custom_node_input_widget_classes[ self.parent_node][widget_name] return widget_class def add_input_to_scene(self, i): self.flow.scene().addItem(i.gate) self.flow.scene().addItem(i.label) if i.widget: self.flow.scene().addItem(i.proxy) def del_and_remove_input_from_scene(self, i_index): i = self.inputs[i_index] for p in self.inputs[i_index].connected_port_instances: self.flow.connect_gates(i.gate, p.gate) self.flow.scene().removeItem(i.gate) self.flow.scene().removeItem(i.label) if i.widget: self.flow.scene().removeItem(i.proxy) i.widget.remove_event() self.inputs.remove(i) def add_output_to_scene(self, o): self.flow.scene().addItem(o.gate) self.flow.scene().addItem(o.label) def del_and_remove_output_from_scene(self, o_index): o = self.outputs[o_index] for p in self.outputs[o_index].connected_port_instances: self.flow.connect_gates(o.gate, p.gate) self.flow.scene().removeItem(o.gate) self.flow.scene().removeItem(o.label) self.outputs.remove(o) # GENERAL def about_to_remove_from_scene(self): """Called from Flow when the NI gets removed from the scene to stop all running threads and disable personal logs.""" if self.main_widget: self.main_widget.remove_event() self.remove_event() self.disable_personal_logs() def is_active(self): for i in self.inputs: if i.type_ == 'exec': return True for o in self.outputs: if o.type_ == 'exec': return True return False def has_main_widget(self): """Might be used later in CodePreview_Widget to enable not only showing the NI's class but also it's main_widget's class.""" return self.main_widget is not None def get_input_widgets(self): """Might be used later in CodePreview_Widget to enable not only showing the NI's class but its input widgets' classes.""" input_widgets = [] for i in range(len(self.inputs)): inp = self.inputs[i] if inp.widget is not None: input_widgets.append({i: inp.widget}) return input_widgets def get_json_data(self): """Returns all metadata of the NI including position, package etc. in a JSON-able dict format. Used to rebuild the Flow when loading a project.""" # general attributes node_instance_dict = { 'parent node title': self.parent_node.title, 'parent node type': self.parent_node.type_, 'parent node package': self.parent_node.package, 'parent node description': self.parent_node.description, 'position x': self.pos().x(), 'position y': self.pos().y() } if self.main_widget: node_instance_dict['main widget data'] = self.main_widget.get_data( ) node_instance_dict['state data'] = self.get_data() node_instance_dict['special actions'] = self.get_special_actions_data( self.special_actions) # inputs node_instance_inputs_list = [] for i in self.inputs: input_dict = i.get_json_data() node_instance_inputs_list.append(input_dict) node_instance_dict['inputs'] = node_instance_inputs_list # outputs node_instance_outputs_list = [] for o in self.outputs: output_dict = o.get_json_data() node_instance_outputs_list.append(output_dict) node_instance_dict['outputs'] = node_instance_outputs_list return node_instance_dict
def set_shadow(QtWindow, button): shadow = QGraphicsDropShadowEffect(QtWindow) shadow.setBlurRadius(6) shadow.setOffset(3) shadow.setColor(QColor(0, 0, 0, 160)) button.setGraphicsEffect(shadow)
class UIFunctions(MainWindow): ## ==> MAXIMIZE RESTORE FUNCTION def maximize_restore(self): global GLOBAL_STATE status = GLOBAL_STATE # IF NOT MAXIMIZED if status == 0: self.showMaximized() # SET GLOBAL TO 1 GLOBAL_STATE = 1 # IF MAXIMIZED REMOVE MARGINS AND BORDER RADIUS self.ui.drop_shadow_layout.setContentsMargins(0, 0, 0, 0) self.ui.drop_shadow_frame.setStyleSheet( "background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:1, stop:0 rgba(42, 44, 111, 255), stop:0.521368 rgba(28, 29, 73, 255)); border-radius: 0px;") self.ui.maximize_btn.setToolTip("Restore") else: GLOBAL_STATE = 0 self.showNormal() self.resize(self.width() + 1, self.height() + 1) self.ui.drop_shadow_layout.setContentsMargins(10, 10, 10, 10) self.ui.drop_shadow_frame.setStyleSheet( "background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:1, stop:0 rgba(42, 44, 111, 255), stop:0.521368 rgba(28, 29, 73, 255)); border-radius: 10px;") self.ui.maximize_btn.setToolTip("Maximize") ## ==> UI DEFINITIONS def uiDefinitions(self): # REMOVE TITLE BAR self.setWindowFlag(QtCore.Qt.FramelessWindowHint) self.setAttribute(QtCore.Qt.WA_TranslucentBackground) # SET DROPSHADOW WINDOW self.shadow = QGraphicsDropShadowEffect(self) self.shadow.setBlurRadius(20) self.shadow.setXOffset(0) self.shadow.setYOffset(0) self.shadow.setColor(QColor(0, 0, 0, 100)) # APPLY DROPSHADOW TO FRAME self.ui.drop_shadow_frame.setGraphicsEffect(self.shadow) # MAXIMIZE / RESTORE self.ui.maximize_btn.clicked.connect(lambda: UIFunctions.maximize_restore(self)) # MINIMIZE self.ui.minimize_btn.clicked.connect(lambda: self.showMinimized()) # CLOSE self.ui.close_btn.clicked.connect(lambda: self.close()) ## ==> CREATE SIZE GRIP TO RESIZE WINDOW self.sizegrip = QSizeGrip(self.ui.frame_grip) self.sizegrip.setStyleSheet( "QSizeGrip { width: 10px; height: 10px; margin: 5px } QSizeGrip:hover { background-color: rgb(50, 42, 94) }") self.sizegrip.setToolTip("Resize Window") ## RETURN STATUS IF WINDOWS IS MAXIMIZE OR RESTAURED def returnStatus(self): return GLOBAL_STATE
class Central(QFrame): '''Initializes, styles, and connects the various classes''' def __init__(self): super().__init__() # Objects self.overallLayout = QVBoxLayout(self) self.contentLayout = QHBoxLayout() self.dropShadow = QGraphicsDropShadowEffect(self) self.boxManager = BoxManager.BoxManager() self.topBar = TopBar.TopBar() self.selectorArea = QFrame() self.selectorLayout = QVBoxLayout(self.selectorArea) self.folderArea = QFrame() self.folderLayout = QHBoxLayout(self.folderArea) self.folderList = FolderList.FolderList() self.folderBar = ScrollBar.ScrollBar(self.folderList) self.canvas = Canvas.Canvas(self.boxManager) self.imageArea = QFrame() self.imageList = ImageList.ImageList() self.imageLayout = QHBoxLayout(self.imageArea) self.imageBar = ScrollBar.ScrollBar(self.imageList) # Styling self.setStyleSheet('Central { background: transparent; }') self.overallLayout.setMargin(20) self.overallLayout.setSpacing(0) self.dropShadow.setOffset(QPointF(0,4)) self.dropShadow.setColor(QColor(0,0,0,100)) self.dropShadow.setBlurRadius(10) self.setGraphicsEffect(self.dropShadow) self.contentLayout.setAlignment(Qt.AlignCenter) self.contentLayout.setMargin(0) self.contentLayout.setSpacing(0) self.selectorLayout.setMargin(0) self.selectorLayout.setSpacing(0) self.folderLayout.setMargin(0) self.folderLayout.setSpacing(0) self.imageLayout.setMargin(0) self.imageLayout.setSpacing(0) self.folderList.setVerticalScrollBar(self.folderBar) self.imageList.setVerticalScrollBar(self.imageBar) self.selectorArea.setMaximumWidth(400) self.selectorArea.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) # Layout self.folderLayout.addWidget(self.folderList) self.folderLayout.addSpacerItem(QSpacerItem(-7, 0)) self.folderLayout.addWidget(self.folderBar) self.imageLayout.addWidget(self.imageList) self.imageLayout.addSpacerItem(QSpacerItem(-7, 0)) self.imageLayout.addWidget(self.imageBar) self.selectorLayout.addWidget(self.folderArea, 15) self.selectorLayout.addWidget(self.imageArea, 85) self.contentLayout.addWidget(self.selectorArea, 30) self.contentLayout.addWidget(self.canvas, 70) self.overallLayout.addLayout(self.contentLayout) self.overallLayout.insertWidget(0, self.topBar) # Connections self.folderList.selectedFolderChanged.connect(self.handleSelectedFolderChanged) self.imageList.selectedImageChanged.connect(self.handleSelectedImageChanged) def handleSelectedFolderChanged(self, folder): self.imageList.populate(folder) self.canvas.changeImage(None) self.canvas.setMessage('Switching Folders - {}'.format(folder.data(role=Qt.DisplayRole))) self.topBar.setSelectedFolder(str(folder.data(role=Qt.UserRole+1))) self.topBar.setSelectedImage('') def handleSelectedImageChanged(self, image): self.canvas.changeImage(image) self.canvas.setMessage('Switching Images - {}'.format(image.data(role=Qt.DisplayRole))) self.topBar.setSelectedImage(str(image.data(role=Qt.DisplayRole)))
class StyleWindow(QWidget): def __init__(self, parent=None): """ 基于QWidget的窗口,作为一些窗口的基类,提供一些基础的属性 background_color_: 背景色, 目前只支持单色 border_width_: 边框宽度, 为0表示没有边框 border_color_: opacity_: 窗口透明度 shadow_on_: 是否开启阴影 shadow_color_: shadow_blur_: 阴影半径 border_radius_: 四个角的弧度 分别设置4个角的radius left_top_border_radius: left_bottom_border_radius_: right_top_border_radius_: right_bottom_border_radius_ """ QWidget.__init__(self, parent) self._border_width = 0 self._border_indicator_width = 5 self._mouse_left_button_pressed = False self._rt_pre_geometry = self.geometry() self._mouse_move_pre_pos = QPoint() self._background_color = Qt.white self._border_color = Qt.transparent self._border_width = 0 self._border_radius = 0 self._opacity = 1.0 self._shadow = None self._main_v_layout_ = None self._cursor_calc_type = MousePosition.CenterPos # border self._left_top_border_radius = 0 self._left_bottom_border_radius = 0 self._right_top_border_radius = 0 self._right_bottom_border_radius = 0 self._enabled_drag = False self.setMouseTracking(True) # self.setAttribute(Qt.WA_TranslucentBackground) self._init_default_layout() def paint_style_widget(self, painter): style_opt = QStyleOption() style_opt.init(self) self.style().drawPrimitive(QStyle.PE_Widget, style_opt, painter) def paintEvent(self, event): """ paintEvent(self, event:PySide2.QtGui.QPaintEvent) """ painter = QPainter(self) self.paint_border_background(painter) def border_width(self): return self._border_width def set_border_width(self, width): self._border_width = parse_pix_width(width) if self._main_v_layout_ is not None: self._main_v_layout_.setContentsMargins(self._border_width, self._border_width, self._border_width, self._border_width) border_width_ = Property(str, fget=border_width, fset=set_border_width) def border_indicator_width(self): return self._border_indicator_width def set_border_indicator_width(self, width): self._border_indicator_width = parse_pix_width(width) border_indicator_width_ = Property(str, fget=border_indicator_width, fset=set_border_indicator_width) def mouseMoveEvent(self, event): """ mouseMoveEvent(self, event:PySide2.QtGui.QMouseEvent) """ if self._enabled_drag: if self.isMaximized() is not True and self.isFullScreen( ) is not True: self._set_cursor_shape( self._calc_cursor_pos(event.pos(), self._calc_cursor_col(event.pos()))) if self._mouse_left_button_pressed and MousePosition.CenterPos != self._cursor_calc_type: self._drag_resize() super(StyleWindow, self).mouseMoveEvent(event) def _set_cursor_shape(self, pos): """setCurSorShape(self, pos:StyleWindow.MousePosition)""" cursor_shape = Qt.ArrowCursor if MousePosition.TopLeftPos == pos or MousePosition.BottomRightPos == pos: cursor_shape = Qt.SizeFDiagCursor elif MousePosition.LeftPos == pos or MousePosition.RightPos == pos: cursor_shape = Qt.SizeHorCursor elif MousePosition.BottomLeftPos == pos or MousePosition.TopRightPos == pos: cursor_shape = Qt.SizeBDiagCursor elif MousePosition.BottomPos == pos or MousePosition.TopPos == pos: cursor_shape = Qt.SizeVerCursor self.setCursor(cursor_shape) def _calc_cursor_col(self, pt): """calCursorCol(self, pt:PySide2.QtCore.QPoint)""" res = 3 x = pt.x() if x < self.border_indicator_width(): res = 1 elif x < self.width() - self.border_indicator_width(): res = 2 return res def _calc_cursor_pos(self, pt, bit): """calCursorCol(self, pt:PySide2.QtCore.QPoint, bit)""" result = bit y = pt.y() if y < self.border_indicator_width(): result += 10 elif y > self.height() - self.border_indicator_width(): result += 30 else: result += 20 return result def mousePressEvent(self, event): """ mousePressEvent(self, event:PySide2.QtGui.QMouseEvent) """ if self._enabled_drag: self.set_calc_mouse_type( self._calc_cursor_pos(event.pos(), self._calc_cursor_col(event.pos()))) if Qt.LeftButton == event.button( ) and MousePosition.CenterPos != self._cursor_calc_type: self._mouse_left_button_pressed = True self._rt_pre_geometry = self.geometry() self._mouse_move_pre_pos = event.globalPos() super(StyleWindow, self).mousePressEvent(event) def mouseReleaseEvent(self, event): """ mousePressEvent(self, event:PySide2.QtGui.QMouseEvent) """ self._mouse_left_button_pressed = False QApplication.restoreOverrideCursor() super(StyleWindow, self).mouseReleaseEvent(event) def _drag_resize(self): mouse_cur_pos = QCursor.pos() move_pos = mouse_cur_pos - self._mouse_move_pre_pos after_resize_geometry = self._rt_pre_geometry if MousePosition.TopLeftPos == self._cursor_calc_type: after_resize_geometry.setTopLeft(self._rt_pre_geometry.topLeft() + move_pos) elif MousePosition.LeftPos == self._cursor_calc_type: after_resize_geometry.setLeft(self._rt_pre_geometry.left() + move_pos.x()) elif MousePosition.BottomLeftPos == self._cursor_calc_type: after_resize_geometry.setBottomLeft( self._rt_pre_geometry.bottomLeft() + move_pos) elif MousePosition.BottomPos == self._cursor_calc_type: after_resize_geometry.setBottom(self._rt_pre_geometry.bottom() + move_pos.y()) elif MousePosition.BottomRightPos == self._cursor_calc_type: after_resize_geometry.setBottomRight( self._rt_pre_geometry.bottomRight() + move_pos) elif MousePosition.RightPos == self._cursor_calc_type: after_resize_geometry.setRight(self._rt_pre_geometry.right() + move_pos.x()) elif MousePosition.TopRightPos == self._cursor_calc_type: after_resize_geometry.setTopRight( self._rt_pre_geometry.topRight() + move_pos) elif MousePosition.TopPos == self._cursor_calc_type: after_resize_geometry.setTop(self._rt_pre_geometry.top() + move_pos.y()) self.setGeometry(after_resize_geometry) self._mouse_move_pre_pos = mouse_cur_pos self._rt_pre_geometry = after_resize_geometry def background_color(self): return self._background_color def set_background_color(self, clr): self._background_color = clr background_color_ = Property(QColor, fget=background_color, fset=set_background_color) def border_color(self): return self._border_color def set_border_color(self, clr): self._border_color = clr border_color_ = Property(QColor, fget=border_color, fset=set_border_color) def border_radius(self): return self._border_radius def set_border_radius(self, radius): self._border_radius = parse_pix_width(radius) border_radius_ = Property(str, fget=border_radius, fset=set_border_radius) def paint_border_background(self, painter): """paint_border_background(self, painter:PySide2.QtGui.QPainter)""" painter.save() pen = QPen() pen.setColor(self.border_color()) pen.setWidth(self.border_width()) painter.setPen(pen) brush = QBrush(self.background_color()) painter.setBrush(brush) painter.setOpacity(self.opacity()) # painter.setRenderHint(QPainter.Antialiasing) rc = self.rect() paint_path = QPainterPath() # adjust shadow if self._shadow is not None: rc.adjust(self.shadow_blur(), self.shadow_blur(), -self.shadow_blur(), -self.shadow_blur()) # self._calc_background_path(rc, paint_path) if self._calc_background_path(rc, paint_path): painter.setRenderHint(QPainter.Antialiasing) if self.border_width() > 0: painter.drawPath(paint_path) else: painter.fillPath(paint_path, brush) painter.restore() def set_opacity(self, opacity): self._opacity = opacity def opacity(self) -> float: return self._opacity opacity_ = Property(float, fget=opacity, fset=set_opacity) def resizeEvent(self, event): super(StyleWindow, self).resizeEvent(event) # self.setGeometry(5, 5, self.width() - 5, self.height() - 5) def set_shadow(self, on): if on: self._shadow = QGraphicsDropShadowEffect(self) self._shadow.setOffset(0.0, 0.0) self._shadow.color() self.setGraphicsEffect(self._shadow) self.setGeometry(5, 5, self.width() - 5, self.height() - 5) def shadow(self): return self._shadow shadow_on_ = Property(bool, fget=shadow, fset=set_shadow) def shadow_color(self): if self._shadow is not None: return self._shadow.color() def set_shadow_color(self, color): if self._shadow is not None: self._shadow.setColor(color) shadow_color_ = Property(QColor, fget=shadow_color, fset=set_shadow_color) def shadow_blur(self): if self._shadow is not None: return self._shadow.blurRadius() return 0 def set_shadow_blur(self, pix_blur): if self._shadow is not None: blur = parse_pix_width(pix_blur) self._shadow.setBlurRadius(blur) self.set_border_indicator_width(blur + self.border_indicator_width()) if self._main_v_layout_ is not None: self._main_v_layout_.setContentsMargins( blur + self.border_width(), blur + self.border_width(), blur - 1 + self.border_width(), blur + self.border_width()) shadow_blur_ = Property(str, fget=shadow_blur, fset=set_shadow_blur) def _init_default_layout(self): """setting a default QVBoxLayout""" self._main_v_layout_ = QVBoxLayout(self) self._main_v_layout_.setContentsMargins(0, 0, 0, 0) self.setLayout(self._main_v_layout_) def set_left_top_border_radius(self, radius): self._left_top_border_radius = parse_pix_width(radius) def left_top_border_radius(self): if self._left_top_border_radius: return self._left_top_border_radius return self.border_radius() left_top_border_radius_ = Property(str, fget=left_top_border_radius, fset=set_left_top_border_radius) def set_left_bottom_border_radius(self, radius): self._left_bottom_border_radius = parse_pix_width(radius) def left_bottom_border_radius(self): if self._left_bottom_border_radius: return self._left_bottom_border_radius return self.border_radius() left_bottom_border_radius_ = Property(str, fget=left_bottom_border_radius, fset=set_left_bottom_border_radius) def set_right_top_border_radius(self, radius): self._right_top_border_radius = parse_pix_width(radius) def right_top_border_radius(self): if self._right_top_border_radius: return self._right_top_border_radius return self.border_radius() right_top_border_radius_ = Property(str, fget=right_top_border_radius, fset=set_right_top_border_radius) def set_right_bottom_border_radius(self, radius): self._right_bottom_border_radius = parse_pix_width(radius) def right_bottom_border_radius(self): if self._right_bottom_border_radius: return self._right_bottom_border_radius return self.border_radius() right_bottom_border_radius_ = Property(str, fget=right_bottom_border_radius, fset=set_right_bottom_border_radius) def _calc_background_path(self, rc, painter_path): render_hint = False if self.border_radius(): painter_path.addRoundedRect(rc, self.border_radius(), self.border_radius()) render_hint = True else: if self.left_top_border_radius() or self.left_bottom_border_radius() \ or self.right_top_border_radius() or self.right_bottom_border_radius(): # add radius render_hint = True radius_angle = 90.0 rotate_angle = 90.0 painter_path.moveTo(rc.left(), rc.top()) painter_path.arcTo(rc.left(), rc.top(), self.left_top_border_radius(), self.left_top_border_radius(), rotate_angle, radius_angle) painter_path.lineTo( rc.left(), rc.bottom() - self.left_bottom_border_radius() / 2) painter_path.arcTo(rc.left(), rc.bottom() - self.left_bottom_border_radius(), self.left_bottom_border_radius(), self.left_bottom_border_radius(), rotate_angle * 2, radius_angle) painter_path.lineTo( rc.right() - self.right_bottom_border_radius() / 2, rc.bottom()) painter_path.arcTo(rc.right() - self.right_bottom_border_radius(), rc.bottom() - self.right_bottom_border_radius(), self.right_bottom_border_radius(), self.right_bottom_border_radius(), rotate_angle * 3, radius_angle) painter_path.lineTo(rc.right(), rc.top() - self.right_top_border_radius() / 2) painter_path.arcTo(rc.right() - self.right_top_border_radius(), rc.top(), self.right_top_border_radius(), self.right_top_border_radius(), rotate_angle * 4 % rotate_angle, radius_angle) painter_path.lineTo(rc.left() + self.left_top_border_radius() / 2, rc.top()) return render_hint def set_layout(self, layout): if self._main_v_layout_ is not None: self._main_v_layout_.addLayout(layout) @property def mouse_drag_enabled(self): return self._enabled_drag @mouse_drag_enabled.setter def mouse_drag_enabled(self, drag): self._enabled_drag = drag def _mouse_drag_is_center_type(self): if self._cursor_calc_type == MousePosition.CenterPos: return True return False def set_calc_mouse_type(self, mouse_type: MousePosition): self._cursor_calc_type = mouse_type