def paste(self): data = {} try: data = json.loads(QGuiApplication.clipboard().text()) except Exception as e: return self.clear_selection() # calculate offset positions = [] for d in data['drawings']: positions.append({'x': d['pos x'], 'y': d['pos y']}) for n in data['nodes']: positions.append({'x': n['position x'], 'y': n['position y']}) offset_for_middle_pos = QPointF(0, 0) if len(positions) > 0: rect = QRectF(positions[0]['x'], positions[0]['y'], 0, 0) for p in positions: x = p['x'] y = p['y'] if x < rect.left(): rect.setLeft(x) if x > rect.right(): rect.setRight(x) if y < rect.top(): rect.setTop(y) if y > rect.bottom(): rect.setBottom(y) offset_for_middle_pos = self.last_mouse_move_pos - rect.center() self.undo_stack.push(Paste_Command(self, data, offset_for_middle_pos))
def get_points_rect(self): rect = QRectF() left = 1 right = -1 up = 1 down = -1 for p in self.points: if p.x() < left: left = p.x() if p.x() > right: right = p.x() if p.y() < up: up = p.y() if p.y() > down: down = p.y() rect.setLeft(left) rect.setRight(right) rect.setTop(up) rect.setBottom(down) self.width = rect.width() self.height = rect.height() # rect.setLeft(-self.width/2) # rect.setRight(self.width/2) # rect.setTop(-self.height/2) # rect.setBottom(self.height/2) return rect
def boundingRect(self): anchor = self.mapFromParent(self._chart.mapToPosition(self._anchor)) rect = QRectF() rect.setLeft(min(self._rect.left(), anchor.x())) rect.setRight(max(self._rect.right(), anchor.x())) rect.setTop(min(self._rect.top(), anchor.y())) rect.setBottom(max(self._rect.bottom(), anchor.y())) return rect
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
def get_title_rect(self): title_rect_offset_factor = 0.56 header_rect = self.get_header_rect() rect = QRectF() rect.setTop(header_rect.top() + (header_rect.height() / 2) * (1 - title_rect_offset_factor)) rect.setLeft(header_rect.left() + 10) rect.setHeight(header_rect.height() * title_rect_offset_factor) w = header_rect.width() * title_rect_offset_factor title_width = self.display_name_FM.width( get_longest_line(self.parent_node.title)) rect.setWidth(w if w > title_width else title_width) return rect
def get_whole_scene_img(self): self.hide_proxies() img = QImage(self.sceneRect().width() / self.total_scale_div, self.sceneRect().height() / self.total_scale_div, QImage.Format_RGB32) img.fill(Qt.transparent) painter = QPainter(img) painter.setRenderHint(QPainter.Antialiasing) rect = QRectF() rect.setLeft(-self.viewport().pos().x()) rect.setTop(-self.viewport().pos().y()) rect.setWidth(img.rect().width()) rect.setHeight(img.rect().height()) # rect is right... but it only renders from the viewport's point down-and rightwards, not from topleft (0,0) ... self.render(painter, rect, rect.toRect()) self.show_proxies() return img
class Apartment: def __init__(self, coppelia_, n_rooms): self.coppelia = coppelia_ self.num_rooms = n_rooms self.max_rooms_per_side = math.ceil(self.num_rooms / 2) self.initial_corridor_width = -1 self.initial_corridor_height = -1 self.initial_corridor = QRectF() self.fixed_height = random.uniform(4, 6) # Almacena los indices de las habitaciones que tendrán a su izquierda un pasillo self.dict_corridors_index_per_side = {'bottom': [], 'top': []} self.dict_rooms_per_side = {'bottom': [], 'top': []} self.dict_rooms_and_corridors_per_side = {'bottom': [], 'top': []} # Lista final una vez hechas todas las transformaciones self.total_rooms_and_corridors = [] self.create_initial_corridor() self.select_side_corridors() self.get_random_rooms() self.adjust_rooms() # to avoid narrow corridors self.add_doors() self.center_apartment() self.add_floor_per_room() self.add_walls() def create_initial_corridor(self): self.initial_corridor_height = random.uniform(1.5, 3) self.initial_corridor_width = random.uniform(self.num_rooms * 4 / 2, self.num_rooms * 8 / 2) self.initial_corridor = QRectF( 0, 0, self.initial_corridor_width, -self.initial_corridor_height ) # - height para que la parte de arriba sea el top self.initial_corridor.translate(-self.initial_corridor.center()) def select_side_corridors(self): # -1 sin pasillo, 0 antes de la primera habitacion, 1 antes de la segunda corridor_position = np.arange(-1, self.max_rooms_per_side) possibles_corridors_per_side = round(self.max_rooms_per_side / 2) if possibles_corridors_per_side == 0: possibles_corridors_per_side = 1 self.dict_corridors_index_per_side['top'] = random.sample( list(corridor_position), k=possibles_corridors_per_side) self.dict_corridors_index_per_side['bottom'] = random.sample( list(corridor_position), k=possibles_corridors_per_side) while -1 in self.dict_corridors_index_per_side['top']: self.dict_corridors_index_per_side['top'].remove(-1) while -1 in self.dict_corridors_index_per_side['bottom']: self.dict_corridors_index_per_side['bottom'].remove(-1) print('posicion pasillo parte superior', self.dict_corridors_index_per_side['top']) print('posicion pasillo parte inferior', self.dict_corridors_index_per_side['bottom']) def get_random_rooms(self): dict_opposite_side = {'bottom': 'top', 'top': 'bottom'} for i in range(0, self.num_rooms): random_side = random.choice(['top', 'bottom']) if len(self.dict_rooms_per_side[random_side] ) >= self.max_rooms_per_side: random_side = dict_opposite_side[random_side] # El indice de mi habitacion está en la lista de pasillos por indice luego tengo que añadir un pasillo a # su izquierda if len(self.dict_rooms_per_side[random_side] ) in self.dict_corridors_index_per_side[random_side]: self.add_corridor(random_side, self.initial_corridor_height, self.fixed_height) if len(self.dict_rooms_and_corridors_per_side[random_side]) == 0: if random_side == 'bottom': initial_point = self.initial_corridor.bottomLeft() else: initial_point = self.initial_corridor.topLeft() + QPointF( 0, self.fixed_height) else: initial_point = self.dict_rooms_and_corridors_per_side[ random_side][-1].room_qrect.topRight() room = Room(type='genericRoom', p=initial_point, w=random.uniform(4, 8), h=-self.fixed_height) self.dict_rooms_and_corridors_per_side[random_side].append(room) self.dict_rooms_per_side[random_side].append(room) for room_location in ['top', 'bottom']: if len(self.dict_rooms_per_side[room_location] ) in self.dict_corridors_index_per_side[room_location]: try: if self.dict_rooms_and_corridors_per_side[room_location][ -1].type != 'corridor': self.add_corridor(random_side, self.initial_corridor_height, self.fixed_height) except: print('there isnt rooms in this side of the corridor') def add_corridor(self, side, corridor_width, corridor_height): if len(self.dict_rooms_and_corridors_per_side[side]) == 0: if side == 'bottom': initial_point = self.initial_corridor.bottomLeft() else: initial_point = self.initial_corridor.topLeft() + QPointF( 0, self.fixed_height) else: initial_point = self.dict_rooms_and_corridors_per_side[side][ -1].room_qrect.topRight() corridor = Room(type='corridor', p=initial_point, w=corridor_width, h=-corridor_height) self.dict_rooms_and_corridors_per_side[side].append(corridor) def adjust_rooms(self): if self.num_rooms == 1: return dict_side_width = {'bottom': 0., 'right': 0., 'top': 0., 'left': 0.} for side, rooms in self.dict_rooms_per_side.items(): print(f' side {side} has {len(rooms)} rooms ') for room in rooms: r = room.room_qrect dict_side_width[side] += r.width() diff = abs(dict_side_width['top'] - dict_side_width['bottom']) dict_opposite_side = { 'bottom': 'top', 'right': 'left', 'top': 'bottom', 'left': 'right' } if dict_side_width['top'] > dict_side_width['bottom']: print('top side is longer') side_to_modify = 'bottom' else: print('bottom side is longer') side_to_modify = 'top' print(f'--- Modifying {side_to_modify} room ---') room_to_modify = self.dict_rooms_and_corridors_per_side[ side_to_modify][-1] opposite_room = self.dict_rooms_and_corridors_per_side[ dict_opposite_side[side_to_modify]][-1] my_side_right = room_to_modify.room_qrect.topRight() opposite_side_right = opposite_room.room_qrect.topRight() if room_to_modify.type == 'corridor': print(f' Room of type {room_to_modify.type} ') room_to_modify.room_qrect.setTopRight( QPointF(opposite_side_right.x(), my_side_right.y())) self.dict_rooms_and_corridors_per_side[side_to_modify][ -1] = room_to_modify self.dict_rooms_and_corridors_per_side[side_to_modify][ -1].update_room_dimensions() else: if diff < self.initial_corridor_height: print('widening room') num_corridors_to_add = 0 else: print('widening room and creating corridor') num_corridors_to_add = 1 print( f' Room of type {room_to_modify.type} -- adding {num_corridors_to_add} corridors' ) room_to_modify.room_qrect.setTopRight( QPointF( opposite_side_right.x() - num_corridors_to_add * self.initial_corridor_height, my_side_right.y())) self.dict_rooms_and_corridors_per_side[side_to_modify][ -1] = room_to_modify self.dict_rooms_and_corridors_per_side[side_to_modify][ -1].update_room_dimensions() if num_corridors_to_add > 0: self.add_corridor(side=side_to_modify, corridor_width=num_corridors_to_add * self.initial_corridor_height, corridor_height=self.fixed_height) def add_doors(self): opposite = {'bottom': 'top', 'top': 'bottom'} for current_side, rooms in self.dict_rooms_per_side.items(): for i, room in enumerate(rooms): possibles_door_locations = [opposite[current_side]] if i in self.dict_corridors_index_per_side[ current_side]: # Pasillo a la izquierda possibles_door_locations.append('left') if i + 1 in self.dict_corridors_index_per_side[current_side]: possibles_door_locations.append('right') door_location = random.choice(possibles_door_locations) room.add_door(door_location) def center_apartment(self): union_polygon = QPolygonF() for list in self.dict_rooms_and_corridors_per_side.values(): for room in list: union_polygon = union_polygon.united( room.room_qpolygon) # Para obtener el bounding box self.total_rooms_and_corridors.append(room) self.initial_corridor.setLeft(union_polygon.boundingRect().left()) self.initial_corridor.setRight(union_polygon.boundingRect().right()) self.total_rooms_and_corridors.append( Room(type='corridor', p=self.initial_corridor.topLeft(), w=self.initial_corridor.width(), h=self.initial_corridor.height())) union_polygon = union_polygon.united(self.initial_corridor) initial_center = union_polygon.boundingRect().center() union_polygon.translate(-initial_center) self.apartment_boundingRect = union_polygon.boundingRect() # Desplazo habitaciones y pasillos al centro for i, room in enumerate(self.total_rooms_and_corridors): room.room_qpolygon.translate( -initial_center ) # Desplazo los poligonos para que la habitación esté centrada room.room_qrect.translate(-initial_center) def add_walls(self): for i, room in enumerate(self.total_rooms_and_corridors): walls = [] if room.type == 'corridor': continue polygon = room.room_qpolygon prev_point = polygon[0] for i, curr_point in enumerate(polygon): if i == 0: continue walls.append(([prev_point.x(), prev_point.y(), .425], [curr_point.x(), curr_point.y(), .425])) prev_point = curr_point room.walls = walls wall_thread = WallCreator(data, walls) wall_thread.start() walls = [] polygon_br = QPolygonF(self.apartment_boundingRect, closed=True) prev_point_br = polygon_br[0] for i, curr_point_br in enumerate(polygon_br): if i == 0: continue walls.append(([prev_point_br.x(), prev_point_br.y(), .4], [curr_point_br.x(), curr_point_br.y(), .4])) prev_point_br = curr_point_br wall_thread = WallCreator(data, walls) wall_thread.start() def add_floor(self): # un suelo conjunto para el apartamento fscale_x = self.apartment_boundingRect.width() / 5 + 0.5 fscale_y = self.apartment_boundingRect.height() / 5 + 0.5 # Create and scale a floor r = self.coppelia.create_model( 'models/infrastructure/floors/5mX5m wooden floor.ttm', 0, 0, 0, 0) self.coppelia.scale_object(r, fscale_x, fscale_y, 1) for handle in self.coppelia.get_objects_children(r): self.coppelia.scale_object(handle, fscale_x, fscale_y, 1) def add_floor_per_room(self): for room in self.total_rooms_and_corridors: room_boundingRect = room.room_qpolygon.boundingRect() room_center = room_boundingRect.center() fscale_x = room_boundingRect.width() / 5 fscale_y = room_boundingRect.height() / 5 if room.type == 'corridor': floor = self.coppelia.create_model( 'models/infrastructure/floors/5mX5m wooden floor.ttm', room_center.x(), room_center.y(), 0, 0) else: floor = self.coppelia.create_model( 'models/infrastructure/floors/5mX5m concrete floor.ttm', room_center.x(), room_center.y(), 0, 0) self.coppelia.scale_object(floor, fscale_x, fscale_y, 1)
class QSlideNavigationBar(QWidget): class ItemLineStyle(Enum): ItemNone = 1 ItemTop = 2 ItemRight = 3 ItemBottom = 4 ItemLeft = 5 ItemRect = 6 itemClicked = Signal(int, str) def __init__(self): super(QSlideNavigationBar, self).__init__() # -------成员变量定义------------# # ==========属性=========# self._m_bar_start_color = QColor('#511235') # type: QColor # 导航栏起始颜色 self._m_bar_end_color = QColor('#150507') # type: QColor # 导航栏结束颜色 self._m_bar_radius = 0 # type: int # 导航栏四个角的圆弧半径 self._m_item_start_color = QColor(255, 255, 255, 50) # type: QColor # item 的起始颜色 self._m_item_end_color = QColor("black") # type: QColor # item 的结束颜色 self._m_current_hover_index = -1 # type: int # 当前光标所在的item的index self._m_item_hover_start_color = QColor(255, 0, 0, 25) # type: QColor # 光标所在的item 的起始颜色 self._m_item_hover_end_color = QColor(255, 0, 255, 25) # type: QColor self._m_item_text_color = QColor("red") # type: QColor # item 的文字颜色 self._m_item_line_color = QColor("red") # type: QColor # item 的线的颜色 self._m_item_line_width = 5 # type: int # 线的宽度 self._m_item_line_style = self.ItemLineStyle.ItemNone # type: QSlideNavigationBar.ItemLineStyle # 线的样式类型 self._m_item_font = QFont('宋体') # type: QFont # 字体家族 self._m_item_font_size = 16 # type: int # 字体大小 self._m_item_radius = 0 # type: int # item 的圆角半径 self._m_space = 40 # type: int # 间距大小, item 背景大小 self._m_orientation = Qt.Horizontal # type: Qt.Orientation # 导航栏的方向:横向,纵向 self._m_enable_key_move = True # type: bool # 是否可以使用按键切换item self._m_fixed = False # type: bool # 大小固定 self._m_slide_velocity = 10 # type: int # 滑动速度 self._m_shake_velocity = 10 # type: int # 晃动速度 # ===========内部变量=========# self._m_item_maps = {} # type: map(int, list(str, QRectF)) # 保存的item列表 self._m_total_text_width = 0 # type: int # 总的文字的宽度 self._m_total_text_height = 0 # type: int # 总的文字的高度 self._m_current_index = 0 # type: int # 当前选中的item 的索引 self._m_start_rect = QRectF() # type:QRectF #起始矩形 self._m_stop_rect = QRectF() # type: QRectF # 结束矩形 self._m_slide_timer = QTimer(self) # type: QTimer # 滑动的定时器 self._m_shake_timer = QTimer(self) # type: QTimer # 晃动的定时器 self._m_forward = False # type: bool # 前进 # ----------执行初始化动作-------------# self.setAttribute(Qt.WA_TranslucentBackground) self._m_slide_timer.setInterval(self._m_slide_velocity) self._m_slide_timer.timeout.connect(self._on_do_slide) self._m_shake_timer.setInterval(self._m_shake_velocity) self._m_shake_timer.timeout.connect(self._on_do_shake) self.setFocusPolicy(Qt.ClickFocus) self.setMouseTracking(True) # ---------以下是 API 接口----------# def add_item(self, item_str: str): """ 向导航栏中添加项目(代表一个选项卡),添加之前会查重 :param item_str:项目名称(显示出来的文字) :return:None """ if not item_str: return for key, value in self._m_item_maps.items(): if value[0] == item_str: return # 如果存在同名item,则返回 f = QFont() f.setPointSize(self._m_item_font_size) fm = QFontMetrics(f) text_width = fm.width(item_str) text_height = fm.height() item_count = len(self._m_item_maps) if item_count > 0: if self._m_orientation == Qt.Horizontal: top_left = QPointF(self._m_total_text_width, 0) self._m_total_text_width += text_width + self._m_space bottom_right = QPointF(self._m_total_text_width, self._m_total_text_height) else: top_left = QPointF(0, self._m_total_text_height) self._m_total_text_height += text_height + self._m_space bottom_right = QPointF(self._m_total_text_width, self._m_total_text_height) self._m_item_maps[item_count] = [item_str, QRectF(top_left, bottom_right)] else: if self._m_orientation == Qt.Horizontal: # 水平方向,水平各占1个space, 竖直占1个space self._m_total_text_width = text_width + self._m_space self._m_total_text_height = text_height + self._m_space else: # 竖直方向, 水平各占2个space, 竖直占一个space self._m_total_text_width = text_width + 2 * self._m_space self._m_total_text_height = text_height + self._m_space top_left = QPointF(0.0, 0.0) bottom_right = QPointF(self._m_total_text_width, self._m_total_text_height) self._m_item_maps[item_count] = [item_str, QRectF(top_left, bottom_right)] self.setMinimumSize(self._m_total_text_width, self._m_total_text_height) if self._m_fixed: if self._m_orientation == Qt.Horizontal: self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) # 固定高度 else: self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) # 固定宽度 if len(self._m_item_maps): self._m_start_rect = QRectF(self._m_item_maps[0][1]) self.update() def set_items(self, items_list: list): """ 一次性设置一组项目, 会清空之前设置的项目 :param items_list: 项目名称列表 :return: None """ pass def get_items(self): pass def set_bar_start_color(self, color: QColor): """ 设置导航栏背景的起始颜色(渐变色的起始) :param color:QColor类型 :return: None """ if color != self._m_bar_start_color: self._m_bar_start_color = color self.update() def set_bar_end_color(self, color: QColor): """ 设置导航栏背景的结束颜色(渐变色的结束) :param color: QColor 类型 :return: None """ if color != self._m_bar_end_color: self._m_bar_end_color = color self.update() def set_item_start_color(self, color: QColor): """ 设置项目的背景色的起始颜色(渐变色的起始) :param color: QColor 类型 :return: None """ if color != self._m_item_start_color: self._m_item_start_color = color self.update() def set_item_end_color(self, color: QColor): """ 设置项目的背景色的结束颜色(渐变色的结束) :param color: QColor 类型 :return: None """ if color != self._m_item_end_color: self._m_item_end_color = color self.update() def set_item_text_color(self, color: QColor) -> None: """ 设置 item 的文字颜色 :param color: QColor 类型 :return: None """ if color != self._m_item_text_color: self._m_item_text_color = color self.update() def set_item_line_color(self, color: QColor) -> None: """ 设置 item 的线的颜色 :param color: QColor 类型 :return: None """ if color != self._m_item_line_color: self._m_item_line_color = color self.update() def set_bar_radius(self, radius: int): """ 设置导航栏四个角的圆弧半径 :param radius: int 类型 :return: None """ if radius >= 0 and radius != self._m_bar_radius: self._m_bar_radius = radius self.update() def set_item_radius(self, radius: int): """ 设置项目的四个角的圆弧半径 :param radius: int 类型 :return: None """ if radius >= 0 and radius != self._m_item_radius: self._m_item_radius = radius self.update() def set_space(self, space: int): """ 设置 item 所占的空间大小 :param space: int 类型 :return: None """ if space >= 0 and space != self._m_space: self._m_space = space self.update() def set_item_line_width(self, width: int): """ 设置项目周围的线宽度 :param width: int 类型 :return: None """ if width >= 0 and width != self._m_item_line_width: self._m_item_line_width = width self.update() def set_item_line_style(self, style: ItemLineStyle): """ 设置项目周围的线的类型:不显示,上方,下方,左方, 右方, 矩形 :param style: 枚举类型 :return: None """ if style != self._m_item_line_style: self._m_item_line_style = style self.update() def set_orientation(self, orientation: Qt.Orientation): """ 设置导航栏是横向还是纵向 :param orientation: Qt.Orientation 类型 :return: None """ if orientation != self._m_orientation: self._m_orientation = orientation self.update() def set_fixed(self, fixed: bool): """ 设置导航栏尺寸固定,不随窗口大小进行缩放 :param fixed: bool 类型 :return: None """ if fixed != self._m_fixed: self._m_fixed = fixed self.update() def get_current_item_index(self): """ 返回当前选中项目的索引号 :return: int类型,索引号 """ return self._m_current_index # -----------以下是槽函数--------------# def on_set_enable_key_move(self, enable: bool): """ 设置可以使用按键来切换导航项目 :param enable: bool 类型 :return: None """ if enable != self._m_enable_key_move: self._m_enable_key_move = enable def on_move_to_first_item(self): """ 切换到第一个项目 :return: None """ self.on_move_to_index(0) def on_move_to_last_item(self): """ 切换到最后一个项目 :return: None """ self.on_move_to_index(len(self._m_item_maps) - 1) def on_move_to_previous_item(self): """ 切换到上一个项目 :return: None """ if self._m_current_index == 0: return self.on_move_to_index(self._m_current_index - 1) def on_move_to_next_item(self): """ 切换到下一个项目 :return: None """ if self._m_current_index == len(self._m_item_maps) - 1: return self.on_move_to_index(self._m_current_index + 1) def on_move_to_index(self, index: int): """ 切换到指定索引的项目 :param index: int, 项目的索引号 :return: None """ if (index >= 0) and (index < len(self._m_item_maps)) and (index != self._m_current_index): self.itemClicked.emit(index, self._m_item_maps[index][0]) if self._m_current_index == -1: self._m_start_rect = QRectF(self._m_item_maps[index][1]) self._m_forward = index > self._m_current_index self._m_current_index = index self._m_stop_rect = QRectF(self._m_item_maps[index][1]) self._m_slide_timer.start() def on_move_to_name(self, name: str): """ 切换到指定名称的项目 :param name: 项目的名称,str类型 :return: None """ for key, value in self._m_item_maps.items(): if value[0] == name: target_index = key if target_index == self._m_current_index: return self.on_move_to_index(target_index) break def on_move_to_position(self, point: QPointF): """ 切换到指定位置的项目 :param point: 位置坐标,QPointF类型 :return: None """ for key, value in self._m_item_maps.items(): if value[1].contains(point): target_index = key if target_index == self._m_current_index: return self.on_move_to_index(target_index) break def on_set_current_item_index(self, index: int): """ 将当前选中项目切换到指定索引的项目 :param index: 索引号, int :return: NOne """ self.on_move_to_index(index) # ------------以下是私有槽函数---------# def _on_do_slide(self): """ 完成滑动动作 :return: None """ if self._m_space <= 0 or self._m_start_rect == self._m_stop_rect: self.update() self._m_slide_timer.stop() return if self._m_orientation == Qt.Horizontal: dx = self._m_space / 2.0 dy = 0 else: dx = 0 dy = self._m_space / 2.0 if self._m_forward: self._m_start_rect.adjust(dx, dy, dx, dy) if ((self._m_orientation == Qt.Horizontal) and (self._m_start_rect.topLeft().x() >= self._m_stop_rect.topLeft().x())) or \ ((self._m_orientation == Qt.Vertical) and (self._m_start_rect.topLeft().y() >= self._m_stop_rect.topLeft().y())): self._m_slide_timer.stop() if self._m_start_rect != self._m_stop_rect: self._m_shake_timer.start() else: self._m_start_rect.adjust(-dx, -dy, -dx, -dy) if ((self._m_orientation == Qt.Horizontal) and (self._m_start_rect.topLeft().x() <= self._m_stop_rect.topLeft().x())) or \ ((self._m_orientation == Qt.Vertical) and (self._m_start_rect.topLeft().y() <= self._m_stop_rect.topLeft().y())): self._m_slide_timer.stop() if self._m_start_rect != self._m_stop_rect: self._m_shake_timer.start() self.update() def _on_do_shake(self): """ 完成晃动动作 :return: None """ delta = 2.0 dx1 = dx2 = dy1 = dy2 = 0.0 if self._m_start_rect.topLeft().x() > self._m_stop_rect.topLeft().x(): dx1 = -delta elif self._m_start_rect.topLeft().x() < self._m_stop_rect.topLeft().x(): dx1 = delta if self._m_start_rect.topLeft().y() > self._m_stop_rect.topLeft().y(): dy1 = -delta elif self._m_start_rect.topLeft().y() < self._m_stop_rect.topLeft().y(): dy1 = delta if self._m_start_rect.bottomRight().x() > self._m_stop_rect.bottomRight().x(): dx2 = -delta elif self._m_start_rect.bottomRight().x() < self._m_stop_rect.bottomRight().x(): dx2 = delta if self._m_start_rect.bottomRight().y() > self._m_stop_rect.bottomRight().y(): dy2 = -delta elif self._m_start_rect.bottomRight().y() < self._m_stop_rect.bottomRight().y(): dy2 = delta self._m_start_rect.adjust(dx1, dy1, dx2, dy2) if abs(self._m_start_rect.topLeft().x() - self._m_stop_rect.topLeft().x()) <= delta: self._m_start_rect.setLeft(self._m_stop_rect.topLeft().x()) if abs(self._m_start_rect.topLeft().y() - self._m_stop_rect.topLeft().y()) <= delta: self._m_start_rect.setTop(self._m_stop_rect.topLeft().y()) if abs(self._m_start_rect.bottomRight().x() - self._m_stop_rect.bottomRight().x()) <= delta: self._m_start_rect.setRight(self._m_stop_rect.bottomRight().x()) if abs(self._m_start_rect.bottomRight().y() - self._m_stop_rect.bottomRight().y()) <= delta: self._m_start_rect.setBottom(self._m_stop_rect.bottomRight().y()) if self._m_start_rect == self._m_stop_rect: self._m_shake_timer.stop() self.update() # -----------以下是事件响应函数的重载函数-----------# def paintEvent(self, a0: QtGui.QPaintEvent) -> None: """ 重载paintEvent函数 :param a0: :return: """ painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) self._draw_bar_background(painter) self._draw_item_background(painter) self._draw_item_line(painter) self._draw_text(painter) def resizeEvent(self, a0: QtGui.QResizeEvent) -> None: """ 重载resizeEvent函数 :param a0: :return: """ self._adjust_item_size() def mousePressEvent(self, a0: QtGui.QMouseEvent) -> None: """ 重载mousePressEvent函数 :param a0: 事件 :return:None """ self.on_move_to_position(a0.pos()) def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None: """ 重载mouseMoveEvent函数 :param a0: 事件 :return: None """ is_on_item = False for key, value in self._m_item_maps.items(): if value[1].contains(a0.pos()): self._m_current_hover_index = key is_on_item = True break if not is_on_item: self._m_current_hover_index = -1 self.update() def keyPressEvent(self, a0: QtGui.QKeyEvent) -> None: """ 重载keyPressEvent函数 :param a0: :return: """ if not self._m_enable_key_move: # self.keyPressEvent(a0) return if a0.key() == Qt.Key_Home: self.on_move_to_first_item() elif a0.key() == Qt.Key_End: self.on_move_to_last_item() elif a0.key() == Qt.Key_Up or a0.key() == Qt.Key_Left: self.on_move_to_previous_item() elif a0.key() == Qt.Key_Down or a0.key() == Qt.Key_Right: self.on_move_to_next_item() else: # self.keyPressEvent(a0) return # -----------------以下是私有成员函数---------------------# def _draw_bar_background(self, p: QPainter): """ 绘制导航栏的背景 :param p: 画刷 :return: None """ p.save() p.setPen(Qt.NoPen) lgt = QLinearGradient(QPointF(0, 0), QPointF(0, self.height())) lgt.setColorAt(0.0, self._m_bar_start_color) lgt.setColorAt(1.0, self._m_bar_end_color) p.setBrush(lgt) p.drawRoundedRect(self.rect(), self._m_bar_radius, self._m_bar_radius) p.restore() def _draw_item_background(self, p: QPainter): """ 绘制项目的背景 :param p: 画刷 :return: None """ if self._m_start_rect.isNull(): return p.save() lgt = QLinearGradient(self._m_start_rect.topLeft(), self._m_start_rect.bottomRight()) lgt.setColorAt(0.0, self._m_item_start_color) lgt.setColorAt(1.0, self._m_item_end_color) p.setPen(Qt.NoPen) p.setBrush(lgt) p.drawRoundedRect(self._m_start_rect, self._m_item_radius, self._m_item_radius) # 绘制 hover 状态下的item if self._m_current_hover_index != -1: hover_rect = QRectF(self._m_item_maps[self._m_current_hover_index][1]) lgt = QLinearGradient(hover_rect.topLeft(), hover_rect.bottomRight()) lgt.setColorAt(0.0, self._m_item_hover_start_color) lgt.setColorAt(1.0, self._m_item_hover_end_color) p.setPen(Qt.NoPen) p.setBrush(lgt) p.drawRoundedRect(hover_rect, self._m_item_radius, self._m_item_radius) p.restore() def _draw_item_line(self, p: QPainter) -> None: """ 绘制项目周边的线条 :param p: 画刷 :return: None """ if self._m_start_rect.isNull(): return if self._m_item_line_style == self.ItemLineStyle.ItemNone: return elif self._m_item_line_style == self.ItemLineStyle.ItemTop: p1 = self._m_start_rect.topLeft() p2 = self._m_start_rect.topRight() elif self._m_item_line_style == self.ItemLineStyle.ItemRight: p1 = self._m_start_rect.topRight() p2 = self._m_start_rect.bottomRight() elif self._m_item_line_style == self.ItemLineStyle.ItemBottom: p1 = self._m_start_rect.bottomLeft() p2 = self._m_start_rect.bottomRight() elif self._m_item_line_style == self.ItemLineStyle.ItemLeft: p1 = self._m_start_rect.topLeft() p2 = self._m_start_rect.bottomLeft() elif self._m_item_line_style == self.ItemLineStyle.ItemRect: p1 = self._m_start_rect.topLeft() p2 = self._m_start_rect.bottomRight() else: return p.save() line_pen = QPen() line_pen.setColor(self._m_item_line_color) line_pen.setWidth(self._m_item_line_width) p.setPen(line_pen) if self._m_item_line_style == self.ItemLineStyle.ItemRect: p.drawRoundedRect(QRectF(p1, p2), self._m_item_radius, self._m_item_radius) else: p.drawLine(p1, p2) p.restore() def _draw_text(self, p: QPainter) -> None: """ 绘制项目的名称 :param p: 画刷 :return: None """ p.save() p.setPen(self._m_item_text_color) for key, value in self._m_item_maps.items(): self._m_item_font.setPointSize(self._m_item_font_size) p.setFont(self._m_item_font) p.drawText(value[1], Qt.AlignCenter, value[0]) p.restore() def _adjust_item_size(self) -> None: """ 调整Item大小 :return: """ if self._m_fixed: return item_count = len(self._m_item_maps) if self._m_orientation == Qt.Horizontal: add_width = 1.0 * (self.width() - self._m_total_text_width) / item_count add_height = 1.0 * (self.height() - self._m_total_text_height) else: add_width = 1.0 * (self.width() - self._m_total_text_width) add_height = 1.0 * (self.height() - self._m_total_text_height) / item_count dx = dy = 0.0 for key, value in self._m_item_maps.items(): # f = QFont() fm = QFontMetrics(self._m_item_font) text_width = fm.width(value[0]) text_height = fm.height() if self._m_orientation == Qt.Horizontal: topLeft = QPointF(dx, 0) dx += text_width + self._m_space + add_width dy = self._m_total_text_height + add_height else: topLeft = QPointF(0, dy) dx = self._m_total_text_width + add_width dy += text_height + self._m_space + add_height bottomRight = QPointF(dx, dy) text_rect = QRectF(topLeft, bottomRight) self._m_item_maps[key] = [value[0], QRectF(text_rect)] if key == self._m_current_index: self._m_start_rect = text_rect self._m_stop_rect = text_rect self.update()