优化更新
This commit is contained in:
+70
-24
@@ -7,13 +7,36 @@ from PyQt6.QtGui import (
|
||||
QDrag, QPixmap, QPainter, QColor, QIcon, QPen, QKeySequence,
|
||||
QFont, QFontMetrics, QTextLayout, QTextOption,
|
||||
)
|
||||
from PyQt6.QtCore import Qt, QMimeData, QByteArray, QSize, QPoint, QFileInfo, QPropertyAnimation, QEasingCurve
|
||||
from PyQt6.QtCore import Qt, QMimeData, QByteArray, QSize, QPoint, QFileInfo
|
||||
from db import database
|
||||
from ui.flow_layout import ITEM_W
|
||||
import ui.theme as theme
|
||||
import ui.dialog_style as dialog_style
|
||||
import shortcut_target
|
||||
|
||||
_icon_provider = QFileIconProvider()
|
||||
# 图标缓存:同一文件路径在刷新/重建条目时避免重复读磁盘提取图标
|
||||
_icon_cache: dict[str, QIcon] = {}
|
||||
_ICON_CACHE_MAX = 512
|
||||
|
||||
|
||||
def _cached_icon(key: str, loader) -> QIcon:
|
||||
icon = _icon_cache.get(key)
|
||||
if icon is None:
|
||||
icon = loader()
|
||||
if len(_icon_cache) >= _ICON_CACHE_MAX:
|
||||
_icon_cache.clear()
|
||||
_icon_cache[key] = icon
|
||||
return icon
|
||||
|
||||
|
||||
def get_icon_width() -> int:
|
||||
"""设置中的图标宽度(限制在 48~120),ItemWidget 尺寸以此为准。"""
|
||||
try:
|
||||
w = int(database.get_setting("icon_width", str(ITEM_W)))
|
||||
except Exception:
|
||||
w = ITEM_W
|
||||
return max(48, min(120, w))
|
||||
|
||||
|
||||
def _wrapped_line_count(text: str, font: QFont, width_px: int) -> int:
|
||||
@@ -56,21 +79,26 @@ def two_line_display_text(text: str, font: QFont, width_px: int) -> str:
|
||||
|
||||
|
||||
def extract_icon(path: str) -> QIcon:
|
||||
try:
|
||||
icon = _icon_provider.icon(QFileInfo(path))
|
||||
if not icon.isNull():
|
||||
px = icon.pixmap(QSize(36, 36))
|
||||
if not px.isNull() and px.width() > 4:
|
||||
return icon
|
||||
except Exception:
|
||||
pass
|
||||
ext = os.path.splitext(path)[1].lower()
|
||||
color = "#4a9eff"
|
||||
if ext == ".exe": return qta.icon("fa5s.desktop", color=color)
|
||||
if ext == ".lnk": return qta.icon("fa5s.link", color=color)
|
||||
if ext in (".bat", ".cmd"): return qta.icon("fa5s.terminal", color=color)
|
||||
if ext == ".url": return qta.icon("fa5s.globe", color=color)
|
||||
return qta.icon("fa5s.file", color=color)
|
||||
key = os.path.normcase(path)
|
||||
|
||||
def _load() -> QIcon:
|
||||
try:
|
||||
icon = _icon_provider.icon(QFileInfo(path))
|
||||
if not icon.isNull():
|
||||
px = icon.pixmap(QSize(64, 64))
|
||||
if not px.isNull() and px.width() > 4:
|
||||
return icon
|
||||
except Exception:
|
||||
pass
|
||||
ext = os.path.splitext(path)[1].lower()
|
||||
color = "#4a9eff"
|
||||
if ext == ".exe": return qta.icon("fa5s.desktop", color=color)
|
||||
if ext == ".lnk": return qta.icon("fa5s.link", color=color)
|
||||
if ext in (".bat", ".cmd"): return qta.icon("fa5s.terminal", color=color)
|
||||
if ext == ".url": return qta.icon("fa5s.globe", color=color)
|
||||
return qta.icon("fa5s.file", color=color)
|
||||
|
||||
return _cached_icon(key, _load)
|
||||
|
||||
|
||||
class ItemWidget(QWidget):
|
||||
@@ -79,10 +107,14 @@ class ItemWidget(QWidget):
|
||||
def __init__(self, item_data: dict, parent=None):
|
||||
super().__init__(parent)
|
||||
self.item_data = item_data
|
||||
self.setFixedSize(68, 76)
|
||||
# 尺寸跟随设置中的「图标宽度」,高度 = 宽度 + 文本区
|
||||
w = get_icon_width()
|
||||
self._icon_px = max(24, int(w * 0.53))
|
||||
self.setFixedSize(w, w + 8)
|
||||
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self._drag_start_pos = QPoint()
|
||||
self._selected = False
|
||||
self._hovered = False
|
||||
self._build_ui()
|
||||
|
||||
def showEvent(self, event):
|
||||
@@ -95,12 +127,12 @@ class ItemWidget(QWidget):
|
||||
|
||||
def _build_ui(self):
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(4, 6, 4, 4)
|
||||
layout.setContentsMargins(4, 6, 4, 2)
|
||||
layout.setSpacing(3)
|
||||
|
||||
icon = extract_icon(self.item_data["path"])
|
||||
self.icon_label = QLabel()
|
||||
self.icon_label.setPixmap(icon.pixmap(QSize(36, 36)))
|
||||
self.icon_label.setPixmap(icon.pixmap(QSize(self._icon_px, self._icon_px)))
|
||||
self.icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.icon_label.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents)
|
||||
|
||||
@@ -133,17 +165,31 @@ class ItemWidget(QWidget):
|
||||
def matches(self, keyword: str) -> bool:
|
||||
return keyword.lower() in self.item_data["name"].lower()
|
||||
|
||||
# ── 选中高亮 ─────────────────────────────────────────
|
||||
# ── 选中/悬停高亮 ─────────────────────────────────────
|
||||
def _set_selected(self, val: bool):
|
||||
self._selected = val
|
||||
self.update()
|
||||
|
||||
def enterEvent(self, event):
|
||||
self._hovered = True
|
||||
self.update()
|
||||
super().enterEvent(event)
|
||||
|
||||
def leaveEvent(self, event):
|
||||
self._hovered = False
|
||||
self.update()
|
||||
super().leaveEvent(event)
|
||||
|
||||
def paintEvent(self, event):
|
||||
if self._selected:
|
||||
if self._selected or self._hovered:
|
||||
p = QPainter(self)
|
||||
p.setRenderHint(QPainter.RenderHint.Antialiasing)
|
||||
p.setBrush(QColor(74, 158, 255, 50))
|
||||
p.setPen(QPen(QColor("#4a9eff"), 1.5))
|
||||
if self._selected:
|
||||
p.setBrush(QColor(74, 158, 255, 50))
|
||||
p.setPen(QPen(QColor("#4a9eff"), 1.5))
|
||||
else:
|
||||
p.setBrush(QColor(128, 128, 128, 36))
|
||||
p.setPen(Qt.PenStyle.NoPen)
|
||||
p.drawRoundedRect(1, 1, self.width() - 2, self.height() - 2, 6, 6)
|
||||
p.end()
|
||||
super().paintEvent(event)
|
||||
@@ -305,7 +351,7 @@ class ItemWidget(QWidget):
|
||||
conn.commit()
|
||||
conn.close()
|
||||
self.item_data["path"] = store
|
||||
self.icon_label.setPixmap(extract_icon(store).pixmap(QSize(36, 36)))
|
||||
self.icon_label.setPixmap(extract_icon(store).pixmap(QSize(self._icon_px, self._icon_px)))
|
||||
self._update_name_display()
|
||||
|
||||
def _delete_item(self, skip_confirm: bool = False, skip_refresh: bool = False):
|
||||
|
||||
Reference in New Issue
Block a user