Restructure main window into a watch-first tab shell
The single-page downloader layout becomes the Downloads tab of a SmoothTabWidget with Watch / Search / Feed / Browse / Downloads pages. The init_ui edit is deliberately small (the old central widget is now self.download_page); all new behavior lives in new modules: - ytsage_gui_router.py: AppRouter signal hub (playVideo, queueVideo, downloadVideo, openChannel, openPlaylist). A card's Download button deep-links into the Downloads tab with the URL prefilled and analysis started automatically. - ytsage_gui_cards.py: VideoCard (thumbnail with disk cache under APP_THUMBNAILS_DIR, title/channel/duration, Play/Queue/Download actions, double-click to play) and VideoCardGrid (responsive grid, Load more pagination). - ytsage_gui_watch.py: WatchPage hosting the mpv PlayerPanel and a drag-reorderable play queue with auto-advance on end of file. - ytsage_gui_search.py: SearchPage running YtdlpClient.search off the GUI thread with Load more pagination. - Browse and Feed pages are placeholders, implemented next. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -53,6 +53,7 @@ from .ytsage_gui_dialogs import ( # use of src\gui\ytsage_gui_dialogs\__init__.
|
||||
from .ytsage_gui_format_table import FormatTableMixin
|
||||
from .ytsage_gui_video_info import VideoInfoMixin
|
||||
from .ytsage_gui_analysis import AnalysisMixin
|
||||
from .ytsage_smooth_tab_widget import SmoothTabWidget
|
||||
from ..utils.ytsage_constants import (
|
||||
ICON_PATH,
|
||||
SOUND_PATH,
|
||||
@@ -381,9 +382,10 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin, AnalysisMixin):
|
||||
self.setWindowTitle(f"{_('app.title')} {_('app.version', version=self.version)}")
|
||||
self.setMinimumSize(900, 750)
|
||||
|
||||
# Main widget and layout
|
||||
# Downloads page keeps the original single-page layout; the central
|
||||
# widget becomes a tab shell built at the end of init_ui
|
||||
main_widget = QWidget()
|
||||
self.setCentralWidget(main_widget)
|
||||
self.download_page = main_widget
|
||||
layout = QVBoxLayout(main_widget)
|
||||
layout.setSpacing(8)
|
||||
layout.setContentsMargins(20, 20, 20, 20)
|
||||
@@ -626,6 +628,61 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin, AnalysisMixin):
|
||||
# Disable analysis-dependent controls until video is analyzed
|
||||
self.toggle_analysis_dependent_controls(enabled=False)
|
||||
|
||||
self._setup_main_tabs()
|
||||
|
||||
def _setup_main_tabs(self) -> None:
|
||||
"""Wrap the pages in the watch-first tab shell (SageTube)."""
|
||||
from .ytsage_gui_browse import BrowsePage
|
||||
from .ytsage_gui_feed import FeedPage
|
||||
from .ytsage_gui_player import PlayerPanel, create_player_panel
|
||||
from .ytsage_gui_router import AppRouter
|
||||
from .ytsage_gui_search import SearchPage
|
||||
from .ytsage_gui_watch import WatchPage
|
||||
|
||||
self.router = AppRouter(self)
|
||||
|
||||
self.watch_page = WatchPage(self.router, self)
|
||||
self.search_page = SearchPage(self.router, self)
|
||||
self.feed_page = FeedPage(self.router, self)
|
||||
self.browse_page = BrowsePage(self.router, self)
|
||||
|
||||
self.main_tabs = SmoothTabWidget(self)
|
||||
self.main_tabs.addTab(self.watch_page, _("main_tabs.watch"))
|
||||
self.main_tabs.addTab(self.search_page, _("main_tabs.search"))
|
||||
self.main_tabs.addTab(self.feed_page, _("main_tabs.feed"))
|
||||
self.main_tabs.addTab(self.browse_page, _("main_tabs.browse"))
|
||||
self.main_tabs.addTab(self.download_page, _("main_tabs.downloads"))
|
||||
self.setCentralWidget(self.main_tabs)
|
||||
|
||||
self.router.playVideo.connect(self._route_play_video)
|
||||
self.router.queueVideo.connect(self.watch_page.enqueue)
|
||||
self.router.downloadVideo.connect(self._route_download_video)
|
||||
self.router.openChannel.connect(self._route_open_channel)
|
||||
self.router.openPlaylist.connect(self._route_open_playlist)
|
||||
|
||||
def _tab_index_of(self, page) -> int:
|
||||
for i in range(self.main_tabs.stack.count()):
|
||||
if self.main_tabs.stack.widget(i) is page:
|
||||
return i
|
||||
return 0
|
||||
|
||||
def _route_play_video(self, entry: dict) -> None:
|
||||
self.main_tabs.set_current_index(self._tab_index_of(self.watch_page))
|
||||
self.watch_page.play_entry(entry)
|
||||
|
||||
def _route_download_video(self, url: str) -> None:
|
||||
self.main_tabs.set_current_index(self._tab_index_of(self.download_page))
|
||||
self.url_input.setText(url)
|
||||
self.analyze_url()
|
||||
|
||||
def _route_open_channel(self, url: str) -> None:
|
||||
self.main_tabs.set_current_index(self._tab_index_of(self.browse_page))
|
||||
self.browse_page.open_url(url)
|
||||
|
||||
def _route_open_playlist(self, url: str) -> None:
|
||||
self.main_tabs.set_current_index(self._tab_index_of(self.browse_page))
|
||||
self.browse_page.open_url(url)
|
||||
|
||||
def _on_url_text_changed(self, text: str) -> None:
|
||||
"""Enable or disable the Analyze button based on URL input content."""
|
||||
self.analyze_button.setEnabled(bool(text.strip()))
|
||||
|
||||
Reference in New Issue
Block a user