From 94c4e656d646a4ff6bdf9c445eb9791eb208dc83 Mon Sep 17 00:00:00 2001 From: baldurk Date: Mon, 20 Apr 2026 14:08:02 +0100 Subject: [PATCH] Move RDToolTip into separate file independent of tree view --- qrenderdoc/Widgets/Extended/RDToolTip.cpp | 139 ++++++++++++++++++ qrenderdoc/Widgets/Extended/RDToolTip.h | 63 ++++++++ qrenderdoc/Widgets/Extended/RDTreeView.cpp | 112 +------------- qrenderdoc/Widgets/Extended/RDTreeView.h | 41 +----- .../PipelineState/PipelineStateViewer.cpp | 4 +- .../PipelineState/PipelineStateViewer.h | 6 +- qrenderdoc/qrenderdoc.pro | 2 + qrenderdoc/qrenderdoc_local.vcxproj | 8 + qrenderdoc/qrenderdoc_local.vcxproj.filters | 9 ++ 9 files changed, 234 insertions(+), 150 deletions(-) create mode 100644 qrenderdoc/Widgets/Extended/RDToolTip.cpp create mode 100644 qrenderdoc/Widgets/Extended/RDToolTip.h diff --git a/qrenderdoc/Widgets/Extended/RDToolTip.cpp b/qrenderdoc/Widgets/Extended/RDToolTip.cpp new file mode 100644 index 000000000..29b836291 --- /dev/null +++ b/qrenderdoc/Widgets/Extended/RDToolTip.cpp @@ -0,0 +1,139 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2026 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include "RDToolTip.h" +#include +#include +#include +#include +#include +#include + +void ICustomToolTipDisplay::showTip(QWidget *widget, QString text, QModelIndex idx) +{ + QPoint p = QCursor::pos(); + + // estimate, as this is not easily queryable + const QPoint cursorSize(16, 16); + const QRect screenAvailGeom = QApplication::desktop()->availableGeometry(p); + + // start with the tooltip placed bottom-right of the cursor, as the default + QRect tooltipRect; + tooltipRect.setTopLeft(p + cursorSize); + tooltipRect.setSize(configureTip(widget, text, idx)); + + // clip by the available geometry in x + if(tooltipRect.right() > screenAvailGeom.right()) + tooltipRect.moveRight(screenAvailGeom.right()); + + // if we'd go out of bounds in y, place the tooltip above the cursor. Don't just clip like + // in x, because that could place the tooltip over the cursor. + if(tooltipRect.bottom() > screenAvailGeom.bottom()) + tooltipRect.moveBottom(p.y() - cursorSize.y()); + + showTipAtPos(tooltipRect.topLeft()); +} + +RDToolTip::RDToolTip(QWidget *listener) : QLabel(NULL), mouseListener(listener) +{ + int margin = style()->pixelMetric(QStyle::PM_ToolTipLabelFrameWidth, NULL, this); + int opacity = style()->styleHint(QStyle::SH_ToolTipLabel_Opacity, NULL, this); + + setWindowFlags(Qt::ToolTip); + setAttribute(Qt::WA_TransparentForMouseEvents); + setForegroundRole(QPalette::ToolTipText); + setBackgroundRole(QPalette::ToolTipBase); + setMargin(margin + 1); + setFrameStyle(QFrame::NoFrame); + setAlignment(Qt::AlignLeft); + setIndent(1); + setWindowOpacity(opacity / 255.0); +} + +QSize RDToolTip::configureTip(QWidget *, QString text, QModelIndex) +{ + setText(text); + QSize ret = minimumSizeHint(); + resize(ret); + return ret; +} + +void RDToolTip::showTipAtPos(QPoint pos) +{ + move(pos); + show(); +} + +bool RDToolTip::forceTip(QWidget *widget, QModelIndex idx) +{ + return false; +} + +void RDToolTip::paintEvent(QPaintEvent *ev) +{ + QStylePainter p(this); + QStyleOptionFrame opt; + opt.init(this); + p.drawPrimitive(QStyle::PE_PanelTipLabel, opt); + p.end(); + + QLabel::paintEvent(ev); +} + +void RDToolTip::mousePressEvent(QMouseEvent *e) +{ + if(mouseListener) + sendListenerEvent(e); +} + +void RDToolTip::sendListenerEvent(QMouseEvent *e) +{ + QMouseEvent *duplicate = + new QMouseEvent(e->type(), mouseListener->mapFromGlobal(e->globalPos()), e->windowPos(), + e->globalPos(), e->button(), e->buttons(), e->modifiers(), e->source()); + QCoreApplication::postEvent(mouseListener, duplicate); +} + +void RDToolTip::mouseReleaseEvent(QMouseEvent *e) +{ + if(mouseListener) + sendListenerEvent(e); +} + +void RDToolTip::mouseDoubleClickEvent(QMouseEvent *e) +{ + if(mouseListener) + sendListenerEvent(e); +} + +void RDToolTip::resizeEvent(QResizeEvent *e) +{ + QStyleHintReturnMask frameMask; + QStyleOption option; + option.init(this); + if(style()->styleHint(QStyle::SH_ToolTip_Mask, &option, this, &frameMask)) + setMask(frameMask.region); + + QLabel::resizeEvent(e); +} diff --git a/qrenderdoc/Widgets/Extended/RDToolTip.h b/qrenderdoc/Widgets/Extended/RDToolTip.h new file mode 100644 index 000000000..e4041c1a5 --- /dev/null +++ b/qrenderdoc/Widgets/Extended/RDToolTip.h @@ -0,0 +1,63 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2026 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#pragma once + +#include +#include + +struct ICustomToolTipDisplay +{ +public: + virtual void hideTip() = 0; + virtual QSize configureTip(QWidget *widget, QString text, QModelIndex idx = QModelIndex()) = 0; + virtual void showTipAtPos(QPoint pos) = 0; + virtual bool forceTip(QWidget *widget, QModelIndex idx) = 0; + void showTip(QWidget *widget, QString text, QModelIndex idx = QModelIndex()); +}; + +class RDToolTip : public QLabel, public ICustomToolTipDisplay +{ +private: + Q_OBJECT + + QWidget *mouseListener; + +public: + explicit RDToolTip(QWidget *listener = NULL); + + void hideTip() { hide(); } + QSize configureTip(QWidget *widget, QString text, QModelIndex idx = QModelIndex()); + void showTipAtPos(QPoint pos); + bool forceTip(QWidget *widget, QModelIndex idx); + +protected: + void paintEvent(QPaintEvent *); + void mousePressEvent(QMouseEvent *); + void mouseReleaseEvent(QMouseEvent *); + void mouseDoubleClickEvent(QMouseEvent *); + void resizeEvent(QResizeEvent *); + + void sendListenerEvent(QMouseEvent *e); +}; diff --git a/qrenderdoc/Widgets/Extended/RDTreeView.cpp b/qrenderdoc/Widgets/Extended/RDTreeView.cpp index 006837477..34dea8def 100644 --- a/qrenderdoc/Widgets/Extended/RDTreeView.cpp +++ b/qrenderdoc/Widgets/Extended/RDTreeView.cpp @@ -26,16 +26,13 @@ #include #include #include -#include #include #include #include #include #include -#include #include #include -#include #include #include "Code/QRDUtils.h" #include "Code/Resources.h" @@ -117,87 +114,6 @@ QSize RDTreeViewDelegate::sizeHint(const QStyleOptionViewItem &option, const QMo return ret; } -RDTipLabel::RDTipLabel(QWidget *listener) : QLabel(NULL), mouseListener(listener) -{ - int margin = style()->pixelMetric(QStyle::PM_ToolTipLabelFrameWidth, NULL, this); - int opacity = style()->styleHint(QStyle::SH_ToolTipLabel_Opacity, NULL, this); - - setWindowFlags(Qt::ToolTip); - setAttribute(Qt::WA_TransparentForMouseEvents); - setForegroundRole(QPalette::ToolTipText); - setBackgroundRole(QPalette::ToolTipBase); - setMargin(margin + 1); - setFrameStyle(QFrame::NoFrame); - setAlignment(Qt::AlignLeft); - setIndent(1); - setWindowOpacity(opacity / 255.0); -} - -QSize RDTipLabel::configureTip(QWidget *, QModelIndex, QString text) -{ - setText(text); - return minimumSizeHint(); -} - -void RDTipLabel::showTip(QPoint pos) -{ - move(pos); - show(); -} - -bool RDTipLabel::forceTip(QWidget *widget, QModelIndex idx) -{ - return false; -} - -void RDTipLabel::paintEvent(QPaintEvent *ev) -{ - QStylePainter p(this); - QStyleOptionFrame opt; - opt.init(this); - p.drawPrimitive(QStyle::PE_PanelTipLabel, opt); - p.end(); - - QLabel::paintEvent(ev); -} - -void RDTipLabel::mousePressEvent(QMouseEvent *e) -{ - if(mouseListener) - sendListenerEvent(e); -} - -void RDTipLabel::sendListenerEvent(QMouseEvent *e) -{ - QMouseEvent *duplicate = - new QMouseEvent(e->type(), mouseListener->mapFromGlobal(e->globalPos()), e->windowPos(), - e->globalPos(), e->button(), e->buttons(), e->modifiers(), e->source()); - QCoreApplication::postEvent(mouseListener, duplicate); -} - -void RDTipLabel::mouseReleaseEvent(QMouseEvent *e) -{ - if(mouseListener) - sendListenerEvent(e); -} - -void RDTipLabel::mouseDoubleClickEvent(QMouseEvent *e) -{ - if(mouseListener) - sendListenerEvent(e); -} - -void RDTipLabel::resizeEvent(QResizeEvent *e) -{ - QStyleHintReturnMask frameMask; - QStyleOption option; - option.init(this); - if(style()->styleHint(QStyle::SH_ToolTip_Mask, &option, this, &frameMask)) - setMask(frameMask.region); - - QLabel::resizeEvent(e); -} - RDTreeView::RDTreeView(QWidget *parent) : QTreeView(parent) { setMouseTracking(true); @@ -205,7 +121,7 @@ RDTreeView::RDTreeView(QWidget *parent) : QTreeView(parent) m_delegate = new RDTreeViewDelegate(this); QTreeView::setItemDelegate(m_delegate); - m_TooltipLabel = new RDTipLabel(viewport()); + m_TooltipLabel = new RDToolTip(viewport()); m_TooltipLabel->hide(); m_CurrentTooltipElided = false; @@ -258,27 +174,7 @@ void RDTreeView::mouseMoveEvent(QMouseEvent *e) // We don't use QToolTip since we have a custom tooltip for showing elided results, and we // use that for consistency. This also makes it easier to slot in a custom tooltip widget // externally. - QPoint p = QCursor::pos(); - - // estimate, as this is not easily queryable - const QPoint cursorSize(16, 16); - const QRect screenAvailGeom = QApplication::desktop()->availableGeometry(p); - - // start with the tooltip placed bottom-right of the cursor, as the default - QRect tooltipRect; - tooltipRect.setTopLeft(p + cursorSize); - tooltipRect.setSize(m_Tooltip->configureTip(this, m_currentHoverIndex, tooltip)); - - // clip by the available geometry in x - if(tooltipRect.right() > screenAvailGeom.right()) - tooltipRect.moveRight(screenAvailGeom.right()); - - // if we'd go out of bounds in y, place the tooltip above the cursor. Don't just clip like - // in x, because that could place the tooltip over the cursor. - if(tooltipRect.bottom() > screenAvailGeom.bottom()) - tooltipRect.moveBottom(p.y() - cursorSize.y()); - - m_Tooltip->showTip(tooltipRect.topLeft()); + m_Tooltip->showTip(this, tooltip, m_currentHoverIndex); m_CurrentTooltipElided = false; } } @@ -459,8 +355,8 @@ bool RDTreeView::viewportEvent(QEvent *event) // need to use a custom label tooltip since the QToolTip freaks out as we're placing it // underneath the cursor instead of next to it (so that the tooltip lines up over the // row) - m_Tooltip->configureTip(this, index, fullText); - m_Tooltip->showTip(viewport()->mapToGlobal(option.rect.topLeft())); + m_Tooltip->configureTip(this, fullText, index); + m_Tooltip->showTipAtPos(viewport()->mapToGlobal(option.rect.topLeft())); m_CurrentTooltipElided = true; } } diff --git a/qrenderdoc/Widgets/Extended/RDTreeView.h b/qrenderdoc/Widgets/Extended/RDTreeView.h index 086c5fc63..b8cadc8c8 100644 --- a/qrenderdoc/Widgets/Extended/RDTreeView.h +++ b/qrenderdoc/Widgets/Extended/RDTreeView.h @@ -28,6 +28,7 @@ #include #include #include "Code/QRDUtils.h" +#include "RDToolTip.h" class RDTreeView; @@ -47,40 +48,6 @@ public: QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; }; -struct ITreeViewTipDisplay -{ -public: - virtual void hideTip() = 0; - virtual QSize configureTip(QWidget *widget, QModelIndex idx, QString text) = 0; - virtual void showTip(QPoint pos) = 0; - virtual bool forceTip(QWidget *widget, QModelIndex idx) = 0; -}; - -class RDTipLabel : public QLabel, public ITreeViewTipDisplay -{ -private: - Q_OBJECT - - QWidget *mouseListener; - -public: - explicit RDTipLabel(QWidget *listener = NULL); - - void hideTip() { hide(); } - QSize configureTip(QWidget *widget, QModelIndex idx, QString text); - void showTip(QPoint pos); - bool forceTip(QWidget *widget, QModelIndex idx); - -protected: - void paintEvent(QPaintEvent *); - void mousePressEvent(QMouseEvent *); - void mouseReleaseEvent(QMouseEvent *); - void mouseDoubleClickEvent(QMouseEvent *); - void resizeEvent(QResizeEvent *); - - void sendListenerEvent(QMouseEvent *e); -}; - typedef std::function ExpansionKeyGen; class RDTreeView : public QTreeView @@ -116,7 +83,7 @@ public: void setItemDelegate(QAbstractItemDelegate *delegate); QAbstractItemDelegate *itemDelegate() const; - void setCustomTooltip(ITreeViewTipDisplay *tip) + void setCustomTooltip(ICustomToolTipDisplay *tip) { m_Tooltip = tip; m_TooltipElidedItems = false; @@ -230,8 +197,8 @@ private: QAbstractItemDelegate *m_userDelegate = NULL; RDTreeViewDelegate *m_delegate; - RDTipLabel *m_TooltipLabel; - ITreeViewTipDisplay *m_Tooltip; + RDToolTip *m_TooltipLabel; + ICustomToolTipDisplay *m_Tooltip; bool m_CurrentTooltipElided = false; int m_VertMargin = 6; diff --git a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp index 2892f5106..13e8484bb 100644 --- a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp @@ -127,7 +127,7 @@ void RDPreviewTooltip::hideTip() hide(); } -QSize RDPreviewTooltip::configureTip(QWidget *widget, QModelIndex idx, QString text) +QSize RDPreviewTooltip::configureTip(QWidget *widget, QString text, QModelIndex idx) { ResourceId id = pipe->updateThumbnail(widget, idx); if(id != ResourceId()) @@ -146,7 +146,7 @@ QSize RDPreviewTooltip::configureTip(QWidget *widget, QModelIndex idx, QString t return minimumSizeHint(); } -void RDPreviewTooltip::showTip(QPoint pos) +void RDPreviewTooltip::showTipAtPos(QPoint pos) { move(pos); resize(minimumSize()); diff --git a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h index e123366ca..f94366e8f 100644 --- a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h +++ b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h @@ -59,7 +59,7 @@ struct ScopedTreeUpdater int vs; }; -class RDPreviewTooltip : public QFrame, public ITreeViewTipDisplay +class RDPreviewTooltip : public QFrame, public ICustomToolTipDisplay { private: Q_OBJECT @@ -74,8 +74,8 @@ public: ICaptureContext &ctx); void hideTip(); - QSize configureTip(QWidget *widget, QModelIndex idx, QString text); - void showTip(QPoint pos); + QSize configureTip(QWidget *widget, QString text, QModelIndex idx); + void showTipAtPos(QPoint pos); bool forceTip(QWidget *widget, QModelIndex idx); protected: diff --git a/qrenderdoc/qrenderdoc.pro b/qrenderdoc/qrenderdoc.pro index 1f3bc170c..5fa774ccf 100644 --- a/qrenderdoc/qrenderdoc.pro +++ b/qrenderdoc/qrenderdoc.pro @@ -222,6 +222,7 @@ SOURCES += Code/qrenderdoc.cpp \ Windows/PipelineState/D3D12PipelineStateViewer.cpp \ Windows/PipelineState/GLPipelineStateViewer.cpp \ Widgets/Extended/RDTreeView.cpp \ + Widgets/Extended/RDToolTip.cpp \ Widgets/Extended/RDTreeWidget.cpp \ Widgets/BufferFormatSpecifier.cpp \ Windows/BufferViewer.cpp \ @@ -311,6 +312,7 @@ HEADERS += Code/CaptureContext.h \ Windows/PipelineState/D3D12PipelineStateViewer.h \ Windows/PipelineState/GLPipelineStateViewer.h \ Widgets/Extended/RDTreeView.h \ + Widgets/Extended/RDToolTip.h \ Widgets/Extended/RDTreeWidget.h \ Widgets/BufferFormatSpecifier.h \ Windows/BufferViewer.h \ diff --git a/qrenderdoc/qrenderdoc_local.vcxproj b/qrenderdoc/qrenderdoc_local.vcxproj index d5662faeb..b63aa1c79 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj +++ b/qrenderdoc/qrenderdoc_local.vcxproj @@ -662,6 +662,7 @@ + @@ -724,6 +725,7 @@ + @@ -1138,6 +1140,12 @@ MOC %(Filename).h $(IntDir)generated\moc_%(Filename).cpp + + %(Fullpath);$(QtBinDir)\moc.exe;%(AdditionalInputs) + "$(QtBinDir)\moc.exe" -DUNICODE -DWIN32 -DWIN64 -D_WIN32 -D_WIN64 -DRENDERDOC_PLATFORM_WIN32 -DSCINTILLA_QT=1 -DSCI_LEXER=1 -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -D_MSC_VER=1900 -I"$(ProjectDir)." -I"$(SolutionDir)\renderdoc\api\replay" -I"$(QtIncludeDir)" -I"$(QtIncludeDir)\QtWidgets" -I"$(QtIncludeDir)\QtGui" -I"$(QtIncludeDir)\QtCore" "%(Fullpath)" -o "$(IntDir)generated\moc_%(Filename).cpp" + MOC %(Filename).h + $(IntDir)generated\moc_%(Filename).cpp + %(Fullpath);$(QtBinDir)\moc.exe;%(AdditionalInputs) "$(QtBinDir)\moc.exe" -DUNICODE -DWIN32 -DWIN64 -D_WIN32 -D_WIN64 -DRENDERDOC_PLATFORM_WIN32 -DSCINTILLA_QT=1 -DSCI_LEXER=1 -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -D_MSC_VER=1900 -I"$(ProjectDir)." -I"$(SolutionDir)\renderdoc\api\replay" -I"$(QtIncludeDir)" -I"$(QtIncludeDir)\QtWidgets" -I"$(QtIncludeDir)\QtGui" -I"$(QtIncludeDir)\QtCore" "%(Fullpath)" -o "$(IntDir)generated\moc_%(Filename).cpp" diff --git a/qrenderdoc/qrenderdoc_local.vcxproj.filters b/qrenderdoc/qrenderdoc_local.vcxproj.filters index 189e4de8d..67bd04742 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj.filters +++ b/qrenderdoc/qrenderdoc_local.vcxproj.filters @@ -789,6 +789,12 @@ Windows\Dialogs + + Generated Files + + + Widgets\Extended + @@ -1616,6 +1622,9 @@ Windows\Dialogs + + Widgets\Extended +