def __init__(self, app, parent=None): super().__init__(parent) self._app = app self._pixmap = None self._layout = QVBoxLayout(self) self.scrollarea = ScrollArea(self._app, self) self.table_container = self.scrollarea.t # TODO: collection container will be removed self.collection_container = CollectionContainer(self._app, self) self.bottom_panel = BottomPanel(app, self) self._setup_ui()
def __init__(self, app): self._app = app self._layout = QVBoxLayout(app) self._top_separator = Separator(parent=app) self._bottom_separator = Separator(parent=app) self._splitter = QSplitter(app) # NOTE: 以位置命名的部件应该只用来组织界面布局,不要 # 给其添加任何功能性的函数 self.top_panel = TopPanel(app, app) self.bottom_panel = BottomPanel(app, app) self._left_panel_con = LeftPanel(self._app,) self.left_panel = self._left_panel_con.p self.right_panel = RightPanel(self._app, self._splitter) self.mpv_widget = MpvOpenGLWidget(self._app) self.mpv_widget.hide() # alias self.magicbox = self.bottom_panel.magicbox self.pc_panel = self.top_panel.pc_panel self.table_container = self.right_panel.table_container # backward compatible, old name is songs_table_container self.songs_table_container = self.table_container self.songs_table = self.table_container.songs_table self.back_btn = self.bottom_panel.back_btn self.forward_btn = self.bottom_panel.forward_btn self.toggle_video_btn = self.pc_panel.toggle_video_btn self.pc_panel.playlist_btn.clicked.connect(self.show_player_playlist) self.pc_panel.mv_btn.clicked.connect(self._play_mv) self.toggle_video_btn.clicked.connect(self._toggle_video_widget) self._app.player.video_format_changed.connect( self.on_video_format_changed, aioqueue=True) self._app.hotkey_mgr.registe( [QKeySequence('Ctrl+F'), QKeySequence(':'), QKeySequence('Alt+x')], self.magicbox.setFocus ) self._setup_ui()
class Ui: def __init__(self, app): self._app = app self._layout = QVBoxLayout(app) self._top_separator = Separator(parent=app) self._bottom_separator = Separator(parent=app) self._splitter = QSplitter(app) # NOTE: 以位置命名的部件应该只用来组织界面布局,不要 # 给其添加任何功能性的函数 self.top_panel = TopPanel(app, app) self.bottom_panel = BottomPanel(app, app) self._left_panel_con = LeftPanel(self._app,) self.left_panel = self._left_panel_con.p self.right_panel = RightPanel(self._app, self._splitter) self.mpv_widget = MpvOpenGLWidget(self._app) self.mpv_widget.hide() # alias self.magicbox = self.bottom_panel.magicbox self.pc_panel = self.top_panel.pc_panel self.table_container = self.right_panel.table_container # backward compatible, old name is songs_table_container self.songs_table_container = self.table_container self.songs_table = self.table_container.songs_table self.back_btn = self.bottom_panel.back_btn self.forward_btn = self.bottom_panel.forward_btn self.toggle_video_btn = self.pc_panel.toggle_video_btn self.pc_panel.playlist_btn.clicked.connect(self.show_player_playlist) self.pc_panel.mv_btn.clicked.connect(self._play_mv) self.toggle_video_btn.clicked.connect(self._toggle_video_widget) self._app.player.video_format_changed.connect( self.on_video_format_changed, aioqueue=True) self._app.hotkey_mgr.registe( [QKeySequence('Ctrl+F'), QKeySequence(':'), QKeySequence('Alt+x')], self.magicbox.setFocus ) self._setup_ui() def _setup_ui(self): self._app.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self._splitter.setHandleWidth(0) self._splitter.addWidget(self._left_panel_con) self._splitter.addWidget(self.right_panel) self.right_panel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self._left_panel_con.setMinimumWidth(200) self._layout.addWidget(self.bottom_panel) self._layout.addWidget(self._bottom_separator) self._layout.addWidget(self._splitter) self._layout.addWidget(self.mpv_widget) self._layout.addWidget(self._top_separator) self._layout.addWidget(self.top_panel) self._layout.setSpacing(0) self._layout.setContentsMargins(0, 0, 0, 0) self.top_panel.layout().setSpacing(0) self.top_panel.layout().setContentsMargins(0, 0, 0, 0) def _play_mv(self): song = self._app.player.current_song mv = song.mv if song else None if mv is not None: if mv.meta.support_multi_quality: media, _ = mv.select_media() else: media = mv.media self._app.player.play(media) self.show_video_widget() def show_player_playlist(self): self.table_container.show_player_playlist() def on_video_format_changed(self, vformat): if vformat is None: self.hide_video_widget() self.toggle_video_btn.hide() else: self.toggle_video_btn.show() def _toggle_video_widget(self): if self.mpv_widget.isVisible(): self.hide_video_widget() else: self.show_video_widget() def hide_video_widget(self): self.mpv_widget.hide() self._splitter.show() self.bottom_panel.show() self._bottom_separator.show() self.pc_panel.toggle_video_btn.setText('△') def show_video_widget(self): self.bottom_panel.hide() self._bottom_separator.hide() self._splitter.hide() self.mpv_widget.show() self.pc_panel.toggle_video_btn.setText('▽')
class RightPanel(QFrame): def __init__(self, app, parent=None): super().__init__(parent) self._app = app self._pixmap = None self._layout = QVBoxLayout(self) self.scrollarea = ScrollArea(self._app, self) self.table_container = self.scrollarea.t # TODO: collection container will be removed self.collection_container = CollectionContainer(self._app, self) self.bottom_panel = BottomPanel(app, self) self._setup_ui() def _setup_ui(self): self.scrollarea.setMinimumHeight(100) self.collection_container.hide() self._layout.addWidget(self.bottom_panel) self._layout.addWidget(self.scrollarea) self._layout.addWidget(self.collection_container) self._layout.setContentsMargins(0, 0, 0, 0) self._layout.setSpacing(0) def show_model(self, model): self.scrollarea.show() self.collection_container.hide() # TODO: use PreemptiveTask aio.create_task(self.table_container.show_model(model)) def show_songs(self, songs): self.collection_container.hide() self.scrollarea.show() self.table_container.show_songs(songs) def show_collection(self, coll): def _show_pure_albums_coll(coll): self.collection_container.hide() reader = RandomSequentialReader.from_list(coll.models) self.table_container.show_albums_coll(reader) def _show_pure_songs_coll(coll): self.collection_container.hide() self.scrollarea.show() self.table_container.show_collection(coll) def _show_mixed_coll(coll): self.scrollarea.hide() self.collection_container.show() self.collection_container.show_collection(coll) def _show_pure_videos_coll(coll): from feeluown.containers.table import VideosRenderer self.collection_container.hide() self.scrollarea.show() reader = RandomSequentialReader.from_list(coll.models) renderer = VideosRenderer(reader) aio.create_task(self.table_container.set_renderer(renderer)) if coll.name == DEFAULT_COLL_ALBUMS: _show_pure_albums_coll(coll) return types = set() for model in coll.models: types.add(model.meta.model_type) if len(types) >= 2: break if len(types) == 1: type_ = types.pop() if type_ == ModelType.song: _show_pure_songs_coll(coll) elif type_ == ModelType.album: _show_pure_albums_coll(coll) elif type_ == ModelType.video: _show_pure_videos_coll(coll) else: _show_mixed_coll(coll) else: _show_mixed_coll(coll) def show_background_image(self, pixmap): self._pixmap = pixmap if pixmap is None: self.table_container.meta_widget.setMinimumHeight(0) else: height = (self._app.height() - self.bottom_panel.height() - self.table_container.toolbar.height()) // 2 self.table_container.meta_widget.setMinimumHeight(height) self.update() def paintEvent(self, e): """ draw pixmap as a the background with a dark overlay HELP: currently, this cost much CPU """ painter = QPainter(self) painter.setPen(Qt.NoPen) painter.setRenderHint(QPainter.Antialiasing) painter.setRenderHint(QPainter.SmoothPixmapTransform) # calculate available size draw_width = self.width() draw_height = 10 # spacing defined in table container draw_height += self.bottom_panel.height() if self.table_container.meta_widget.isVisible(): draw_height += self.table_container.meta_widget.height() extra = self.table_container.current_extra if extra is not None and extra.isVisible(): draw_height += extra.height() if self.table_container.toolbar.isVisible(): draw_height += self.table_container.toolbar.height() scrolled = self.scrollarea.verticalScrollBar().value() max_scroll_height = draw_height - self.bottom_panel.height() if scrolled >= max_scroll_height: painter.save() painter.setBrush(self.palette().brush(QPalette.Window)) painter.drawRect(self.bottom_panel.rect()) painter.restore() return if self._pixmap is not None: self._draw_pixmap(painter, draw_width, draw_height, scrolled) self._draw_pixmap_overlay(painter, draw_width, draw_height, scrolled) else: # draw gradient for widgets(bottom panel + meta_widget + ...) above table self._draw_overlay(painter, draw_width, draw_height, scrolled) # if scrolled height > 30, draw background to seperate bottom_panel and body if scrolled >= 30: painter.save() painter.setBrush(self.palette().brush(QPalette.Window)) painter.drawRect(self.bottom_panel.rect()) painter.restore() return # since the body's background color is palette(base), we use # the color to draw background for remain empty area painter.save() painter.setBrush(self.palette().brush(QPalette.Base)) painter.drawRect(0, draw_height, draw_width, self.height() - draw_height) painter.restore() painter.end() def _draw_pixmap_overlay(self, painter, draw_width, draw_height, scrolled): painter.save() rect = QRect(0, 0, draw_width, draw_height) painter.translate(0, -scrolled) gradient = QLinearGradient(rect.topLeft(), rect.bottomLeft()) color = self.palette().color(QPalette.Base) if draw_height == self.height(): gradient.setColorAt(0, add_alpha(color, 180)) gradient.setColorAt(1, add_alpha(color, 230)) else: if self._app.theme_mgr.theme == Light: gradient.setColorAt(0, add_alpha(color, 220)) gradient.setColorAt(0.1, add_alpha(color, 180)) gradient.setColorAt(0.2, add_alpha(color, 140)) gradient.setColorAt(0.6, add_alpha(color, 140)) gradient.setColorAt(0.8, add_alpha(color, 200)) gradient.setColorAt(0.9, add_alpha(color, 240)) gradient.setColorAt(1, color) else: gradient.setColorAt(0, add_alpha(color, 50)) gradient.setColorAt(0.6, add_alpha(color, 100)) gradient.setColorAt(0.8, add_alpha(color, 200)) gradient.setColorAt(0.9, add_alpha(color, 240)) gradient.setColorAt(1, color) painter.setBrush(gradient) painter.drawRect(rect) painter.restore() def _draw_overlay(self, painter, draw_width, draw_height, scrolled): painter.save() rect = QRect(0, 0, draw_width, draw_height) painter.translate(0, -scrolled) gradient = QLinearGradient(rect.topLeft(), rect.bottomLeft()) gradient.setColorAt(0, self.palette().color(QPalette.Window)) gradient.setColorAt(1, self.palette().color(QPalette.Base)) painter.setBrush(gradient) painter.drawRect(rect) painter.restore() def _draw_pixmap(self, painter, draw_width, draw_height, scrolled): # scale pixmap scaled_pixmap = self._pixmap.scaledToWidth( draw_width, mode=Qt.SmoothTransformation) pixmap_size = scaled_pixmap.size() # draw the center part of the pixmap on available rect painter.save() brush = QBrush(scaled_pixmap) painter.setBrush(brush) # note: in practice, most of the time, we can't show the # whole artist pixmap, as a result, the artist head will be cut, # which causes bad visual effect. So we render the top-center part # of the pixmap here. y = (pixmap_size.height() - draw_height) // 3 painter.translate(0, -y - scrolled) rect = QRect(0, y, draw_width, draw_height) painter.drawRect(rect) painter.restore() def sizeHint(self): size = super().sizeHint() return QSize(760, size.height())
class RightPanel(QFrame): def __init__(self, app, parent=None): super().__init__(parent) self._app = app self._pixmap = None self._layout = QVBoxLayout(self) self.scrollarea = ScrollArea(self._app, self) self.table_container = self.scrollarea.t # TODO: collection container will be removed self.collection_container = CollectionContainer(self._app, self) self.bottom_panel = BottomPanel(app, self) self._setup_ui() def _setup_ui(self): self.scrollarea.setMinimumHeight(100) self.collection_container.hide() self._layout.addWidget(self.bottom_panel) self._layout.addWidget(self.scrollarea) self._layout.addWidget(self.collection_container) self._layout.setContentsMargins(0, 0, 0, 0) self._layout.setSpacing(0) def show_model(self, model): self.scrollarea.show() self.collection_container.hide() # TODO: use PreemptiveTask aio.create_task(self.table_container.show_model(model)) def show_songs(self, songs): self.collection_container.hide() self.scrollarea.show() self.table_container.show_songs(songs) def show_collection(self, coll): pure_songs = True for model in coll.models: if model.meta.model_type != ModelType.song: pure_songs = False break if coll.name == DEFAULT_COLL_ALBUMS: self.collection_container.hide() reader = RandomSequentialReader.from_list(coll.models) self.table_container.show_albums_coll(reader) return if pure_songs: self.collection_container.hide() self.scrollarea.show() self.table_container.show_collection(coll) else: self.scrollarea.hide() self.collection_container.show() self.collection_container.show_collection(coll) def show_background_image(self, pixmap): self._pixmap = pixmap if pixmap is None: self.table_container.meta_widget.setMinimumHeight(0) else: height = (self._app.height() - self.bottom_panel.height() - self.table_container.toolbar.height()) // 2 self.table_container.meta_widget.setMinimumHeight(height) self.update() def paintEvent(self, e): """ draw pixmap as a the background with a dark overlay HELP: currently, this cost much CPU """ if self._pixmap is None: return painter = QPainter(self) painter.setPen(Qt.NoPen) painter.setRenderHint(QPainter.Antialiasing) painter.setRenderHint(QPainter.SmoothPixmapTransform) # calculate available size draw_width = self.width() draw_height = self.bottom_panel.height() if self.table_container.meta_widget.isVisible(): draw_height += self.table_container.meta_widget.height() if self.table_container.toolbar.isVisible(): draw_height += self.table_container.toolbar.height() scrolled = self.scrollarea.verticalScrollBar().value() max_scroll_height = draw_height - self.bottom_panel.height() if scrolled >= max_scroll_height: painter.save() painter.setBrush(self.palette().brush(QPalette.Window)) painter.drawRect(self.rect()) painter.restore() return # scale pixmap scaled_pixmap = self._pixmap.scaledToWidth( draw_width, mode=Qt.SmoothTransformation) pixmap_size = scaled_pixmap.size() # draw the center part of the pixmap on available rect painter.save() brush = QBrush(scaled_pixmap) painter.setBrush(brush) # note: in practice, most of the time, we can't show the # whole artist pixmap, as a result, the artist head will be cut, # which causes bad visual effect. So we render the top-center part # of the pixmap here. y = (pixmap_size.height() - draw_height) // 3 painter.translate(0, -y - scrolled) rect = QRect(0, y, draw_width, draw_height) painter.drawRect(rect) # draw overlay gradient = QLinearGradient(rect.topLeft(), rect.bottomLeft()) color = self.palette().color(QPalette.Base) if draw_height == self.height(): gradient.setColorAt(0, add_alpha(color, 180)) gradient.setColorAt(1, add_alpha(color, 230)) else: gradient.setColorAt(0, add_alpha(color, 50)) gradient.setColorAt(0.6, add_alpha(color, 100)) gradient.setColorAt(0.8, add_alpha(color, 200)) gradient.setColorAt(0.9, add_alpha(color, 240)) gradient.setColorAt(1, color) painter.setBrush(gradient) painter.drawRect(rect) painter.restore() painter.end() def sizeHint(self): size = super().sizeHint() return QSize(760, size.height())