diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 9d174cdc2f..070cb6a821 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -503,6 +503,9 @@ def create_defs(): defs['bookshelf_height'] = 119 defs['bookshelf_make_space_for_second_line'] = False defs['bookshelf_emblem_position'] = 'auto' + defs['bookshelf_divider_text_right'] = False + defs['bookshelf_start_with_divider'] = False + defs['bookshelf_divider_style'] = 'text' # Migrate beta bookshelf_thumbnail if isinstance(btv := gprefs.get('bookshelf_thumbnail'), bool): diff --git a/src/calibre/gui2/library/bookshelf_view.py b/src/calibre/gui2/library/bookshelf_view.py index 1204fc4209..c038149c80 100644 --- a/src/calibre/gui2/library/bookshelf_view.py +++ b/src/calibre/gui2/library/bookshelf_view.py @@ -42,6 +42,7 @@ from qt.core import ( QMouseEvent, QObject, QPainter, + QPainterPath, QPaintEvent, QPalette, QParallelAnimationGroup, @@ -150,6 +151,10 @@ class WoodTheme(NamedTuple): inner_shadow_color: QColor cavity_color: QColor + # Divider colors + divider_color: QColor + divider_line_color: QColor + @classmethod def light_theme(cls) -> WoodTheme: # Light oak/pine colors for light mode @@ -182,6 +187,9 @@ class WoodTheme(NamedTuple): side_panel_dark=QColor(145, 105, 70), inner_shadow_color=QColor(60, 40, 25, 20), cavity_color=QColor(90, 60, 40), + + divider_color=QColor(250, 250, 250), + divider_line_color=QColor(74, 74, 106), ) @classmethod @@ -216,6 +224,9 @@ class WoodTheme(NamedTuple): side_panel_dark=QColor(38, 25, 18), inner_shadow_color=QColor(0, 0, 0, 30), cavity_color=QColor(20, 14, 10), + + divider_color=QColor(100, 100, 100), + divider_line_color=QColor(180, 180, 182), ) @@ -234,7 +245,9 @@ class RenderCase: def __init__(self): self.last_rendered_shelf_at = QRect(0, 0, 0, 0), False self.last_rendered_background_at = QRect(0, 0, 0, 0), False + self.last_rendered_divider_at = QRect(0, 0, 0, 0), False, 0 self.last_rendered_background = QPixmap() + self.last_rendered_divider = QPixmap() self.shelf_cache: dict[int, QPixmap] = {} self.back_panel_grain = tuple(self.generate_grain_lines(count=80, seed=42)) @@ -455,6 +468,26 @@ class RenderCase: end_gradient.setColorAt(1.0, self.theme.end_grain_dark) painter.fillRect(right_end, end_gradient) + def divider_as_pixmap(self, width: int, height: int, corner_radius: int = 0, offset: int = 0) -> QPixmap: + rect = QRect(0, 0, width, height + offset) + is_dark = is_dark_theme() + q = rect, is_dark, corner_radius + if self.last_rendered_divider_at == q: + return self.last_rendered_divider + self.last_rendered_divider_at = q + self.ensure_theme(is_dark) + ans = QImage(width, height + offset, QImage.Format_ARGB32_Premultiplied) + ans.fill(Qt.GlobalColor.transparent) + with QPainter(ans) as painter: + painter.setRenderHint(QPainter.RenderHint.Antialiasing) + path = QPainterPath() + path.addRoundedRect( + QRectF(0, 0, width, height + offset + corner_radius), corner_radius, corner_radius, + ) + painter.fillPath(path, self.theme.divider_color) + self.last_rendered_divider = p = QPixmap.fromImage(ans) + return p + class ImageWithDominantColor(QImage): @@ -1059,6 +1092,7 @@ class BookCase(QObject): if mdb is None or invalidate.is_set(): return db = mdb.new_api + start_with_divider = gprefs['bookshelf_start_with_divider'] spine_size_template = db.pref('bookshelf_spine_size_template', get_default_from_defaults=True) or '' if gprefs['bookshelf_make_space_for_second_line']: author_template = db.pref('bookshelf_author_template', get_default_from_defaults=True) or '' @@ -1099,6 +1133,8 @@ class BookCase(QObject): current_case_item = CaseItem(y=y, height=lc.spine_height, idx=len(self.items)) if case_end_divider: current_case_item.add_group_divider(case_end_divider, lc) + elif start_with_divider: + current_case_item.add_group_divider(group_name, lc) current_case_item.add_book(book_id, spine_width, group_name, lc) book_id_to_item_map[book_id] = current_case_item.items[-1] book_id_visual_order_map[book_id] = len(book_id_visual_order_map) @@ -1839,24 +1875,44 @@ class BookshelfView(MomentumScrollMixin, QAbstractScrollArea): second_line = fm.elidedText(second_line, Qt.TextElideMode.ElideRight, width) return first_line, second_line, font - def divider_color(self) -> QColor: - return QColor('#b4b4b6' if is_dark_theme() else '#4a4a6a') - def draw_inline_divider(self, painter: QPainter, divider: ShelfItem, scroll_y: int): '''Draw an inline group divider with it group name write vertically and a gradient line.''' lc = self.layout_constraints rect = divider.rect(lc).translated(0, -scroll_y) divider_rect = QRect(-rect.height() // 2, -rect.width() // 2, rect.height(), rect.width()) + text_right = gprefs['bookshelf_divider_text_right'] + + def draw_rounded_divider(corner_radius: int, offset: int): + p = self.case_renderer.divider_as_pixmap(rect.width(), rect.height(), corner_radius, offset) + painter.drawPixmap(rect.adjusted(0, -offset, 0, 0), p) + + match gprefs['bookshelf_divider_style']: + case 'block': + painter.fillRect(rect, self.case_renderer.theme.divider_color) + case 'round_top': + radius = rect.width() // 2 + offset = radius // 4 + draw_rounded_divider(radius, offset) + case 'rounded_corner': + radius = rect.width() // 4 + offset = radius // 3 + draw_rounded_divider(radius, offset) # Bottom margin - text_rect = divider_rect.adjusted(self.TEXT_MARGIN, 0, 0, 0) + text_rect = divider_rect.adjusted( + 0 if text_right else self.TEXT_MARGIN, + 0, + -self.TEXT_MARGIN if text_right else 0, + 0, + ) elided_text, _, font = self.get_text_metrics(divider.group_name, '', text_rect.size(), bold=True) painter.save() painter.setFont(font) painter.setPen(self.palette().color(QPalette.ColorRole.WindowText)) painter.translate(rect.left() + rect.width() // 2, rect.top() + rect.height() // 2) painter.rotate(90 if gprefs['bookshelf_up_to_down'] else -90) - sized_rect = painter.drawText(text_rect, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, elided_text) + alignment = Qt.AlignmentFlag.AlignRight if text_right else Qt.AlignmentFlag.AlignLeft + sized_rect = painter.drawText(text_rect, alignment | Qt.AlignmentFlag.AlignVCenter, elided_text) # Calculate line dimensions line_rect = text_rect.adjusted(sized_rect.width(), 0, 0, 0) overflow = (line_rect.height() - self.DIVIDER_LINE_WIDTH) // 2 @@ -1864,7 +1920,9 @@ class BookshelfView(MomentumScrollMixin, QAbstractScrollArea): # Draw vertical gradient line if long enough if line_rect.width() > 8: - color1 = self.divider_color().toRgb() + if text_right: + line_rect.translate(-sized_rect.width(), 0) + color1 = self.case_renderer.theme.divider_line_color.toRgb() color2 = color1.toRgb() color1.setAlphaF(0.0) # Transparent at top/bottom color2.setAlphaF(0.75) # Visible in middle diff --git a/src/calibre/gui2/preferences/look_feel_tabs/bookshelf_view.py b/src/calibre/gui2/preferences/look_feel_tabs/bookshelf_view.py index c0b5055d9c..bd3bc1bbde 100644 --- a/src/calibre/gui2/preferences/look_feel_tabs/bookshelf_view.py +++ b/src/calibre/gui2/preferences/look_feel_tabs/bookshelf_view.py @@ -79,6 +79,15 @@ class BookshelfTab(QTabWidget, LazyConfigWidgetBase, Ui_Form): r('bookshelf_height', gprefs) r('bookshelf_make_space_for_second_line', gprefs) + r('bookshelf_divider_text_right', gprefs) + r('bookshelf_start_with_divider', gprefs) + r('bookshelf_divider_style', gprefs, choices=[ + (_('Simple text'), 'text'), + (_('Block'), 'block'), + (_('Rounded corners'), 'rounded_corner'), + (_('Round top'), 'round_top'), + ]) + r('bookshelf_thumbnail_opacity', gprefs) r('bookshelf_thumbnail', gprefs, choices=[ (_('Full'), 'full'), diff --git a/src/calibre/gui2/preferences/look_feel_tabs/bookshelf_view.ui b/src/calibre/gui2/preferences/look_feel_tabs/bookshelf_view.ui index 19cdf7fa5b..c61be4bf21 100644 --- a/src/calibre/gui2/preferences/look_feel_tabs/bookshelf_view.ui +++ b/src/calibre/gui2/preferences/look_feel_tabs/bookshelf_view.ui @@ -227,7 +227,7 @@ - Duration of hover &animation + Duration of hover &animation: opt_bookshelf_fade_time @@ -281,7 +281,48 @@ - + + + + Groups divider &style: + + + opt_bookshelf_divider_style + + + + + + + Control the style to use for the groups divider + + + + + + + + + Control whether the text of the groups divider aligned on the right or left + + + Groups divider text &aligned to the right + + + + + + + When groupins the books and that a group extends on more than one shelf, place a group divider at the beginning of the additional shelves + + + &Start shelves with a group divider + + + + + + @@ -315,7 +356,7 @@ - + Qt::Vertical