#include "serverwheelview.h" #include #include #include #include #include #include #include #include #include #include #include ServerWheelView::ServerWheelView(QWidget* parent) : QWidget(parent) { setAttribute(Qt::WA_StyledBackground, true); setStyleSheet("ServerWheelView { background: transparent; }"); m_scrollAnim = new QVariantAnimation(this); m_scrollAnim->setEasingCurve(QEasingCurve::OutExpo); m_scrollAnim->setDuration(450); connect(m_scrollAnim, &QVariantAnimation::valueChanged, this, [this](const QVariant& val){ m_scrollOffset = val.toReal(); updateLayout(); }); } ServerWheelView::~ServerWheelView() { clear(); } void ServerWheelView::addCard(QWidget* card) { card->setParent(this); auto* effect = new QGraphicsOpacityEffect(card); card->setGraphicsEffect(effect); installFilterRecursively(card); m_items.append(card); updateLayout(); } void ServerWheelView::clear() { m_scrollAnim->stop(); m_scrollOffset = 0.0; m_currentIndex = 0; qDeleteAll(m_items); m_items.clear(); } int ServerWheelView::count() const { return m_items.size(); } void ServerWheelView::setCurrentIndex(int index, bool animated) { int N = m_items.size(); if (N == 0) return; index = index % N; if (index < 0) index += N; if (animated) { qreal delta = index - m_scrollOffset; qreal wrapped_delta = delta - N * qRound(delta / static_cast(N)); scrollTo(m_scrollOffset + wrapped_delta); } else { m_scrollAnim->stop(); m_scrollOffset = index; updateLayout(); } } int ServerWheelView::currentIndex() const { return m_currentIndex; } void ServerWheelView::setTransitionMode(bool active) { m_isTransitioning = active; if (active) { m_scrollAnim->stop(); m_scrollOffset = m_currentIndex; } updateLayout(); } void ServerWheelView::resizeEvent(QResizeEvent*) { updateLayout(); } void ServerWheelView::handleMousePress(QPoint globalPos) { if (m_items.isEmpty()) return; m_scrollAnim->stop(); m_isDragging = true; m_dragStarted = false; m_lastPos = globalPos; m_dragVelocity = 0.0; m_lastTime = QDateTime::currentMSecsSinceEpoch(); } void ServerWheelView::handleMouseMove(QPoint globalPos) { if (!m_isDragging) return; qreal dy = globalPos.y() - m_lastPos.y(); if (!m_dragStarted && qAbs(dy) > 5) m_dragStarted = true; if (m_dragStarted) { qreal deltaOffset = -dy / 80.0; qint64 now = QDateTime::currentMSecsSinceEpoch(); qint64 dt = now - m_lastTime; if (dt > 0) m_dragVelocity = deltaOffset / dt * 16.0; m_scrollOffset += deltaOffset; m_lastPos = globalPos; m_lastTime = now; updateLayout(); } } bool ServerWheelView::handleMouseRelease() { if (!m_isDragging) return false; m_isDragging = false; if (m_dragStarted) { if (!m_items.isEmpty()) { qreal target = qRound(m_scrollOffset + m_dragVelocity * 10.0); scrollTo(target); } return true; } return false; } void ServerWheelView::mousePressEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton) handleMousePress(event->globalPosition().toPoint()); QWidget::mousePressEvent(event); } void ServerWheelView::mouseMoveEvent(QMouseEvent* event) { handleMouseMove(event->globalPosition().toPoint()); QWidget::mouseMoveEvent(event); } void ServerWheelView::mouseReleaseEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton) handleMouseRelease(); QWidget::mouseReleaseEvent(event); } bool ServerWheelView::eventFilter(QObject* obj, QEvent* event) { if (event->type() == QEvent::MouseButtonPress) { auto* me = static_cast(event); if (me->button() == Qt::LeftButton) handleMousePress(me->globalPosition().toPoint()); } else if (event->type() == QEvent::MouseMove) { auto* me = static_cast(event); handleMouseMove(me->globalPosition().toPoint()); if (m_dragStarted) return true; } else if (event->type() == QEvent::MouseButtonRelease) { auto* me = static_cast(event); if (me->button() == Qt::LeftButton) { if (handleMouseRelease()) return true; } } return QWidget::eventFilter(obj, event); } void ServerWheelView::wheelEvent(QWheelEvent* event) { if (m_items.isEmpty()) return; qreal angleDelta = event->angleDelta().y(); qreal step = -angleDelta / 120.0; qreal baseTarget = m_scrollAnim->state() == QAbstractAnimation::Running ? m_scrollAnim->endValue().toReal() : qRound(m_scrollOffset); scrollTo(baseTarget + step); } void ServerWheelView::installFilterRecursively(QWidget* w) { w->installEventFilter(this); for (auto* child : w->findChildren()) { child->installEventFilter(this); } } void ServerWheelView::scrollTo(qreal target) { m_scrollAnim->stop(); m_scrollAnim->setStartValue(m_scrollOffset); m_scrollAnim->setEndValue(target); m_scrollAnim->start(); } void ServerWheelView::updateLayout() { int N = m_items.size(); if (N == 0) return; int vh = height(); int vw = width(); int centerY = vh / 2; int baseItemHeight = 72; QMap zOrderMap; int newIndex = qRound(m_scrollOffset) % N; if (newIndex < 0) newIndex += N; if (newIndex != m_currentIndex) { m_currentIndex = newIndex; Q_EMIT currentIndexChanged(m_currentIndex); } for (int i = 0; i < N; ++i) { QWidget* card = m_items[i]; qreal rawDelta = i - m_scrollOffset; qreal logicalIdx = rawDelta - N * qRound(rawDelta / static_cast(N)); qreal distance = qAbs(logicalIdx); qreal opacity = qMax(0.0, 1.0 - distance * 0.4); bool shouldHide = (opacity <= 0.0 || (m_isTransitioning && i != m_currentIndex)); if (shouldHide) { if (card->isVisible()) card->hide(); continue; } else { if (!card->isVisible()) card->show(); } if (card->testAttribute(Qt::WA_TransparentForMouseEvents)) { card->setAttribute(Qt::WA_TransparentForMouseEvents, false); } bool isDepthCard = (distance > 1.5); if (card->property("isDepthCard").toBool() != isDepthCard) { card->setProperty("isDepthCard", isDepthCard); } if (isDepthCard) { QWidget* actionContainer = nullptr; if (card->property("cachedActionPtr").isValid()) { actionContainer = reinterpret_cast(card->property("cachedActionPtr").toULongLong()); } else { for (auto* btn : card->findChildren()) { if (btn->objectName() == "action-edit-btn" && btn->parentWidget()) { actionContainer = btn->parentWidget(); card->setProperty("cachedActionPtr", reinterpret_cast(actionContainer)); break; } } } if (actionContainer && actionContainer->isVisible()) { actionContainer->hide(); } } qreal angle = logicalIdx * (M_PI / 6.0); qreal R = vh / 2.0; qreal scale = qMax(0.5, 1.0 - distance * 0.2); int projectedW = vw * scale; int projectedH = baseItemHeight * scale; int projectedX = (vw - projectedW) / 2; int projectedY = centerY + R * qSin(angle) - projectedH / 2.0; QRect newGeometry(projectedX, projectedY, projectedW, projectedH); if (card->geometry() != newGeometry) { card->setGeometry(newGeometry); } for (auto* child : card->findChildren()) { if (qobject_cast(child) || qobject_cast(child)) { if (!child->property("originalFontSize").isValid()) { QFont f = child->font(); qreal size = f.pixelSize() > 0 ? f.pixelSize() : f.pointSizeF(); child->setProperty("originalFontSize", size); child->setProperty("usePixel", f.pixelSize() > 0); } qreal origSize = child->property("originalFontSize").toReal(); bool usePixel = child->property("usePixel").toBool(); int newSizeInt = qMax(1, qRound(origSize * scale)); int lastSizeInt = child->property("lastAppliedFontSize").toInt(); if (newSizeInt != lastSizeInt) { child->setProperty("lastAppliedFontSize", newSizeInt); QFont newFont = child->font(); if (usePixel) { newFont.setPixelSize(newSizeInt); } else { newFont.setPointSizeF(newSizeInt); } child->setFont(newFont); } } if (QLabel* label = qobject_cast(child)) { if (!label->property("originalIconSize").isValid() && label->minimumWidth() == label->maximumWidth() && label->minimumHeight() == label->maximumHeight() && label->minimumWidth() > 0) { label->setProperty("originalIconSize", label->minimumWidth()); } if (label->property("originalIconSize").isValid()) { int origIconSize = label->property("originalIconSize").toInt(); int newSize = qMax(1, qRound(origIconSize * scale)); int lastIconSize = label->property("lastAppliedIconSize").toInt(); if (newSize != lastIconSize) { label->setProperty("lastAppliedIconSize", newSize); label->setFixedSize(newSize, newSize); } } } } if (auto* effect = qobject_cast(card->graphicsEffect())) { if (!qFuzzyCompare(effect->opacity(), opacity)) { effect->setOpacity(opacity); } bool enableEffect = opacity < 0.99; if (effect->isEnabled() != enableEffect) { effect->setEnabled(enableEffect); } } zOrderMap.insert(-distance, card); } for (auto* card : zOrderMap.values()) { card->raise(); } }