mirror of https://github.com/PCSX2/pcsx2.git
573 lines
16 KiB
C++
573 lines
16 KiB
C++
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
|
// SPDX-License-Identifier: GPL-3.0+
|
|
|
|
#include "DropIndicators.h"
|
|
|
|
#include "QtUtils.h"
|
|
#include "Debugger/Docking/DockViews.h"
|
|
|
|
#include "common/Assertions.h"
|
|
|
|
#include <kddockwidgets/Config.h>
|
|
#include <kddockwidgets/core/Group.h>
|
|
#include <kddockwidgets/core/Platform.h>
|
|
#include <kddockwidgets/core/indicators/SegmentedDropIndicatorOverlay.h>
|
|
#include <kddockwidgets/qtwidgets/ViewFactory.h>
|
|
|
|
#include <QtGui/QPainter>
|
|
|
|
static std::pair<QColor, QColor> pickNiceColours(const QPalette& palette, bool hovered)
|
|
{
|
|
QColor fill = palette.highlight().color();
|
|
QColor outline = palette.highlight().color();
|
|
|
|
if (QtUtils::IsLightTheme(palette))
|
|
{
|
|
fill = fill.darker(200);
|
|
outline = outline.darker(200);
|
|
}
|
|
else
|
|
{
|
|
fill = fill.lighter(200);
|
|
outline = outline.lighter(200);
|
|
}
|
|
|
|
fill.setAlpha(200);
|
|
outline.setAlpha(255);
|
|
|
|
if (!hovered)
|
|
{
|
|
fill.setAlpha(fill.alpha() / 2);
|
|
outline.setAlpha(outline.alpha() / 2);
|
|
}
|
|
|
|
return {fill, outline};
|
|
}
|
|
|
|
// *****************************************************************************
|
|
|
|
DockDropIndicatorProxy::DockDropIndicatorProxy(KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators)
|
|
: m_classic_indicators(classic_indicators)
|
|
{
|
|
recreateWindowIfNecessary();
|
|
}
|
|
|
|
DockDropIndicatorProxy::~DockDropIndicatorProxy()
|
|
{
|
|
delete m_window;
|
|
delete m_fallback_window;
|
|
}
|
|
|
|
void DockDropIndicatorProxy::setObjectName(const QString& name)
|
|
{
|
|
window()->setObjectName(name);
|
|
}
|
|
|
|
KDDockWidgets::DropLocation DockDropIndicatorProxy::hover(QPoint globalPos)
|
|
{
|
|
return window()->hover(globalPos);
|
|
}
|
|
|
|
QPoint DockDropIndicatorProxy::posForIndicator(KDDockWidgets::DropLocation loc) const
|
|
{
|
|
return window()->posForIndicator(loc);
|
|
}
|
|
|
|
void DockDropIndicatorProxy::updatePositions()
|
|
{
|
|
// Check if a compositor is running whenever a drag starts.
|
|
recreateWindowIfNecessary();
|
|
|
|
window()->updatePositions();
|
|
}
|
|
|
|
void DockDropIndicatorProxy::raise()
|
|
{
|
|
window()->raise();
|
|
}
|
|
|
|
void DockDropIndicatorProxy::setVisible(bool visible)
|
|
{
|
|
window()->setVisible(visible);
|
|
}
|
|
|
|
void DockDropIndicatorProxy::resize(QSize size)
|
|
{
|
|
window()->resize(size);
|
|
}
|
|
|
|
void DockDropIndicatorProxy::setGeometry(QRect rect)
|
|
{
|
|
window()->setGeometry(rect);
|
|
}
|
|
|
|
bool DockDropIndicatorProxy::isWindow() const
|
|
{
|
|
return window()->isWindow();
|
|
}
|
|
|
|
void DockDropIndicatorProxy::updateIndicatorVisibility()
|
|
{
|
|
window()->updateIndicatorVisibility();
|
|
}
|
|
|
|
KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* DockDropIndicatorProxy::window()
|
|
{
|
|
if (!m_supports_compositing)
|
|
{
|
|
pxAssert(m_fallback_window);
|
|
return m_fallback_window;
|
|
}
|
|
|
|
pxAssert(m_window);
|
|
return m_window;
|
|
}
|
|
|
|
const KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* DockDropIndicatorProxy::window() const
|
|
{
|
|
if (!m_supports_compositing)
|
|
{
|
|
pxAssert(m_fallback_window);
|
|
return m_fallback_window;
|
|
}
|
|
|
|
pxAssert(m_window);
|
|
return m_window;
|
|
}
|
|
|
|
void DockDropIndicatorProxy::recreateWindowIfNecessary()
|
|
{
|
|
bool supports_compositing = QtUtils::IsCompositorManagerRunning();
|
|
if (supports_compositing == m_supports_compositing && (m_window || m_fallback_window))
|
|
return;
|
|
|
|
m_supports_compositing = supports_compositing;
|
|
|
|
DockViewFactory* factory = static_cast<DockViewFactory*>(KDDockWidgets::Config::self().viewFactory());
|
|
|
|
if (supports_compositing)
|
|
{
|
|
if (!m_window)
|
|
m_window = new DockDropIndicatorWindow(m_classic_indicators);
|
|
|
|
QWidget* old_window = dynamic_cast<QWidget*>(m_fallback_window);
|
|
if (old_window)
|
|
{
|
|
m_window->setObjectName(old_window->objectName());
|
|
m_window->setVisible(old_window->isVisible());
|
|
m_window->setGeometry(old_window->geometry());
|
|
}
|
|
|
|
delete m_fallback_window;
|
|
m_fallback_window = nullptr;
|
|
}
|
|
else
|
|
{
|
|
if (!m_fallback_window)
|
|
m_fallback_window = factory->createFallbackClassicIndicatorWindow(m_classic_indicators, nullptr);
|
|
|
|
QWidget* old_window = dynamic_cast<QWidget*>(m_window);
|
|
if (old_window)
|
|
{
|
|
m_window->setObjectName(old_window->objectName());
|
|
m_window->setVisible(old_window->isVisible());
|
|
m_window->setGeometry(old_window->geometry());
|
|
}
|
|
|
|
delete m_window;
|
|
m_window = nullptr;
|
|
}
|
|
}
|
|
|
|
// *****************************************************************************
|
|
|
|
static const constexpr int IND_LEFT = 0;
|
|
static const constexpr int IND_TOP = 1;
|
|
static const constexpr int IND_RIGHT = 2;
|
|
static const constexpr int IND_BOTTOM = 3;
|
|
static const constexpr int IND_CENTER = 4;
|
|
static const constexpr int IND_OUTER_LEFT = 5;
|
|
static const constexpr int IND_OUTER_TOP = 6;
|
|
static const constexpr int IND_OUTER_RIGHT = 7;
|
|
static const constexpr int IND_OUTER_BOTTOM = 8;
|
|
|
|
static const constexpr int INDICATOR_SIZE = 40;
|
|
static const constexpr int INDICATOR_MARGIN = 10;
|
|
|
|
static bool isWayland()
|
|
{
|
|
return KDDockWidgets::Core::Platform::instance()->displayType() ==
|
|
KDDockWidgets::Core::Platform::DisplayType::Wayland;
|
|
}
|
|
|
|
static QWidget* parentForIndicatorWindow(KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators)
|
|
{
|
|
if (isWayland())
|
|
return KDDockWidgets::QtCommon::View_qt::asQWidget(classic_indicators->view());
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static Qt::WindowFlags flagsForIndicatorWindow()
|
|
{
|
|
if (isWayland())
|
|
return Qt::Widget;
|
|
|
|
return Qt::Tool | Qt::BypassWindowManagerHint;
|
|
}
|
|
|
|
DockDropIndicatorWindow::DockDropIndicatorWindow(
|
|
KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators)
|
|
: QWidget(parentForIndicatorWindow(classic_indicators), flagsForIndicatorWindow())
|
|
, m_classic_indicators(classic_indicators)
|
|
, m_indicators({
|
|
/* [IND_LEFT] = */ new DockDropIndicator(KDDockWidgets::DropLocation_Left, this),
|
|
/* [IND_TOP] = */ new DockDropIndicator(KDDockWidgets::DropLocation_Top, this),
|
|
/* [IND_RIGHT] = */ new DockDropIndicator(KDDockWidgets::DropLocation_Right, this),
|
|
/* [IND_BOTTOM] = */ new DockDropIndicator(KDDockWidgets::DropLocation_Bottom, this),
|
|
/* [IND_CENTER] = */ new DockDropIndicator(KDDockWidgets::DropLocation_Center, this),
|
|
/* [IND_OUTER_LEFT] = */ new DockDropIndicator(KDDockWidgets::DropLocation_OutterLeft, this),
|
|
/* [IND_OUTER_TOP] = */ new DockDropIndicator(KDDockWidgets::DropLocation_OutterTop, this),
|
|
/* [IND_OUTER_RIGHT] = */ new DockDropIndicator(KDDockWidgets::DropLocation_OutterRight, this),
|
|
/* [IND_OUTER_BOTTOM] = */ new DockDropIndicator(KDDockWidgets::DropLocation_OutterBottom, this),
|
|
})
|
|
{
|
|
setWindowFlag(Qt::FramelessWindowHint, true);
|
|
|
|
if (KDDockWidgets::Config::self().flags() & KDDockWidgets::Config::Flag_KeepAboveIfNotUtilityWindow)
|
|
setWindowFlag(Qt::WindowStaysOnTopHint, true);
|
|
|
|
setAttribute(Qt::WA_TranslucentBackground);
|
|
}
|
|
|
|
void DockDropIndicatorWindow::setObjectName(const QString& name)
|
|
{
|
|
QWidget::setObjectName(name);
|
|
}
|
|
|
|
KDDockWidgets::DropLocation DockDropIndicatorWindow::hover(QPoint globalPos)
|
|
{
|
|
KDDockWidgets::DropLocation result = KDDockWidgets::DropLocation_None;
|
|
|
|
for (DockDropIndicator* indicator : m_indicators)
|
|
{
|
|
if (indicator->isVisible())
|
|
{
|
|
bool hovered = indicator->rect().contains(indicator->mapFromGlobal(globalPos));
|
|
if (hovered != indicator->hovered)
|
|
{
|
|
indicator->hovered = hovered;
|
|
indicator->update();
|
|
}
|
|
|
|
if (hovered)
|
|
result = indicator->location;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
QPoint DockDropIndicatorWindow::posForIndicator(KDDockWidgets::DropLocation loc) const
|
|
{
|
|
for (DockDropIndicator* indicator : m_indicators)
|
|
if (indicator->location == loc)
|
|
return indicator->mapToGlobal(indicator->rect().center());
|
|
|
|
return QPoint();
|
|
}
|
|
|
|
void DockDropIndicatorWindow::updatePositions()
|
|
{
|
|
DockDropIndicator* left = m_indicators[IND_LEFT];
|
|
DockDropIndicator* top = m_indicators[IND_TOP];
|
|
DockDropIndicator* right = m_indicators[IND_RIGHT];
|
|
DockDropIndicator* bottom = m_indicators[IND_BOTTOM];
|
|
DockDropIndicator* center = m_indicators[IND_CENTER];
|
|
DockDropIndicator* outer_left = m_indicators[IND_OUTER_LEFT];
|
|
DockDropIndicator* outer_top = m_indicators[IND_OUTER_TOP];
|
|
DockDropIndicator* outer_right = m_indicators[IND_OUTER_RIGHT];
|
|
DockDropIndicator* outer_bottom = m_indicators[IND_OUTER_BOTTOM];
|
|
|
|
QRect r = rect();
|
|
int half_indicator_width = INDICATOR_SIZE / 2;
|
|
|
|
outer_left->move(r.x() + INDICATOR_MARGIN, r.center().y() - half_indicator_width);
|
|
outer_bottom->move(r.center().x() - half_indicator_width, r.y() + height() - INDICATOR_SIZE - INDICATOR_MARGIN);
|
|
outer_top->move(r.center().x() - half_indicator_width, r.y() + INDICATOR_MARGIN);
|
|
outer_right->move(r.x() + width() - INDICATOR_SIZE - INDICATOR_MARGIN, r.center().y() - half_indicator_width);
|
|
|
|
KDDockWidgets::Core::Group* hovered_group = m_classic_indicators->hoveredGroup();
|
|
if (hovered_group)
|
|
{
|
|
QRect hoveredRect = hovered_group->view()->geometry();
|
|
center->move(r.topLeft() + hoveredRect.center() - QPoint(half_indicator_width, half_indicator_width));
|
|
top->move(center->pos() - QPoint(0, INDICATOR_SIZE + INDICATOR_MARGIN));
|
|
right->move(center->pos() + QPoint(INDICATOR_SIZE + INDICATOR_MARGIN, 0));
|
|
bottom->move(center->pos() + QPoint(0, INDICATOR_SIZE + INDICATOR_MARGIN));
|
|
left->move(center->pos() - QPoint(INDICATOR_SIZE + INDICATOR_MARGIN, 0));
|
|
}
|
|
}
|
|
|
|
void DockDropIndicatorWindow::raise()
|
|
{
|
|
QWidget::raise();
|
|
}
|
|
|
|
void DockDropIndicatorWindow::setVisible(bool is)
|
|
{
|
|
QWidget::setVisible(is);
|
|
}
|
|
|
|
void DockDropIndicatorWindow::resize(QSize size)
|
|
{
|
|
QWidget::resize(size);
|
|
}
|
|
|
|
void DockDropIndicatorWindow::setGeometry(QRect rect)
|
|
{
|
|
QWidget::setGeometry(rect);
|
|
}
|
|
|
|
bool DockDropIndicatorWindow::isWindow() const
|
|
{
|
|
return QWidget::isWindow();
|
|
}
|
|
|
|
void DockDropIndicatorWindow::updateIndicatorVisibility()
|
|
{
|
|
for (DockDropIndicator* indicator : m_indicators)
|
|
indicator->setVisible(m_classic_indicators->dropIndicatorVisible(indicator->location));
|
|
}
|
|
|
|
void DockDropIndicatorWindow::resizeEvent(QResizeEvent* ev)
|
|
{
|
|
QWidget::resizeEvent(ev);
|
|
updatePositions();
|
|
}
|
|
|
|
// *****************************************************************************
|
|
|
|
DockDropIndicator::DockDropIndicator(KDDockWidgets::DropLocation loc, QWidget* parent)
|
|
: QWidget(parent)
|
|
, location(loc)
|
|
{
|
|
setFixedSize(INDICATOR_SIZE, INDICATOR_SIZE);
|
|
setVisible(true);
|
|
}
|
|
|
|
void DockDropIndicator::paintEvent(QPaintEvent* event)
|
|
{
|
|
QPainter painter(this);
|
|
painter.setRenderHint(QPainter::Antialiasing, true);
|
|
|
|
auto [fill, outline] = pickNiceColours(palette(), hovered);
|
|
|
|
painter.setBrush(fill);
|
|
|
|
QPen pen;
|
|
pen.setColor(outline);
|
|
pen.setWidth(2);
|
|
painter.setPen(pen);
|
|
|
|
painter.drawRect(rect());
|
|
|
|
QRectF rf = rect().toRectF();
|
|
|
|
QRectF outer = rf.marginsRemoved(QMarginsF(4.f, 4.f, 4.f, 4.f));
|
|
QPointF icon_position;
|
|
switch (location)
|
|
{
|
|
case KDDockWidgets::DropLocation_Left:
|
|
case KDDockWidgets::DropLocation_OutterLeft:
|
|
outer = outer.marginsRemoved(QMarginsF(0.f, 0.f, outer.width() / 2.f, 0.f));
|
|
icon_position = rf.marginsRemoved(QMarginsF(rf.width() / 2.f, 0.f, 0.f, 0.f)).center();
|
|
break;
|
|
case KDDockWidgets::DropLocation_Top:
|
|
case KDDockWidgets::DropLocation_OutterTop:
|
|
outer = outer.marginsRemoved(QMarginsF(0.f, 0.f, 0.f, outer.width() / 2.f));
|
|
icon_position = rf.marginsRemoved(QMarginsF(0.f, rf.width() / 2.f, 0.f, 0.f)).center();
|
|
break;
|
|
case KDDockWidgets::DropLocation_Right:
|
|
case KDDockWidgets::DropLocation_OutterRight:
|
|
outer = outer.marginsRemoved(QMarginsF(outer.width() / 2.f, 0.f, 0.f, 0.f));
|
|
icon_position = rf.marginsRemoved(QMarginsF(0.f, 0.f, rf.width() / 2.f, 0.f)).center();
|
|
break;
|
|
case KDDockWidgets::DropLocation_Bottom:
|
|
case KDDockWidgets::DropLocation_OutterBottom:
|
|
outer = outer.marginsRemoved(QMarginsF(0.f, outer.width() / 2.f, 0.f, 0.f));
|
|
icon_position = rf.marginsRemoved(QMarginsF(0.f, 0.f, 0.f, rf.width() / 2.f)).center();
|
|
break;
|
|
default:
|
|
{
|
|
}
|
|
}
|
|
|
|
painter.drawRect(outer);
|
|
|
|
float arrow_size = INDICATOR_SIZE / 10.f;
|
|
|
|
QPolygonF arrow;
|
|
switch (location)
|
|
{
|
|
case KDDockWidgets::DropLocation_Left:
|
|
arrow = {
|
|
icon_position + QPointF(-arrow_size, 0.f),
|
|
icon_position + QPointF(arrow_size, arrow_size * 2.f),
|
|
icon_position + QPointF(arrow_size, -arrow_size * 2.f),
|
|
};
|
|
break;
|
|
case KDDockWidgets::DropLocation_Top:
|
|
arrow = {
|
|
icon_position + QPointF(0.f, -arrow_size),
|
|
icon_position + QPointF(arrow_size * 2.f, arrow_size),
|
|
icon_position + QPointF(-arrow_size * 2.f, arrow_size),
|
|
};
|
|
break;
|
|
case KDDockWidgets::DropLocation_Right:
|
|
arrow = {
|
|
icon_position + QPointF(arrow_size, 0.f),
|
|
icon_position + QPointF(-arrow_size, arrow_size * 2.f),
|
|
icon_position + QPointF(-arrow_size, -arrow_size * 2.f),
|
|
};
|
|
break;
|
|
case KDDockWidgets::DropLocation_Bottom:
|
|
arrow = {
|
|
icon_position + QPointF(0.f, arrow_size),
|
|
icon_position + QPointF(arrow_size * 2.f, -arrow_size),
|
|
icon_position + QPointF(-arrow_size * 2.f, -arrow_size),
|
|
};
|
|
break;
|
|
default:
|
|
{
|
|
}
|
|
}
|
|
|
|
painter.drawPolygon(arrow);
|
|
}
|
|
|
|
// *****************************************************************************
|
|
|
|
std::string DockSegmentedDropIndicatorOverlay::s_indicator_style;
|
|
|
|
DockSegmentedDropIndicatorOverlay::DockSegmentedDropIndicatorOverlay(
|
|
KDDockWidgets::Core::SegmentedDropIndicatorOverlay* controller, QWidget* parent)
|
|
: KDDockWidgets::QtWidgets::SegmentedDropIndicatorOverlay(controller, parent)
|
|
{
|
|
}
|
|
|
|
void DockSegmentedDropIndicatorOverlay::paintEvent(QPaintEvent* event)
|
|
{
|
|
if (s_indicator_style == "Minimalistic")
|
|
drawMinimalistic();
|
|
else
|
|
drawSegmented();
|
|
}
|
|
|
|
void DockSegmentedDropIndicatorOverlay::drawSegmented()
|
|
{
|
|
QPainter painter(this);
|
|
painter.setRenderHint(QPainter::Antialiasing, true);
|
|
|
|
KDDockWidgets::Core::SegmentedDropIndicatorOverlay* controller =
|
|
asController<KDDockWidgets::Core::SegmentedDropIndicatorOverlay>();
|
|
|
|
const std::unordered_map<KDDockWidgets::DropLocation, QPolygon>& segments = controller->segments();
|
|
|
|
for (KDDockWidgets::DropLocation location :
|
|
{KDDockWidgets::DropLocation_Left,
|
|
KDDockWidgets::DropLocation_Top,
|
|
KDDockWidgets::DropLocation_Right,
|
|
KDDockWidgets::DropLocation_Bottom,
|
|
KDDockWidgets::DropLocation_Center,
|
|
KDDockWidgets::DropLocation_OutterLeft,
|
|
KDDockWidgets::DropLocation_OutterTop,
|
|
KDDockWidgets::DropLocation_OutterRight,
|
|
KDDockWidgets::DropLocation_OutterBottom})
|
|
{
|
|
auto segment = segments.find(location);
|
|
if (segment == segments.end() || segment->second.size() < 2)
|
|
continue;
|
|
|
|
bool hovered = segment->second.containsPoint(controller->hoveredPt(), Qt::OddEvenFill);
|
|
auto [fill, outline] = pickNiceColours(palette(), hovered);
|
|
|
|
painter.setBrush(fill);
|
|
|
|
QPen pen(outline);
|
|
pen.setWidth(1);
|
|
painter.setPen(pen);
|
|
|
|
int margin = KDDockWidgets::Core::SegmentedDropIndicatorOverlay::s_segmentGirth * 2;
|
|
|
|
// Make sure the rectangles don't intersect with each other.
|
|
QRect rect;
|
|
switch (location)
|
|
{
|
|
case KDDockWidgets::DropLocation_Top:
|
|
case KDDockWidgets::DropLocation_Bottom:
|
|
case KDDockWidgets::DropLocation_OutterTop:
|
|
case KDDockWidgets::DropLocation_OutterBottom:
|
|
{
|
|
rect = segment->second.boundingRect().marginsRemoved(QMargins(margin, 4, margin, 4));
|
|
break;
|
|
}
|
|
case KDDockWidgets::DropLocation_Left:
|
|
case KDDockWidgets::DropLocation_Right:
|
|
case KDDockWidgets::DropLocation_OutterLeft:
|
|
case KDDockWidgets::DropLocation_OutterRight:
|
|
{
|
|
rect = segment->second.boundingRect().marginsRemoved(QMargins(4, margin, 4, margin));
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
rect = segment->second.boundingRect().marginsRemoved(QMargins(4, 4, 4, 4));
|
|
break;
|
|
}
|
|
}
|
|
|
|
painter.drawRect(rect);
|
|
}
|
|
}
|
|
|
|
void DockSegmentedDropIndicatorOverlay::drawMinimalistic()
|
|
{
|
|
QPainter painter(this);
|
|
painter.setRenderHint(QPainter::Antialiasing, true);
|
|
|
|
KDDockWidgets::Core::SegmentedDropIndicatorOverlay* controller =
|
|
asController<KDDockWidgets::Core::SegmentedDropIndicatorOverlay>();
|
|
|
|
const std::unordered_map<KDDockWidgets::DropLocation, QPolygon>& segments = controller->segments();
|
|
|
|
for (KDDockWidgets::DropLocation location :
|
|
{KDDockWidgets::DropLocation_Left,
|
|
KDDockWidgets::DropLocation_Top,
|
|
KDDockWidgets::DropLocation_Right,
|
|
KDDockWidgets::DropLocation_Bottom,
|
|
KDDockWidgets::DropLocation_Center,
|
|
KDDockWidgets::DropLocation_OutterLeft,
|
|
KDDockWidgets::DropLocation_OutterTop,
|
|
KDDockWidgets::DropLocation_OutterRight,
|
|
KDDockWidgets::DropLocation_OutterBottom})
|
|
{
|
|
auto segment = segments.find(location);
|
|
if (segment == segments.end() || segment->second.size() < 2)
|
|
continue;
|
|
|
|
if (!segment->second.containsPoint(controller->hoveredPt(), Qt::OddEvenFill))
|
|
continue;
|
|
|
|
auto [fill, outline] = pickNiceColours(palette(), true);
|
|
|
|
painter.setBrush(fill);
|
|
|
|
QPen pen(outline);
|
|
pen.setWidth(1);
|
|
painter.setPen(pen);
|
|
|
|
painter.drawRect(segment->second.boundingRect());
|
|
}
|
|
}
|