diff --git a/desktop-client/room_widget.py b/desktop-client/room_widget.py index 02b0328..4fcafc1 100644 --- a/desktop-client/room_widget.py +++ b/desktop-client/room_widget.py @@ -4,15 +4,28 @@ import html import re from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, - QPushButton, QFrame, QSlider, QTextEdit, QTextBrowser, QApplication, QToolTip, QSplitter + QPushButton, QFrame, QSlider, QTextEdit, QTextBrowser, QApplication, QToolTip, QSplitter, + QMainWindow ) -from PyQt6.QtCore import Qt, pyqtSignal, QTimer +from PyQt6.QtCore import Qt, pyqtSignal, QTimer, QEvent from PyQt6.QtGui import QIcon, QCursor import uuid from vlc_player import VLCSyncPlayer +class ChatPopoutWindow(QMainWindow): + closed = pyqtSignal() + + def __init__(self, parent=None): + super().__init__(parent) + self.setWindowTitle("Live Chat") + self.resize(350, 600) + + def closeEvent(self, event): + self.closed.emit() + super().closeEvent(event) + class ClickableSlider(QSlider): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -80,6 +93,8 @@ class RoomWidget(QWidget): self.current_users = [] self._is_first_user_update = True + self.popout_window = None + self._setup_ui() def _setup_ui(self): @@ -213,6 +228,17 @@ class RoomWidget(QWidget): chat_header = QLabel("Live Chat") chat_header.setObjectName("chatHeader") + self.popout_btn = QPushButton("↗") + self.popout_btn.setObjectName("iconBtn") + self.popout_btn.setFixedSize(24, 24) + self.popout_btn.setToolTip("Pop out chat") + self.popout_btn.clicked.connect(self.popout_chat) + + header_layout = QHBoxLayout() + header_layout.addWidget(chat_header) + header_layout.addStretch() + header_layout.addWidget(self.popout_btn) + self.users_lbl = QLabel("0 watching") self.users_lbl.setObjectName("usersLbl") @@ -280,7 +306,7 @@ class RoomWidget(QWidget): self.chat_send_btn.clicked.connect(self.send_chat) self.chat_input.returnPressed.connect(self.send_chat) - chat_layout.addWidget(chat_header) + chat_layout.addLayout(header_layout) chat_layout.addWidget(self.users_lbl) chat_layout.addWidget(self.tags_container) chat_layout.addWidget(self.chat_messages, 1) @@ -296,7 +322,8 @@ class RoomWidget(QWidget): # Prevent UI components from stealing focus (which breaks spacebar shortcuts) for w in [self.copy_code_btn, self.leave_btn, self.play_btn, self.fullscreen_btn, - self.chat_send_btn, self.seekbar, self.volume_slider, self.tags_header_btn, self.chat_toggle_btn]: + self.chat_send_btn, self.seekbar, self.volume_slider, self.tags_header_btn, + self.chat_toggle_btn, self.popout_btn]: w.setFocusPolicy(Qt.FocusPolicy.NoFocus) # specifically for chat_messages, we allow clicking links, but avoid focus stealing @@ -381,6 +408,11 @@ class RoomWidget(QWidget): self.tags_header_btn.setText("▶ Highlights") def toggle_chat(self): + if self.popout_window: + self.popout_window.activateWindow() + self.popout_window.raise_() + return + if self.chat_container.isVisible(): self._saved_chat_width = self.chat_container.width() self.chat_container.hide() @@ -390,6 +422,45 @@ class RoomWidget(QWidget): sizes = self.splitter.sizes() self.splitter.setSizes([sizes[0] - w, w]) + def popout_chat(self): + if self.popout_window: + return + + self.popout_window = ChatPopoutWindow(self.window()) + self.popout_window.setWindowTitle(f"Chat - {self.room_code}") + + # Detach from layout + self.chat_container.setParent(None) + + # Set central widget + central = QWidget() + l = QVBoxLayout(central) + l.setContentsMargins(0, 0, 0, 0) + l.addWidget(self.chat_container) + self.popout_window.setCentralWidget(central) + + self.popout_window.closed.connect(self.on_popout_closed) + self.popout_window.show() + + self.popout_btn.hide() + + # Collapse the space in splitter + self.splitter.setSizes([self.width(), 0]) + + def on_popout_closed(self): + self.popout_window = None + + # Re-attach to layout + self.chat_container.setParent(self) + self.splitter.insertWidget(1, self.chat_container) + + self.popout_btn.show() + + # Restore splitter sizes + w = getattr(self, '_saved_chat_width', 320) + self.splitter.setSizes([self.width() - w, w]) + self.chat_container.show() + def toggle_fullscreen(self): top_window = self.window() if top_window.isFullScreen(): @@ -780,7 +851,7 @@ class RoomWidget(QWidget): self.chat_message_ready.emit(text) self.chat_input.setText("") - def _parse_time_arg(self, arg: str) -> float | None: + def _parse_time_arg(self, arg: str): """Parses a time string (absolute time, offset like +30s, or 'now') and returns the target time in MS. Returns None on error.""" current_ms = self.vlc_player.current_time_ms length_ms = self.vlc_player.get_length()