diff --git a/beeref/__main__.py b/beeref/__main__.py index 84f29d9..8cfb445 100755 --- a/beeref/__main__.py +++ b/beeref/__main__.py @@ -64,15 +64,15 @@ class BeeRefMainWindow(QtWidgets.QMainWindow): self.show() def closeEvent(self, event): - # Проверяем наличие несохраненных изменений + # Check for unsaved changes confirm = self.view.get_confirmation_unsaved_changes( 'There are unsaved changes. Are you sure you want to quit?') if not confirm: - # Пользователь отменил закрытие + # User cancelled closing event.ignore() return - # Сохраняем геометрию окна + # Save window geometry geom = self.saveGeometry() self.view.settings.setValue('MainWindow/geometry', geom) event.accept() diff --git a/beeref/assets/__init__.py b/beeref/assets/__init__.py index 237c089..1bbc471 100644 --- a/beeref/assets/__init__.py +++ b/beeref/assets/__init__.py @@ -59,20 +59,20 @@ class BeeAssets: pixmap, int(hotspot[0]/scaling), int(hotspot[1]/scaling)) def cursor_from_svg(self, filename, hotspot, size=24): - """Создает курсор из SVG файла.""" + """Creates cursor from SVG file.""" app = QtWidgets.QApplication.instance() scaling = app.primaryScreen().devicePixelRatio() - # Загружаем SVG + # Load SVG svg_path = str(self.PATH.joinpath(filename)) renderer = QtSvg.QSvgRenderer(svg_path) - # Создаем pixmap нужного размера + # Create pixmap of required size pixmap_size = int(size * scaling) pixmap = QtGui.QPixmap(pixmap_size, pixmap_size) - pixmap.fill(QtGui.QColor(0, 0, 0, 0)) # Прозрачный фон + pixmap.fill(QtGui.QColor(0, 0, 0, 0)) # Transparent background - # Рендерим SVG в pixmap + # Render SVG to pixmap painter = QtGui.QPainter(pixmap) renderer.render(painter) painter.end() diff --git a/beeref/fileio/image.py b/beeref/fileio/image.py index c18f0d6..c214169 100644 --- a/beeref/fileio/image.py +++ b/beeref/fileio/image.py @@ -30,7 +30,7 @@ logger = logging.getLogger(__name__) def is_gif_file(path): - """Проверяет, является ли файл GIF.""" + """Checks if file is a GIF.""" if isinstance(path, str): ext = os.path.splitext(path)[1].lower() return ext == '.gif' @@ -95,9 +95,9 @@ def exif_rotated_image(path=None): def load_image(path): if isinstance(path, str): path = os.path.normpath(path) - # Проверяем, является ли файл GIF + # Check if file is a GIF if is_gif_file(path): - return (None, path) # Возвращаем None для изображения, путь для GIF + return (None, path) # Return None for image, path for GIF return (exif_rotated_image(path), path) if path.isLocalFile(): path = os.path.normpath(path.toLocalFile()) diff --git a/beeref/items.py b/beeref/items.py index 8c56ead..2126562 100644 --- a/beeref/items.py +++ b/beeref/items.py @@ -745,7 +745,7 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem): """Class for freehand drawing items.""" TYPE = 'draw' - CLICKABLE_PADDING = 8.0 # Отступ вокруг линии для увеличения кликабельной области + CLICKABLE_PADDING = 8.0 # Padding around line to increase clickable area def __init__(self, path=None, **kwargs): super().__init__() @@ -753,9 +753,9 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem): logger.debug(f'Initialized {self}') self.is_image = False self.init_selectable() - self.is_editable = False # Рисование не редактируется через двойной клик + self.is_editable = False # Drawing is not editable via double-click - # Настройки пера по умолчанию + # Default pen settings self.pen_color = QtGui.QColor(*COLORS['Scene:Text']) self.pen_width = 8 self.pen_style = 'solid' # 'solid', 'dashed', 'arrow', '<-', '<->' @@ -766,7 +766,7 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem): self.setPath(path) def setPath(self, path): - """Устанавливает путь и обновляет геометрию.""" + """Sets path and updates geometry.""" self.prepareGeometryChange() super().setPath(path) self.update() @@ -776,7 +776,7 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem): data = kwargs.get('data', {}) item = cls() - # Восстановление пути из данных + # Restore path from data if 'path' in data and data['path']: path = QtGui.QPainterPath() path_data = data['path'] @@ -800,7 +800,7 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem): return f'Drawing ({self.path().elementCount()} points)' def get_extra_save_data(self): - """Сохраняет данные рисунка для сериализации.""" + """Saves drawing data for serialization.""" path = self.path() path_data = [] for i in range(path.elementCount()): @@ -815,7 +815,7 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem): } def _update_pen(self): - """Обновляет перо с текущими настройками.""" + """Updates pen with current settings.""" if self.pen_style == 'dashed': pen_style = QtCore.Qt.PenStyle.DashLine else: @@ -828,19 +828,19 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem): self.setPen(pen) def set_pen_color(self, color: QtGui.QColor): - """Устанавливает цвет пера.""" + """Sets pen color.""" self.pen_color = color self._update_pen() self.update() def set_pen_width(self, width: int): - """Устанавливает толщину пера.""" - self.pen_width = max(1, min(width, 50)) # Ограничение 1-50 + """Sets pen width.""" + self.pen_width = max(1, min(width, 50)) # Limit 1-50 self._update_pen() self.update() def set_pen_style(self, style: str): - """Устанавливает стиль линии: 'solid', 'dashed', 'arrow', '<-', '<->'.""" + """Sets line style: 'solid', 'dashed', 'arrow', '<-', '<->'.""" if style in ('solid', 'dashed', 'arrow', '<-', '<->'): self.pen_style = style self._update_pen() @@ -861,31 +861,31 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem): return item def bounding_rect_unselected(self): - """Возвращает границы элемента без учета выделения.""" + """Returns item bounds without selection.""" path = self.path() if path.isEmpty(): return QtCore.QRectF() - # Получаем boundingRect напрямую из пути + # Get boundingRect directly from path base_rect = path.boundingRect() - # Добавляем отступ для толщины пера и кликабельной области + # Add margin for pen width and clickable area margin = (self.pen_width / 2.0) + self.CLICKABLE_PADDING return base_rect.marginsAdded( QtCore.QMarginsF(margin, margin, margin, margin)) def shape(self): - """Возвращает прямоугольную кликабельную область, как в PureRef.""" + """Returns rectangular clickable area, like in PureRef.""" path = QtGui.QPainterPath() rect = self.bounding_rect_unselected() - # Если элемент выделен и есть ручки, добавляем области для ручек + # If item is selected and has handles, add handle areas if self.has_selection_handles(): margin = self.select_resize_size / 2 rect = rect.marginsAdded( QtCore.QMarginsF(margin, margin, margin, margin)) path.addRect(rect) - # Добавляем области для ручек поворота в углах + # Add rotation handle areas at corners for corner in self.corners: path.addPath(self.get_rotate_bounds(corner)) else: @@ -894,74 +894,74 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem): return path def contains(self, point): - """Проверяет, попадает ли точка в прямоугольную область линии.""" - # Используем boundingRect для прямоугольной области клика + """Checks if point falls within rectangular line area.""" + # Use boundingRect for rectangular click area return self.bounding_rect_unselected().contains(point) def _get_path_end_points(self, path): - """Получает последние две точки пути для определения направления стрелки.""" + """Gets last two path points to determine arrow direction.""" if path.elementCount() < 2: return None, None - # Получаем последнюю точку пути + # Get last path point last_point = path.pointAtPercent(1.0) - # Получаем предпоследнюю точку (близко к концу) + # Get second-to-last point (close to end) if path.elementCount() >= 2: - prev_point = path.pointAtPercent(0.95) # 95% от пути + prev_point = path.pointAtPercent(0.95) # 95% of path else: prev_point = path.pointAtPercent(0.0) return prev_point, last_point def _get_path_start_points(self, path): - """Получает первые две точки пути для определения направления стрелки.""" + """Gets first two path points to determine arrow direction.""" if path.elementCount() < 2: return None, None - # Получаем первую точку пути + # Get first path point first_point = path.pointAtPercent(0.0) - # Получаем вторую точку (близко к началу) + # Get second point (close to start) if path.elementCount() >= 2: - second_point = path.pointAtPercent(0.05) # 5% от пути + second_point = path.pointAtPercent(0.05) # 5% of path else: second_point = path.pointAtPercent(1.0) return first_point, second_point def _draw_arrow_right(self, painter, path): - """Рисует стрелку вправо в конце линии.""" + """Draws arrow to the right at the end of line.""" if path.elementCount() < 2: return - # Получаем последние две точки для определения направления + # Get last two points to determine direction prev_point, last_point = self._get_path_end_points(path) if prev_point is None or last_point is None: return - # Вычисляем направление стрелки + # Calculate arrow direction dx = last_point.x() - prev_point.x() dy = last_point.y() - prev_point.y() length = (dx * dx + dy * dy) ** 0.5 if length == 0: return - # Нормализуем вектор направления + # Normalize direction vector dx /= length dy /= length - # Размер стрелки зависит от толщины линии + # Arrow size depends on line width arrow_size = max(self.pen_width * 3, 8) - # Угол стрелки - angle = 0.5 # примерно 30 градусов + # Arrow angle + angle = 0.5 # approximately 30 degrees - # Координаты конца стрелки + # Arrow end coordinates end_x = last_point.x() end_y = last_point.y() - # Координаты боковых точек стрелки + # Arrow side point coordinates perp_x = -dy perp_y = dx arrow_x1 = end_x - arrow_size * dx + arrow_size * angle * perp_x @@ -969,7 +969,7 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem): arrow_x2 = end_x - arrow_size * dx - arrow_size * angle * perp_x arrow_y2 = end_y - arrow_size * dy - arrow_size * angle * perp_y - # Рисуем стрелку + # Draw arrow arrow_path = QtGui.QPainterPath() arrow_path.moveTo(end_x, end_y) arrow_path.lineTo(arrow_x1, arrow_y1) @@ -983,36 +983,36 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem): painter.drawPath(arrow_path) def _draw_arrow_left(self, painter, path): - """Рисует стрелку влево в начале линии.""" + """Draws arrow to the left at the start of line.""" if path.elementCount() < 2: return - # Получаем первые две точки для определения направления + # Get first two points to determine direction first_point, second_point = self._get_path_start_points(path) if first_point is None or second_point is None: return - # Вычисляем направление стрелки (от второй точки к первой) + # Calculate arrow direction (from second point to first) dx = first_point.x() - second_point.x() dy = first_point.y() - second_point.y() length = (dx * dx + dy * dy) ** 0.5 if length == 0: return - # Нормализуем вектор направления + # Normalize direction vector dx /= length dy /= length - # Размер стрелки зависит от толщины линии + # Arrow size depends on line width arrow_size = max(self.pen_width * 3, 8) - # Угол стрелки - angle = 0.5 # примерно 30 градусов + # Arrow angle + angle = 0.5 # approximately 30 degrees - # Координаты начала стрелки + # Arrow start coordinates start_x = first_point.x() start_y = first_point.y() - # Координаты боковых точек стрелки + # Arrow side point coordinates perp_x = -dy perp_y = dx arrow_x1 = start_x - arrow_size * dx + arrow_size * angle * perp_x @@ -1020,7 +1020,7 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem): arrow_x2 = start_x - arrow_size * dx - arrow_size * angle * perp_x arrow_y2 = start_y - arrow_size * dy - arrow_size * angle * perp_y - # Рисуем стрелку + # Draw arrow arrow_path = QtGui.QPainterPath() arrow_path.moveTo(start_x, start_y) arrow_path.lineTo(arrow_x1, arrow_y1) @@ -1034,11 +1034,11 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem): painter.drawPath(arrow_path) def _draw_arrow_both(self, painter, path): - """Рисует стрелки в обе стороны линии.""" + """Draws arrows on both sides of line.""" if path.elementCount() < 2: return - # Рисуем стрелку вправо (в конце) + # Draw arrow to the right (at end) prev_point, last_point = self._get_path_end_points(path) if prev_point is not None and last_point is not None: dx = last_point.x() - prev_point.x() @@ -1070,7 +1070,7 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem): QtCore.Qt.PenJoinStyle.RoundJoin)) painter.drawPath(arrow_path) - # Рисуем стрелку влево (в начале) + # Draw arrow to the left (at start) first_point, second_point = self._get_path_start_points(path) if first_point is not None and second_point is not None: dx = first_point.x() - second_point.x() @@ -1103,15 +1103,15 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem): painter.drawPath(arrow_path) def paint(self, painter, option, widget): - """Отрисовка пути с рамкой выделения.""" - # Отключаем стандартную отрисовку Qt для выделенных элементов + """Renders path with selection outline.""" + # Disable standard Qt rendering for selected items option.state &= ~QtWidgets.QStyle.StateFlag.State_Selected option.state &= ~QtWidgets.QStyle.StateFlag.State_HasFocus - # Рисуем основную линию + # Draw main line super().paint(painter, option, widget) path = self.path() - # Рисуем стрелку в зависимости от стиля + # Draw arrow depending on style if self.pen_style == 'arrow': self._draw_arrow_right(painter, path) elif self.pen_style == '<-': @@ -1122,13 +1122,13 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem): self.paint_selectable(painter, option, widget) def paint_debug(self, painter, option, widget): - """Переопределяем для полного отключения отладочной информации.""" - # Полностью отключаем отладочную информацию для линий + """Override to completely disable debug information.""" + # Completely disable debug information for lines pass def paint_selectable(self, painter, option, widget): - """Переопределяем для убирания пунктирной обводки (отладочной информации).""" - # Не вызываем paint_debug, чтобы убрать пунктирную обводку + """Override to remove dashed outline (debug information).""" + # Don't call paint_debug to remove dashed outline # self.paint_debug(painter, option, widget) if not self.has_selection_outline(): @@ -1152,7 +1152,7 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem): painter.drawPoint(corner) def copy_to_clipboard(self, clipboard): - """Для рисования копирование не поддерживается.""" + """Copying is not supported for drawings.""" pass @@ -1231,6 +1231,6 @@ class BeeErrorItem(BeeItemMixin, QtWidgets.QGraphicsTextItem): clipboard.setText(self.toPlainText()) -# Импортируем GIF item для регистрации в item_registry +# Import GIF item for registration in item_registry from beeref.gif_item import BeeGifItem # noqa: E402, F401 diff --git a/beeref/widgets/draw_floating_menu.py b/beeref/widgets/draw_floating_menu.py index e6ef86b..9428385 100644 --- a/beeref/widgets/draw_floating_menu.py +++ b/beeref/widgets/draw_floating_menu.py @@ -25,15 +25,15 @@ class UpwardComboBox(QtWidgets.QComboBox): def showPopup(self): """Override to show popup above the combobox.""" super().showPopup() - # Используем QTimer для получения popup после его создания + # Use QTimer to get popup after it's created QtCore.QTimer.singleShot(0, self._reposition_popup) def _reposition_popup(self): - """Перемещает popup выше combobox.""" - # Ищем активный popup виджет + """Moves popup above the combobox.""" + # Find active popup widget popup = QtWidgets.QApplication.activePopupWidget() if not popup: - # Альтернативный способ - найти через view + # Alternative method - find through view view = self.view() if view: popup = view.parent() @@ -41,9 +41,9 @@ class UpwardComboBox(QtWidgets.QComboBox): popup = popup.parent() if popup: - # Получаем глобальную позицию combobox + # Get global position of combobox global_pos = self.mapToGlobal(QtCore.QPoint(0, 0)) - # Вычисляем новую позицию выше combobox + # Calculate new position above combobox popup_height = popup.height() new_y = global_pos.y() - popup_height popup.move(global_pos.x(), new_y) @@ -56,7 +56,7 @@ class DrawFloatingMenu(FloatingMenu): super().__init__(parent, view) self.current_item: Optional["BeeDrawItem"] = None - # Кнопка выбора цвета через colorpicker + # Color selection button via colorpicker assets = BeeAssets() icons_path = assets.PATH.joinpath('icons') palette_icon = QtGui.QIcon(str(icons_path.joinpath('palette.svg'))) @@ -68,15 +68,15 @@ class DrawFloatingMenu(FloatingMenu): ) self.color_btn.setToolTip("Color") - # Combobox для выбора стиля линии + # Combobox for line style selection self.add_separator() self.style_combo = self.add_style_combobox() - # Селектор толщины пера + # Pen width selector self.add_separator() self.width_slider = self.add_width_slider() - # Кнопка закрытия меню (отмена режима рисования) + # Close menu button (cancel drawing mode) self.add_separator() close_icon = QtGui.QIcon(str(icons_path.joinpath('close.svg'))) self.close_button = self.add_button( @@ -87,11 +87,11 @@ class DrawFloatingMenu(FloatingMenu): self.close_button.setToolTip("Close") def add_style_combobox(self): - """Добавляет combobox для выбора стиля линии.""" + """Adds combobox for line style selection.""" assets = BeeAssets() icons_path = assets.PATH.joinpath('icons') - # Загружаем иконки для стилей + # Load icons for styles solid_icon = QtGui.QIcon(str(icons_path.joinpath('line-solid.svg'))) dashed_icon = QtGui.QIcon(str(icons_path.joinpath('line-dashed.svg'))) arrow_icon = QtGui.QIcon(str(icons_path.joinpath('line-arrow.svg'))) @@ -103,20 +103,20 @@ class DrawFloatingMenu(FloatingMenu): combo.setMinimumWidth(40) combo.setIconSize(QtCore.QSize(32, 32)) - # Добавляем элементы с иконками + # Add items with icons combo.addItem(solid_icon, "", 'solid') combo.addItem(dashed_icon, "", 'dashed') combo.addItem(arrow_icon, "", 'arrow') combo.addItem(arrow_left_icon, "", '<-') combo.addItem(arrow_both_icon, "", '<->') - # Устанавливаем иконки для элементов + # Set icons for items combo.setItemIcon(0, solid_icon) combo.setItemIcon(1, dashed_icon) combo.setItemIcon(2, arrow_icon) combo.setItemIcon(3, arrow_left_icon) combo.setItemIcon(4, arrow_both_icon) - combo.setCurrentIndex(0) # По умолчанию solid + combo.setCurrentIndex(0) # Default to solid combo.currentIndexChanged.connect(self._on_style_changed) combo.setToolTip("Line style") @@ -124,7 +124,7 @@ class DrawFloatingMenu(FloatingMenu): return combo def _on_color_clicked(self) -> None: - """Открывает диалог выбора цвета.""" + """Opens color selection dialog.""" if not self.current_item: return @@ -136,7 +136,7 @@ class DrawFloatingMenu(FloatingMenu): self.set_pen_color(color) def _on_style_changed(self, index: int) -> None: - """Обработчик изменения стиля линии.""" + """Handler for line style change.""" if not self.current_item: return style = self.style_combo.itemData(index) @@ -144,7 +144,7 @@ class DrawFloatingMenu(FloatingMenu): self.set_pen_style(style) def add_width_slider(self): - """Добавляет слайдер для выбора толщины пера.""" + """Adds slider for pen width selection.""" container = QtWidgets.QWidget() layout = QtWidgets.QHBoxLayout(container) layout.setContentsMargins(0, 0, 0, 0) @@ -162,11 +162,11 @@ class DrawFloatingMenu(FloatingMenu): return slider def show_for_item(self, item: "BeeDrawItem") -> None: - """Показывает меню для выбранного элемента рисования.""" + """Shows menu for selected drawing item.""" self.current_item = item if item: self.width_slider.setValue(item.pen_width) - # Устанавливаем текущий стиль в combobox + # Set current style in combobox style_index = self.style_combo.findData(item.pen_style) if style_index >= 0: self.style_combo.blockSignals(True) @@ -175,29 +175,29 @@ class DrawFloatingMenu(FloatingMenu): super().show_for_item(item) def set_pen_color(self, color: QtGui.QColor): - """Устанавливает цвет пера для выбранного элемента.""" + """Sets pen color for selected item.""" if self.current_item: self.current_item.set_pen_color(color) def set_pen_width(self, width: int): - """Устанавливает толщину пера для выбранного элемента.""" + """Sets pen width for selected item.""" if self.current_item: self.current_item.set_pen_width(width) def set_pen_style(self, style: str): - """Устанавливает стиль линии для выбранного элемента.""" + """Sets line style for selected item.""" if self.current_item: self.current_item.set_pen_style(style) def _on_close_clicked(self) -> None: - """Закрывает меню и отменяет режим рисования.""" + """Closes menu and cancels drawing mode.""" self.view.cancel_drawing_mode() self.hide_menu() def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: - """Обрабатывает нажатия клавиш.""" + """Handles key press events.""" if event.key() == QtCore.Qt.Key.Key_Escape: - # ESC закрывает меню и отменяет режим рисования + # ESC closes menu and cancels drawing mode self.view.cancel_drawing_mode() self.hide_menu() event.accept() diff --git a/beeref/widgets/floating_menu.py b/beeref/widgets/floating_menu.py index f0cedc5..462396f 100644 --- a/beeref/widgets/floating_menu.py +++ b/beeref/widgets/floating_menu.py @@ -39,10 +39,10 @@ class FloatingMenu(QtWidgets.QWidget): self._cached_viewport_size: Optional[QtCore.QSize] = None self._cached_window_pos: Optional[QtCore.QPoint] = None - # Таймер для отслеживания перемещения главного окна + # Timer for tracking main window movement self._position_timer = QtCore.QTimer(self) self._position_timer.timeout.connect(self._check_window_position) - self._position_timer.setInterval(50) # Проверка каждые 50мс + self._position_timer.setInterval(50) # Check every 50ms # Main layout with uniform spacing self._layout = QtWidgets.QHBoxLayout(self) @@ -56,7 +56,7 @@ class FloatingMenu(QtWidgets.QWidget): self.hide() def _check_window_position(self) -> None: - """Проверяет позицию viewport и обновляет позицию меню при необходимости.""" + """Checks viewport position and updates menu position if needed.""" if not self.isVisible(): return @@ -68,11 +68,11 @@ class FloatingMenu(QtWidgets.QWidget): if viewport is None: return - # Получаем позицию viewport в глобальных координатах + # Get viewport position in global coordinates view_rect = viewport.rect() current_viewport_pos = viewport.mapToGlobal(view_rect.topLeft()) - # Если позиция viewport изменилась, обновляем позицию меню + # If viewport position changed, update menu position if self._cached_window_pos is not None and self._cached_window_pos != current_viewport_pos: self.update_position() @@ -80,50 +80,50 @@ class FloatingMenu(QtWidgets.QWidget): def _apply_rounded_mask(self) -> None: """ - Применяет закругленную маску к виджету с антиалиасингом. - Реализация на основе подхода из статьи VK Teams. + Applies rounded mask to widget with antialiasing. + Implementation based on approach from VK Teams article. """ size = self.size() if size.width() == 0 or size.height() == 0: return - # Для более плавного сглаживания используем QBitmap с антиалиасингом - # Создаем изображение с увеличенным разрешением (как в статье) + # Use QBitmap with antialiasing for smoother rendering + # Create image with increased resolution (as in article) scale_factor = 2 scaled_size = QtCore.QSize( int(size.width() * scale_factor), int(size.height() * scale_factor) ) - # Создаем QPixmap для рисования с антиалиасингом + # Create QPixmap for drawing with antialiasing pixmap = QtGui.QPixmap(scaled_size) pixmap.fill(QtCore.Qt.GlobalColor.transparent) painter = QtGui.QPainter(pixmap) painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing, True) painter.setPen(QtCore.Qt.PenStyle.NoPen) - painter.setBrush(QtCore.Qt.GlobalColor.black) # Черный для маски + painter.setBrush(QtCore.Qt.GlobalColor.black) # Black for mask - # Рисуем закругленный прямоугольник на увеличенном разрешении + # Draw rounded rectangle at increased resolution scaled_rect = QtCore.QRectF(0, 0, scaled_size.width(), scaled_size.height()) scaled_radius = self.CORNER_RADIUS * scale_factor painter.drawRoundedRect(scaled_rect, scaled_radius, scaled_radius) painter.end() - # Масштабируем обратно с сглаживанием + # Scale back with smoothing pixmap = pixmap.scaled( size, QtCore.Qt.AspectRatioMode.IgnoreAspectRatio, QtCore.Qt.TransformationMode.SmoothTransformation ) - # Преобразуем в QBitmap для маски - # В маске: непрозрачные пиксели = видимые области, прозрачные = невидимые + # Convert to QBitmap for mask + # In mask: opaque pixels = visible areas, transparent = invisible image = pixmap.toImage() - # Создаем маску из непрозрачных пикселей + # Create mask from opaque pixels bitmap = QtGui.QBitmap.fromImage(image.createAlphaMask()) - # Применяем маску + # Apply mask self.setMask(bitmap) def add_widget(self, widget: QtWidgets.QWidget) -> QtWidgets.QWidget: @@ -172,18 +172,18 @@ class FloatingMenu(QtWidgets.QWidget): self._cached_viewport_size = None self._cached_window_pos = None self.update_position() - # Запускаем таймер для отслеживания перемещения окна + # Start timer for tracking window movement self._position_timer.start() # Ensure menu is on top after positioning self.raise_() - # Возвращаем фокус view, чтобы события клавиатуры обрабатывались правильно - # FloatingMenu не должен перехватывать фокус, так как он имеет NoFocus + # Return focus to view so keyboard events are handled correctly + # FloatingMenu should not intercept focus as it has NoFocus if self.view: self.view.setFocus() def hide_menu(self) -> None: self.current_item = None - # Останавливаем таймер + # Stop timer self._position_timer.stop() # Clear cache when hiding menu self._cached_position = None @@ -207,7 +207,7 @@ class FloatingMenu(QtWidgets.QWidget): view_rect = viewport.rect() viewport_size = view_rect.size() - # Получаем текущую позицию viewport для проверки изменений + # Get current viewport position for change detection current_viewport_pos = viewport.mapToGlobal(view_rect.topLeft()) # Check if viewport size and position changed @@ -232,28 +232,28 @@ class FloatingMenu(QtWidgets.QWidget): bottom_right_global = viewport.mapToGlobal(view_rect.bottomRight()) top_right_global = viewport.mapToGlobal(view_rect.topRight()) - # Получаем границы viewport в глобальных координатах + # Get viewport bounds in global coordinates viewport_left = top_left_global.x() viewport_right = top_right_global.x() viewport_top = top_left_global.y() viewport_bottom = bottom_left_global.y() viewport_width = viewport_right - viewport_left - # Вычисляем позицию по X (центрируем, но не выходим за границы) + # Calculate X position (center, but don't go beyond boundaries) x = viewport_left + max(0, (viewport_width - width) // 2) - # Ограничиваем, чтобы меню не выходило за левую и правую границы + # Limit to prevent menu from going beyond left and right boundaries x = max(viewport_left, min(x, viewport_right - width)) - # Вычисляем позицию по Y (снизу с отступом) + # Calculate Y position (bottom with margin) y = viewport_bottom - height - self.BOTTOM_MARGIN - # Ограничиваем, чтобы меню не выходило за верхнюю границу - # Если меню не помещается снизу, размещаем его сверху + # Limit to prevent menu from going beyond top boundary + # If menu doesn't fit at bottom, place it at top if y < viewport_top: y = viewport_top + self.BOTTOM_MARGIN - # Также проверяем, что меню не выходит за нижнюю границу + # Also check that menu doesn't go beyond bottom boundary if y + height > viewport_bottom: y = viewport_bottom - height - self.BOTTOM_MARGIN - # Если и так не помещается, размещаем сверху + # If still doesn't fit, place at top if y < viewport_top: y = viewport_top + self.BOTTOM_MARGIN @@ -265,7 +265,7 @@ class FloatingMenu(QtWidgets.QWidget): self._cached_position = new_position self._cached_viewport_size = viewport_size self._cached_window_pos = current_viewport_pos - # Обновляем маску после изменения размера/позиции + # Update mask after size/position change self._apply_rounded_mask() def parentWidget(self): @@ -289,6 +289,6 @@ class FloatingMenu(QtWidgets.QWidget): super().mouseReleaseEvent(event) def resizeEvent(self, event: QtGui.QResizeEvent) -> None: - """Обновляет маску при изменении размера виджета.""" + """Updates mask when widget size changes.""" super().resizeEvent(event) self._apply_rounded_mask()