2121 QStyle ,
2222 QStyledItemDelegate ,
2323 QToolButton ,
24+ QToolTip ,
2425 QVBoxLayout ,
2526 QWidget ,
2627)
@@ -48,6 +49,14 @@ def pinned_provider_ids() -> list[str]:
4849
4950
5051class _ProviderCardDelegate (QStyledItemDelegate ):
52+ hovered_pin = None
53+ pressed_pin = None
54+
55+ @staticmethod
56+ def pin_rect (rect ):
57+ # 绘制和命中检测共用 40px 热区,避免只有星形细线附近才能明确操作。
58+ return QRectF (rect ).adjusted (rect .width () - 44 , 6 , - 4 , - rect .height () + 46 )
59+
5160 def paint (self , painter , option , index ) -> None :
5261 provider_id , connected , pinned = index .data (Qt .ItemDataRole .UserRole )
5362 tokens = current_theme ()
@@ -66,14 +75,22 @@ def paint(self, painter, option, index) -> None:
6675 icon_rect .setSize (QSize (26 , 26 ))
6776 provider_icon (provider_id , 26 ).paint (painter , icon_rect )
6877 # 收藏采用矢量星形;品牌标记始终来自独立的品牌 SVG 资产。
69- center = QPointF (rect .right () - 19 , rect .top () + 22 )
78+ star_rect = self .pin_rect (option .rect )
79+ hovered_pin = self .hovered_pin == provider_id
80+ pressed_pin = hovered_pin and self .pressed_pin == provider_id
81+ if pinned or hovered_pin :
82+ painter .setPen (QPen (QColor (tokens .accent ), 1 ) if hovered_pin else Qt .PenStyle .NoPen )
83+ painter .setBrush (QColor (tokens .accent if pressed_pin else tokens .accent_soft ))
84+ painter .drawRoundedRect (star_rect .adjusted (3 , 3 , - 3 , - 3 ), 7 , 7 )
85+ center = star_rect .center ()
7086 points = QPolygonF ([
7187 QPointF (center .x () + (7 if n % 2 == 0 else 3.1 ) * cos (- pi / 2 + n * pi / 5 ),
7288 center .y () + (7 if n % 2 == 0 else 3.1 ) * sin (- pi / 2 + n * pi / 5 ))
7389 for n in range (10 )
7490 ])
75- painter .setPen (QPen (QColor (tokens .accent if pinned else tokens .subtext ), 1.1 ))
76- painter .setBrush (QColor (tokens .accent ) if pinned else Qt .BrushStyle .NoBrush )
91+ star_color = QColor (tokens .window if pressed_pin else tokens .accent if pinned or hovered_pin else tokens .subtext )
92+ painter .setPen (QPen (star_color , 1.4 ))
93+ painter .setBrush (star_color if pinned else Qt .BrushStyle .NoBrush )
7794 painter .drawPolygon (points )
7895 painter .setFont (option .font )
7996 painter .setPen (QColor (tokens .value ))
@@ -196,6 +213,9 @@ def _set_filter(self, selected: str) -> None:
196213 self ._populate ()
197214
198215 def _populate (self , * _args ) -> None :
216+ self .grid .itemDelegate ().hovered_pin = None
217+ self .grid .itemDelegate ().pressed_pin = None
218+ self ._grid_viewport .unsetCursor ()
199219 query = self .search .text ().strip ().casefold ()
200220 if query and self ._filter != "all" :
201221 # 搜索面向所有可接入平台,不能因默认的已配置筛选而隐藏用户正在找的品牌。
@@ -352,7 +372,10 @@ def _toggle_pin(self, item: QListWidgetItem) -> None:
352372 if self ._pins != pins :
353373 bind_text (self .hint , "常用平台保存失败,请检查数据目录" )
354374 return
355- bind_text (self .hint , "Enter 切换 · Ctrl+D 收藏 · Esc 关闭" )
375+ bind_text (self .hint , lambda : tr (
376+ "已收藏 {provider}" if provider_id in pins else "已取消收藏 {provider}" ,
377+ provider = provider_short_name (provider_id ),
378+ ))
356379 self .pins_changed .emit ()
357380
358381 def eventFilter (self , watched , event ) -> bool :
@@ -370,17 +393,40 @@ def eventFilter(self, watched, event) -> bool:
370393 if watched is self ._grid_viewport and event .type () == QEvent .Type .Resize :
371394 self .grid .doItemsLayout ()
372395 self ._position_remove_buttons ()
396+ if watched is self ._grid_viewport and event .type () == QEvent .Type .Leave :
397+ self .grid .itemDelegate ().hovered_pin = None
398+ self ._grid_viewport .unsetCursor ()
399+ self ._grid_viewport .update ()
373400 if watched is self ._grid_viewport and event .type () in (
401+ QEvent .Type .MouseMove , QEvent .Type .ToolTip ,
374402 QEvent .Type .MouseButtonPress , QEvent .Type .MouseButtonRelease ,
375- ) and event .button () == Qt .MouseButton .LeftButton :
376- item = self .grid .itemAt (event .position ().toPoint ())
377- if item is not None :
378- rect = self .grid .visualItemRect (item )
379- star = rect .adjusted (rect .width () - 44 , 5 , - 4 , - rect .height () + 44 )
380- if star .contains (event .position ().toPoint ()):
381- if event .type () == QEvent .Type .MouseButtonRelease :
403+ ):
404+ position = event .pos () if event .type () == QEvent .Type .ToolTip else event .position ().toPoint ()
405+ item = self .grid .itemAt (position )
406+ delegate = self .grid .itemDelegate ()
407+ over_pin = item is not None and delegate .pin_rect (self .grid .visualItemRect (item )).contains (position )
408+ provider_id = item .data (Qt .ItemDataRole .UserRole )[0 ] if over_pin else None
409+ delegate .hovered_pin = provider_id
410+ self ._grid_viewport .setCursor (Qt .CursorShape .PointingHandCursor if over_pin else Qt .CursorShape .ArrowCursor )
411+ self ._grid_viewport .update ()
412+ if event .type () == QEvent .Type .ToolTip and over_pin :
413+ label = "请先在全部平台中重新启用此平台。" if provider_id in self ._disabled else "取消收藏" if provider_id in self ._pins else "收藏"
414+ QToolTip .showText (event .globalPos (), tr (label ), self ._grid_viewport )
415+ return True
416+ if event .type () == QEvent .Type .MouseButtonPress and event .button () == Qt .MouseButton .LeftButton :
417+ delegate .pressed_pin = provider_id
418+ if over_pin :
419+ return True
420+ if event .type () == QEvent .Type .MouseButtonRelease and event .button () == Qt .MouseButton .LeftButton :
421+ pressed_pin = delegate .pressed_pin
422+ delegate .pressed_pin = None
423+ # 必须在同一收藏热区按下并松开;拖出按钮时取消,不能误收藏或切换平台。
424+ if pressed_pin is not None :
425+ if pressed_pin == provider_id :
382426 self ._toggle_pin (item )
383427 return True
428+ if over_pin :
429+ return True
384430 if event .type () == QEvent .Type .KeyPress :
385431 if (watched is self .grid or watched in self .remove_buttons .values ()) and event .key () == Qt .Key .Key_Delete :
386432 self ._remove_selected ()
0 commit comments