Skip to content

Commit

Permalink
refine code n/n
Browse files Browse the repository at this point in the history
  • Loading branch information
cosven committed Dec 23, 2023
1 parent 95ed687 commit 30cbf34
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 101 deletions.
20 changes: 19 additions & 1 deletion feeluown/gui/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@
import random
import sys
import logging
from contextlib import contextmanager
from typing import TypeVar, List, Optional, Generic, Union, cast, TYPE_CHECKING

try:
# helper module should work in no-window mode
from PyQt5.QtCore import QModelIndex, QSize, Qt, pyqtSignal, QSortFilterProxyModel, \
QAbstractListModel
from PyQt5.QtGui import QPalette, QFontMetrics, QColor
from PyQt5.QtGui import QPalette, QFontMetrics, QColor, QPainter
from PyQt5.QtWidgets import QApplication, QScrollArea, QWidget
except ImportError:
pass
Expand Down Expand Up @@ -557,6 +558,23 @@ def random_solarized_color():
return QColor(random.choice(list(SOLARIZED_COLORS.values())))


@contextmanager
def painter_save(painter: QPainter):
painter.save()
yield
painter.restore()


def secondary_text_color(palette: QPalette):
text_color: QColor = palette.color(QPalette.Text)
if text_color.lightness() > 150:
non_text_color = text_color.darker(140)
else:
non_text_color = text_color.lighter(150)
non_text_color.setAlpha(100)
return non_text_color


# https://ethanschoonover.com/solarized/
SOLARIZED_COLORS = {
'yellow': '#b58900',
Expand Down
8 changes: 5 additions & 3 deletions feeluown/gui/page_containers/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@
from feeluown.gui.widgets.img_card_list import ImgCardListView
from feeluown.gui.widgets.img_card_list import (
AlbumCardListModel, AlbumCardListView, AlbumFilterProxyModel, AlbumCardListDelegate,
ArtistCardListModel, ArtistCardListView, ArtistFilterProxyModel, ArtistCardListDelegate,
ArtistCardListModel, ArtistCardListView, ArtistFilterProxyModel,
VideoCardListModel, VideoCardListView, VideoFilterProxyModel, VideoCardListDelegate,
PlaylistCardListModel, PlaylistCardListView, PlaylistFilterProxyModel, PlaylistCardListDelegate,
PlaylistCardListModel, PlaylistCardListView, PlaylistFilterProxyModel,
PlaylistCardListDelegate, ArtistCardListDelegate,
)
from feeluown.gui.widgets.songs import ColumnsMode, SongsTableModel, SongsTableView, \
SongFilterProxyModel
Expand Down Expand Up @@ -263,7 +264,8 @@ def __init__(self, app, parent=None):
self.videos_table = VideoCardListView(parent=self)
self.videos_table.setItemDelegate(VideoCardListDelegate(self.videos_table))
self.playlists_table = PlaylistCardListView(parent=self)
self.playlists_table.setItemDelegate(PlaylistCardListDelegate(self.playlists_table))
self.playlists_table.setItemDelegate(
PlaylistCardListDelegate(self.playlists_table))
self.comments_table = CommentListView(parent=self)
self.desc_widget = DescLabel(parent=self)

Expand Down
5 changes: 3 additions & 2 deletions feeluown/gui/pages/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,10 @@ async def render(req, **kwargs): # pylint: disable=too-many-locals,too-many-bra
# HACK: set fixed row for tables.
# pylint: disable=protected-access
for table in table_container._tables:
if isinstance(table.itemDelegate(), ImgCardListDelegate):
delegate = table.itemDelegate()
if isinstance(delegate, ImgCardListDelegate):
table._fixed_row_count = 2
table.itemDelegate().img_min_width = 100
delegate.update_settings("card_min_width", 100)
elif isinstance(table, SongsTableView):
table._fixed_row_count = 8
table._row_height = table.verticalHeader().defaultSectionSize()
Expand Down
200 changes: 105 additions & 95 deletions feeluown/gui/widgets/img_card_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@
# pylint: disable=unused-argument
import logging
import random
from typing import TypeVar, Optional, List, cast
from typing import TypeVar, Optional, List, cast, Union

from PyQt5.QtCore import (
QAbstractListModel, QModelIndex, Qt, QObject, QEvent,
QRectF, QRect, QSize, QSortFilterProxyModel, pyqtSignal
)
from PyQt5.QtGui import (
QImage, QColor, QResizeEvent,
QBrush, QPainter, QTextOption, QFontMetrics, QPalette
QBrush, QPainter, QTextOption, QFontMetrics
)
from PyQt5.QtWidgets import (
QAbstractItemDelegate, QListView, QFrame,
Expand All @@ -31,7 +31,8 @@
from feeluown.utils.reader import wrap
from feeluown.models.uri import reverse
from feeluown.gui.helpers import (
ItemViewNoScrollMixin, resize_font, ReaderFetchMoreMixin,
ItemViewNoScrollMixin, resize_font, ReaderFetchMoreMixin, painter_save,
secondary_text_color
)

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -128,8 +129,20 @@ def data(self, index, role):


class ImgCardListDelegate(QAbstractItemDelegate):
"""
Card layout should be like the following::
|card0 card1 card2|
<- vertical spacing
|card3 card4 card5|
<- vertical spacing
The leftmost cards should have a half_h_spacing on the right side,
and the rightmost cards should have a half_h_spacing on the left side.
Middle cards should have a half_h_spacing on both sides.
"""
def __init__(self, parent=None,
img_min_width=150, img_spacing=20, img_text_height=40,
card_min_width=150, card_spacing=20, card_text_height=40,
**_):
super().__init__(parent)

Expand All @@ -138,90 +151,63 @@ def __init__(self, parent=None,
self.as_circle = True
self.w_h_ratio = 1.0

self.img_min_width = img_min_width
self.img_spacing = img_spacing
self.img_text_height = img_text_height
self.card_min_width = card_min_width
self.card_spacing = card_spacing
self.card_text_height = card_text_height

self.spacing = self.img_spacing
self.half_spacing = self.spacing // 2
self.text_height = self.img_text_height
self.h_spacing = self.card_spacing
self.v_spacing = self.half_h_spacing = self.h_spacing // 2
self.text_height = self.card_text_height

self._img_width = self._img_height = 0
# These variables are calculated in on_view_resized().
self._card_width = self._card_height = 0
self._view_width = 0

def column_count(self):
return (self._view_width + self.img_spacing) // (self._img_width + self.img_spacing)

def is_left_first(self, index):
if self.view.isWrapping():
return index.row() % self.column_count() == 0
return index.row() == 0

def is_right_last(self, index):
if self.view.isWrapping():
column_count = self.column_count()
return index.row() % column_count == column_count - 1
return False # FIXME: implement this

def get_spacing(self, index):
if self.is_left_first(index) or self.is_right_last(index):
return self.half_spacing
return self.spacing
def update_settings(self, name, value):
assert hasattr(self, name), f"no such setting: {name}"
setattr(self, name, value)
self.re_calc_all()
self.view.update()

def paint(self, painter, option, index):
painter.save()
painter.setRenderHint(QPainter.Antialiasing)

rect = option.rect
painter.translate(rect.x(), rect.y())
if not self.is_left_first(index):
painter.translate(self.half_spacing, 0)

obj = index.data(Qt.DecorationRole)
obj: Optional[Union[QImage, QColor]] = index.data(Qt.DecorationRole)
if obj is None:
painter.restore()
return

text_title_height = 30
text_source_height = self.text_height - text_title_height
text_source_color = non_text_color = self.get_non_text_color(option)
spacing = self.get_spacing(index)
draw_width = rect.width() - spacing

# Draw cover or color.
cover_height = rect.height() - self.text_height - self.spacing
painter.save()
self.draw_cover_or_color(painter, non_text_color, obj, draw_width, cover_height)
painter.restore()

# Draw text(album name / artist name / playlist name).
painter.translate(0, cover_height)
text_rect = QRectF(0, 0, draw_width, text_title_height)
painter.save()
self.draw_title(painter, index, text_rect)
painter.restore()

# Draw source.
painter.save()
painter.translate(0, text_title_height - 5)
whats_this_rect = QRectF(0, 0, draw_width, text_source_height + 5)
self.draw_whats_this(painter, index, text_source_color, whats_this_rect)
painter.restore()

painter.restore()

def get_non_text_color(self, option):
text_color = option.palette.color(QPalette.Text)
if text_color.lightness() > 150:
non_text_color = text_color.darker(140)
else:
non_text_color = text_color.lighter(150)
non_text_color.setAlpha(100)
return non_text_color

def draw_cover_or_color(self, painter, non_text_color, obj, draw_width, height):
with painter_save(painter):
painter.setRenderHint(QPainter.Antialiasing)
painter.translate(option.rect.x(), option.rect.y())

if not self.is_leftmost(index):
painter.translate(self.half_h_spacing, 0)

spacing = self.get_card_h_spacing(index)
draw_width = option.rect.width() - spacing

secondary_color = border_color = secondary_text_color(option.palette)
# Draw cover or color.
img_height = int(draw_width * self.w_h_ratio)
with painter_save(painter):
self.draw_img_or_color(
painter, border_color, obj, draw_width, img_height)

# Draw text(album name / artist name / playlist name), and draw source.
text_title_height = 30
text_source_height = self.text_height - text_title_height
painter.translate(0, img_height)
text_rect = QRectF(0, 0, draw_width, text_title_height)
with painter_save(painter):
self.draw_title(painter, index, text_rect)
painter.translate(0, text_title_height - 5)
with painter_save(painter):
self.draw_whats_this(painter,
index,
secondary_color,
QRectF(0, 0, draw_width, text_source_height + 5))

def draw_img_or_color(self, painter, border_color, obj, draw_width, height):
pen = painter.pen()
pen.setColor(non_text_color)
pen.setColor(border_color)
painter.setPen(pen)
if isinstance(obj, QColor):
color = obj
Expand Down Expand Up @@ -267,37 +253,61 @@ def draw_whats_this(self, painter, index, non_text_color, whats_this_rect):
painter.drawText(whats_this_rect, whats_this, source_option)

def sizeHint(self, option, index):
spacing = self.get_spacing(index)
width = self._img_width
spacing = self.get_card_h_spacing(index)
width = self._card_width
if index.isValid():
height = int(width / self.w_h_ratio) + self.text_height
return QSize(width + spacing, height + self.spacing)
return QSize(width + spacing, height + self.v_spacing)
return super().sizeHint(option, index)

def on_view_resized(self, size: QSize, old_size: QSize):
def on_view_resized(self, size: QSize, _: QSize):
self._view_width = size.width()
self._img_width, self._img_height = self.re_calc_img_size()
self.view._row_height = self._img_height
self.re_calc_all()

def re_calc_img_size(self):
def re_calc_all(self):
# HELP: CardListView needs about 20 spacing left on macOS
width = max(0, self._view_width - 20)
img_spacing = self.img_spacing
img_min_width = self.img_min_width
img_text_height = self.img_text_height
card_spacing = self.card_spacing

# according to our algorithm, when the widget width is:
# 2(img_min_width + img_spacing) + img_spacing - 1,
# 2(card_min_width + card_spacing) + card_spacing - 1,
# the cover width can take the maximum width, it will be:
# CoverMaxWidth = 2 * img_min_width + img_spacing - 1
# CoverMaxWidth = 2 * card_min_width + card_spacing - 1

# calculate max column count
count = (width + img_spacing) // (img_min_width + img_spacing)
count = (width + card_spacing) // (self.card_min_width + card_spacing)
count = max(count, 1)
# calculate img_width when column count is the max
img_height = img_width = (width + img_spacing) // count - img_spacing
img_height = img_height + img_text_height
return img_width, img_height
self._card_width = (width + card_spacing) // count - card_spacing
self._card_height = int(self._card_width * self.w_h_ratio) + self.text_height
self.view._row_height = self._card_height + self.v_spacing

def column_count(self):
return (self._view_width + self.card_spacing) // \
(self._card_width + self.card_spacing)

def which_column(self, index: QModelIndex):
if not self.view.isWrapping():
return index.row()
return index.row() % self.column_count()

def which_row(self, index: QModelIndex):
if not self.view.isWrapping():
return 0
return index.row() // self.column_count()

def is_leftmost(self, index):
return self.which_column(index) == 0

def is_rightmost(self, index):
if self.view.isWrapping():
return self.which_column(index) == self.column_count() - 1
return False # HELP: no way to check if it is the rightmost.

def get_card_h_spacing(self, index):
if self.is_leftmost(index) or self.is_rightmost(index):
return self.half_h_spacing
return self.h_spacing

def eventFilter(self, _: QObject, event: QEvent):
if event.type() == QEvent.Resize:
Expand Down Expand Up @@ -331,7 +341,7 @@ def filterAcceptsRow(self, source_row, source_parent):
class ImgCardListView(ItemViewNoScrollMixin, QListView):
"""
.. versionchanged:: 3.9
The *img_min_width*, *img_spacing*, *img_text_height* parameter were removed.
The *card_min_width*, *card_spacing*, *card_text_height* parameter were removed.
"""
def __init__(self, parent=None, **kwargs):
super().__init__(parent=parent, **kwargs)
Expand Down

0 comments on commit 30cbf34

Please sign in to comment.