from PyQt6.QtWidgets import QWidget from PyQt6.QtCore import QPoint, QTimer, Qt, QRect, QSize from PyQt6.QtGui import QPainter, QColor, QPen ITEM_W = 72 ITEM_H = 80 GAP = 4 RUBBER_MIN = 4 # 小于此尺寸的框选视为误触 class FlowContainer(QWidget): """手动绝对定位的流式容器,完全绕开 QLayout。""" def __init__(self, parent=None): super().__init__(parent) self._widgets: list[QWidget] = [] self._pending = False self._drag_highlight = False self.setMouseTracking(True) self._rubber_active = False self._rubber_origin = QPoint() self._rubber_rect: QRect | None = None def add_widget(self, w: QWidget): w.setParent(self) self._widgets.append(w) if self.width() > 0: self._relayout() else: self._schedule_relayout() def clear_widgets(self): for w in self._widgets: w.hide() w.setParent(None) self._widgets.clear() self.setFixedHeight(60) def get_widgets(self) -> list: return list(self._widgets) def set_drag_highlight(self, active: bool): if self._drag_highlight != active: self._drag_highlight = active self.update() def showEvent(self, event): super().showEvent(event) self._schedule_relayout() def resizeEvent(self, event): self._relayout() super().resizeEvent(event) def _schedule_relayout(self): if not self._pending: self._pending = True QTimer.singleShot(0, self._delayed_relayout) def _delayed_relayout(self): self._pending = False self._relayout() def _relayout(self): avail_w = self.width() if avail_w <= 0: p = self.parent() while p: if p.width() > 0: avail_w = p.width() - 16 break p = p.parent() if avail_w <= 0: self._schedule_relayout() return x, y = GAP, GAP row_h = 0 for w in self._widgets: if not w.isVisible(): w.move(-9999, -9999) continue iw = w.width() if w.width() > 0 else ITEM_W ih = w.height() if w.height() > 0 else ITEM_H if row_h > 0 and x + iw + GAP > avail_w: x = GAP y += row_h + GAP row_h = 0 w.move(x, y) x += iw + GAP row_h = max(row_h, ih) visible = [w for w in self._widgets if w.isVisible()] total_h = (y + row_h + GAP) if visible else 60 self.setFixedHeight(max(total_h, 0)) def index_at(self, pos: QPoint) -> int: """拖拽插入位置:与流式布局顺序一致,同行每个图标单独判断,不再只认行内第一个。""" visible = [w for w in self._widgets if w.isVisible()] if not visible: return 0 if pos.y() < visible[0].geometry().top(): return 0 for w in visible: g = w.geometry() if pos.y() < g.top(): return self._widgets.index(w) if g.top() <= pos.y() <= g.bottom(): if pos.x() < g.left(): return self._widgets.index(w) if pos.x() <= g.right(): mid = (g.left() + g.right()) // 2 if pos.x() < mid: return self._widgets.index(w) return self._widgets.index(w) + 1 # 落在该行、但在本图标右侧,继续看下一个(同行或下一行) return len(self._widgets) def _clear_selection_except(self, keep): for w in self._widgets: if w is not keep and hasattr(w, "_set_selected"): w._set_selected(False) def clear_selection(self): self._clear_selection_except(None) def _begin_rubber(self, pos: QPoint): self.clear_selection() self._rubber_active = True self._rubber_origin = pos self._rubber_rect = QRect(pos, QSize()) self.grabMouse() self.update() def mousePressEvent(self, event): if event.button() == Qt.MouseButton.LeftButton: ch = self.childAt(event.pos()) if ch is None: self._begin_rubber(event.pos()) return super().mousePressEvent(event) def mouseMoveEvent(self, event): if self._rubber_active: self._rubber_rect = QRect(self._rubber_origin, event.pos()).normalized() self.update() return super().mouseMoveEvent(event) def mouseReleaseEvent(self, event): if self._rubber_active and event.button() == Qt.MouseButton.LeftButton: self._rubber_active = False self.releaseMouse() r = self._rubber_rect self._rubber_rect = None self.update() if r is not None and r.width() >= RUBBER_MIN and r.height() >= RUBBER_MIN: self.clear_selection() for w in self._widgets: if not w.isVisible(): continue if hasattr(w, "_set_selected") and r.intersects(w.geometry()): w._set_selected(True) return super().mouseReleaseEvent(event) def paintEvent(self, event): super().paintEvent(event) need_drag = self._drag_highlight need_rubber = self._rubber_rect is not None and self._rubber_active if not need_drag and not need_rubber: return p = QPainter(self) p.setRenderHint(QPainter.RenderHint.Antialiasing) if need_drag: pen = QPen(QColor("#4a9eff"), 2, Qt.PenStyle.DashLine) pen.setDashPattern([6, 4]) p.setPen(pen) p.setBrush(QColor(74, 158, 255, 25)) p.drawRoundedRect(2, 2, self.width() - 4, self.height() - 4, 6, 6) if need_rubber: pen = QPen(QColor("#4a9eff"), 1, Qt.PenStyle.DashLine) p.setPen(pen) p.setBrush(QColor(74, 158, 255, 40)) p.drawRoundedRect(self._rubber_rect, 2, 2) p.end()