/
Ruden161
/
player
Обзор
Документация
Войти
/
Ruden161
/
player
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/ui/tempCodeRunnerFile.py
237 строк
11 KB
Ruden161
Проводник видео, горячие клавиши, YouTube-style панель
01 мар 2026, 19:16
01 мар 2026, 19:16
816ee8b
Код
Авторство
О чём код?
""" Центральная панель с профессиональным видеоплеером (YouTube-style) """ from typing import Optional from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QTabWidget, QLabel, QSizePolicy, QDialog, QPushButton, QSlider, QApplication) from PyQt6.QtCore import Qt, pyqtSignal, QSize, QTimer, QPoint from PyQt6.QtGui import QIcon, QPixmap, QPainter, QColor, QPainterPath, QCursor from src.ui.video_overlay import VideoOverlay from src.ui.video_controls import VideoControls from src.ui.dialogs import AddTimecodeDialog from src.utils.config import Config class CenterPanel(QWidget): """Центральная панель с профессиональным видеоплеером (YouTube-style)""" timecode_added = pyqtSignal(str, float, str) def __init__(self): super().__init__() self.setWindowFlags(self.windowFlags() | Qt.WindowType.Widget) self.mpv_player = None self.video_container = None self.glass_pane = None self.video_controls = None self.video_overlay = None self.placeholder_widget = None # YouTube-style таймер self.hide_timer = QTimer() self.hide_timer.setInterval(2500) self.hide_timer.timeout.connect(self._hide_controls) self._init_ui() # МАГИЯ: Запускаем отслеживание главного окна через небольшую задержку, # чтобы главное окно успело загрузиться в систему QTimer.singleShot(100, self._setup_window_tracking) def set_vlc_player(self, player): """Установить ссылку на MPV плеер""" self.mpv_player = player if self.video_container: self.mpv_player.set_window_handle(self.video_container) self.mpv_player.context_menu_requested.connect(self._show_context_menu) def _init_ui(self): self.setStyleSheet(f""" CenterPanel {{ border: none; }} QTabWidget::pane {{ border: none; background-color: {Config.COLOR_BG_PLAYER}; }} QTabBar::tab {{ background-color: {Config.COLOR_TAB_INACTIVE}; color: {Config.COLOR_TEXT_DIM}; border: none; padding: 6px 12px; min-width: 60px; }} QTabBar::tab:selected {{ background-color: {Config.COLOR_TAB_ACTIVE}; color: {Config.COLOR_TEXT}; }} """) layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.tab_widget = QTabWidget() self.tab_widget.setDocumentMode(True) self.tab_widget.setMovable(False) # Когда переключаем вкладки, нужно прятать или показывать наше стекло self.tab_widget.currentChanged.connect(lambda: QTimer.singleShot(50, self._sync_overlay_geometry)) player_tab = self._create_player_tab() self.tab_widget.addTab(player_tab, "Плеер") table_tab = self._create_placeholder_tab("Карта стола (в разработке)") self.tab_widget.addTab(table_tab, "Карта стола") graphs_tab = self._create_placeholder_tab("Графики (в разработке)") self.tab_widget.addTab(graphs_tab, "Графики") layout.addWidget(self.tab_widget) def _create_player_tab(self) -> QWidget: player_tab = QWidget() player_tab.setStyleSheet(f"background-color: {Config.COLOR_BG_PLAYER};") # Обычный вертикальный слой player_layout = QVBoxLayout(player_tab) player_layout.setContentsMargins(0, 0, 0, 0) # 1. ПЛЕЙСХОЛДЕР (Текст до загрузки видео) self.placeholder_widget = QWidget() ph_layout = QVBoxLayout(self.placeholder_widget) self.placeholder = QLabel("Загрузите видео для начала") self.placeholder.setAlignment(Qt.AlignmentFlag.AlignCenter) self.placeholder.setStyleSheet(f"color: {Config.COLOR_TEXT_DIM}; font-size: 16px;") ph_layout.addWidget(self.placeholder) player_layout.addWidget(self.placeholder_widget) # 2. ВИДЕО КОНТЕЙНЕР (Скрыт по умолчанию) self.video_container = QWidget() self.video_container.setAttribute(Qt.WidgetAttribute.WA_NativeWindow, True) self.video_container.setStyleSheet("background-color: black;") self.video_container.hide() player_layout.addWidget(self.video_container, stretch=1) # ===================================================================== # 3. НЕВИДИМОЕ СТЕКЛО (ПЛАВАЮЩЕЕ ОКНО ПОВЕРХ ВСЕГО) # ===================================================================== # Мы делаем его отдельным Tool-окном без рамок. Оно физически не может быть перекрыто видео! self.glass_pane = QWidget(self.window(), Qt.WindowType.FramelessWindowHint | Qt.WindowType.Tool) self.glass_pane.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) self.glass_pane.setStyleSheet("background: transparent;") self.glass_pane.setMouseTracking(True) self.glass_pane.mouseMoveEvent = self._on_glass_mouse_move self.glass_pane.leaveEvent = self._on_glass_leave # Наши контролы добавляем прямо на это плавающее стекло self.video_overlay = VideoOverlay(self.glass_pane) self.video_overlay.add_timecode_requested.connect(self._on_add_timecode_requested) self.video_controls = VideoControls(self.glass_pane) return player_tab def _create_placeholder_tab(self, message: str) -> QWidget: tab = QWidget() tab.setStyleSheet(f"background-color: {Config.COLOR_BG_PLAYER};") layout = QVBoxLayout(tab) label = QLabel(message) label.setAlignment(Qt.AlignmentFlag.AlignCenter) layout.addWidget(label) return tab # ========================================================================= # МАГИЯ ПОЗИЦИОНИРОВАНИЯ: СТЕКЛО ПРИЛИПАЕТ К ВИДЕО # ========================================================================= def _setup_window_tracking(self): """Перехватываем движение всего приложения, чтобы двигать стекло""" top_window = self.window() if top_window: top_window.installEventFilter(self) def eventFilter(self, obj, event): """Слушаем, не передвинул ли пользователь главное окно""" if obj == self.window() and event.type() in (event.Type.Move, event.Type.Resize): self._sync_overlay_geometry() return super().eventFilter(obj, event) def resizeEvent(self, event): """Слушаем изменение размера панели""" super().resizeEvent(event) self._sync_overlay_geometry() def _sync_overlay_geometry(self): """Самая важная функция: рассчитывает координаты на мониторе""" if not hasattr(self, 'glass_pane') or not self.glass_pane: return # Если видео скрыто или открыта другая вкладка - прячем стекло if not self.video_container.isVisible() or not self.isVisible(): self.glass_pane.hide() return self.glass_pane.show() # Получаем ГЛОБАЛЬНЫЕ координаты контейнера на мониторе (относительно экрана, а не окна) global_pos = self.video_container.mapToGlobal(QPoint(0, 0)) # Натягиваем невидимое стекло ровно на эти координаты self.glass_pane.setGeometry( global_pos.x(), global_pos.y(), self.video_container.width(), self.video_container.height() ) # Располагаем панель управления в самом низу стекла if self.video_controls: panel_height = 80 self.video_controls.setGeometry( 0, self.glass_pane.height() - panel_height, self.glass_pane.width(), panel_height ) if self.video_overlay: self.video_overlay.setGeometry(0, 0, self.glass_pane.width(), self.glass_pane.height()) # ========================================================================= # ЛОГИКА ИНТЕРФЕЙСА # ========================================================================= def hide_placeholder(self): if self.placeholder_widget: self.placeholder_widget.hide() self.video_container.show() self._sync_overlay_geometry() # Сразу синхронизируем позицию стекла self.glass_pane.setCursor(Qt.CursorShape.ArrowCursor) def _hide_controls(self): if self.mpv_player and self.mpv_player.is_playing: self.video_controls.hide() # Курсор прячем ИМЕННО на стекле self.glass_pane.setCursor(Qt.CursorShape.BlankCursor) def _show_controls(self): if self.mpv_player and self.mpv_player.is_playing: self.video_controls.show() self.glass_pane.setCursor(Qt.CursorShape.ArrowCursor) self.hide_timer.start() def _on_glass_mouse_move(self, event): self._show_controls() def _on_glass_leave(self, event): if self.mpv_player and self.mpv_player.is_playing: self.hide_timer.start() def _show_add_timecode_dialog(self, current_time: Optional[float] = None): if not self.mpv_player: return if current_time is None: current_time = self.mpv_player.get_time() / 1000.0 dialog = AddTimecodeDialog(current_time, self.window()) if dialog.exec() == QDialog.DialogCode.Accepted: name = dialog.name_edit.text() description = dialog.description_edit.toPlainText() self.timecode_added.emit(name, current_time, description) def _on_add_timecode_requested(self): if self.mpv_player and self.mpv_player.is_playing: self.mpv_player.pause() if self.mpv_player: current_time = self.mpv_player.get_time() / 1000.0 self._show_add_timecode_dialog(current_time) def _show_context_menu(self): self._on_add_timecode_requested()