Add desktop drawing for cursor condition regions

This commit is contained in:
nanopink
2026-10-25 00:28:16 -04:00
committed by WarmUpTill
parent 00ddbe2646
commit 10f1cc154f
6 changed files with 260 additions and 0 deletions

View File

@@ -395,6 +395,8 @@ AdvSceneSwitcher.condition.cursor.button.middle="Middle mouse button"
AdvSceneSwitcher.condition.cursor.button.right="Right mouse button"
AdvSceneSwitcher.condition.cursor.showFrame="Show frame"
AdvSceneSwitcher.condition.cursor.hideFrame="Hide frame"
AdvSceneSwitcher.condition.cursor.drawFrame="Draw frame"
AdvSceneSwitcher.condition.cursor.drawFrame.prompt="Drag to draw a cursor region. Release to apply. Press Esc or right-click to cancel."
AdvSceneSwitcher.condition.cursor.entry.line1="Cursor{{conditions}}{{buttons}}{{minX}}{{minY}}{{maxX}}{{maxY}}{{toggleFrameButton}}"
AdvSceneSwitcher.condition.cursor.entry.line2="Cursor is currently at{{xPos}}x{{yPos}}"
AdvSceneSwitcher.condition.scene="Scene"

View File

@@ -180,6 +180,8 @@ target_sources(
utils/process-config.hpp
utils/profile-helpers.cpp
utils/profile-helpers.hpp
utils/screen-region-selection.cpp
utils/screen-region-selection.hpp
utils/scene-item-selection.cpp
utils/scene-item-selection.hpp
utils/scene-item-transform-helpers.cpp

View File

@@ -1,6 +1,7 @@
#include "macro-condition-cursor.hpp"
#include "cursor-helpers.hpp"
#include "layout-helpers.hpp"
#include "screen-region-selection.hpp"
#include "utility.hpp"
#include <QGuiApplication>
@@ -144,11 +145,14 @@ MacroConditionCursorEdit::MacroConditionCursorEdit(
_buttons(new QComboBox()),
_frameToggle(new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.cursor.showFrame"))),
_drawFrame(new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.cursor.drawFrame"))),
_xPos(new QLabel("-")),
_yPos(new QLabel("-")),
_curentPosLayout(new QHBoxLayout())
{
_frame.setAttribute(Qt::WA_TransparentForMouseEvents);
_drawFrame->setAttribute(Qt::WA_NoMousePropagation);
populateConditionSelection(_conditions);
populateButtonSelection(_buttons);
@@ -190,6 +194,8 @@ MacroConditionCursorEdit::MacroConditionCursorEdit(
this, SLOT(MaxYChanged(const NumberVariable<int> &)));
QWidget::connect(_frameToggle, SIGNAL(clicked()), this,
SLOT(ToggleFrame()));
connect(_drawFrame, &QPushButton::clicked, this,
&MacroConditionCursorEdit::DrawFrame, Qt::QueuedConnection);
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{conditions}}", _conditions},
@@ -209,6 +215,7 @@ MacroConditionCursorEdit::MacroConditionCursorEdit(
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.condition.cursor.entry.line2"),
_curentPosLayout, widgetPlaceholders);
_curentPosLayout->addWidget(_drawFrame);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(line1);
mainLayout->addLayout(_curentPosLayout);
@@ -287,6 +294,49 @@ void MacroConditionCursorEdit::ToggleFrame()
}
}
void MacroConditionCursorEdit::DrawFrame()
{
if (_loading || !_entryData || !_drawFrame->isEnabled()) {
return;
}
const bool frameVisible = _frame.isVisible();
_frame.hide();
_drawFrame->setEnabled(false);
auto selection = new ScreenRegionSelection(this);
connect(selection, &ScreenRegionSelection::RegionSelected, this,
[this](const QRect &region) {
GUARD_LOADING_AND_LOCK();
if (_entryData->_condition !=
MacroConditionCursor::Condition::REGION) {
return;
}
_entryData->_minX = region.left();
_entryData->_minY = region.top();
_entryData->_maxX = region.right();
_entryData->_maxY = region.bottom();
const QSignalBlocker b1(_minX);
const QSignalBlocker b2(_minY);
const QSignalBlocker b3(_maxX);
const QSignalBlocker b4(_maxY);
_minX->SetValue(_entryData->_minX);
_minY->SetValue(_entryData->_minY);
_maxX->SetValue(_entryData->_maxX);
_maxY->SetValue(_entryData->_maxY);
SetupFrame();
});
connect(selection, &ScreenRegionSelection::Finished, this,
[this, frameVisible]() {
_drawFrame->setEnabled(true);
if (frameVisible &&
_entryData->_condition ==
MacroConditionCursor::Condition::REGION) {
SetupFrame();
_frame.show();
}
});
}
void MacroConditionCursorEdit::SetWidgetVisibility()
{
const bool isRegionCondition = _entryData->_condition ==
@@ -296,6 +346,7 @@ void MacroConditionCursorEdit::SetWidgetVisibility()
_maxX->setVisible(isRegionCondition);
_maxY->setVisible(isRegionCondition);
_frameToggle->setVisible(isRegionCondition);
_drawFrame->setVisible(isRegionCondition);
SetLayoutVisible(_curentPosLayout, isRegionCondition);
if (_frame.isVisible()) {
ToggleFrame();

View File

@@ -74,6 +74,7 @@ private slots:
void MaxYChanged(const NumberVariable<int> &pos);
void UpdateCursorPos();
void ToggleFrame();
void DrawFrame();
protected:
VariableSpinBox *_minX;
@@ -83,6 +84,7 @@ protected:
QComboBox *_conditions;
QComboBox *_buttons;
QPushButton *_frameToggle;
QPushButton *_drawFrame;
QLabel *_xPos;
QLabel *_yPos;
QHBoxLayout *_curentPosLayout;

View File

@@ -0,0 +1,165 @@
#include "screen-region-selection.hpp"
#include "obs-module-helper.hpp"
#include <QCursor>
#include <QGuiApplication>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QPainter>
#include <QScreen>
#include <QWindow>
namespace advss {
ScreenRegionSelection::ScreenRegionSelection(QWidget *parent) : QObject(parent)
{
QWidget *activeOverlay = nullptr;
const auto cursor = QCursor::pos();
for (auto screen : QGuiApplication::screens()) {
auto overlay = std::make_unique<QWidget>(
parent->window(), Qt::Tool | Qt::FramelessWindowHint |
Qt::WindowStaysOnTopHint);
// Keep native overlay windows separate from the macro's widgets.
overlay->setAttribute(Qt::WA_DontCreateNativeAncestors);
overlay->setAttribute(Qt::WA_NoMousePropagation);
overlay->setAttribute(Qt::WA_TranslucentBackground);
overlay->setCursor(Qt::CrossCursor);
overlay->setFocusPolicy(Qt::StrongFocus);
overlay->setMouseTracking(true);
overlay->installEventFilter(this);
// Each monitor needs its own window for mixed display scaling.
overlay->winId();
overlay->windowHandle()->setScreen(screen);
overlay->setGeometry(screen->geometry());
overlay->show();
if (screen->geometry().contains(cursor)) {
activeOverlay = overlay.get();
}
_overlays.emplace_back(std::move(overlay));
}
if (activeOverlay) {
activeOverlay->raise();
activeOverlay->activateWindow();
activeOverlay->setFocus();
}
connect(qGuiApp, &QGuiApplication::screenRemoved, this,
[this]() { Finish(false); });
connect(qGuiApp, &QGuiApplication::screenAdded, this,
[this]() { Finish(false); });
connect(qGuiApp, &QGuiApplication::applicationStateChanged, this,
[this](Qt::ApplicationState state) {
if (state != Qt::ApplicationActive) {
Finish(false);
}
});
}
ScreenRegionSelection::~ScreenRegionSelection()
{
for (auto &overlay : _overlays) {
overlay->removeEventFilter(this);
}
}
bool ScreenRegionSelection::eventFilter(QObject *watched, QEvent *event)
{
if (_finished) {
return false;
}
auto overlay = static_cast<QWidget *>(watched);
switch (event->type()) {
case QEvent::Paint: {
QPainter painter(overlay);
painter.fillRect(overlay->rect(), QColor(0, 0, 0, 70));
if (_drawing) {
const QRect region(
overlay->mapFromGlobal(_selection.topLeft()),
overlay->mapFromGlobal(
_selection.bottomRight()));
painter.fillRect(region, QColor(60, 150, 255, 60));
painter.setPen(QPen(Qt::white, 2));
painter.drawRect(region.adjusted(0, 0, -1, -1));
}
painter.setPen(Qt::white);
painter.drawText(
overlay->rect().adjusted(24, 24, -24, -24),
Qt::AlignTop | Qt::AlignHCenter | Qt::TextWordWrap,
obs_module_text(
"AdvSceneSwitcher.condition.cursor.drawFrame.prompt"));
return true;
}
case QEvent::MouseButtonPress:
case QEvent::MouseButtonDblClick: {
event->accept();
auto mouse = static_cast<QMouseEvent *>(event);
if (mouse->button() == Qt::RightButton) {
Finish(false);
} else if (mouse->button() == Qt::LeftButton) {
_start = QCursor::pos();
_drawing = true;
UpdateSelection();
}
return true;
}
case QEvent::MouseMove:
event->accept();
if (_drawing) {
UpdateSelection();
}
return true;
case QEvent::MouseButtonRelease:
event->accept();
if (_drawing && static_cast<QMouseEvent *>(event)->button() ==
Qt::LeftButton) {
UpdateSelection();
if (_selection.width() > 1 && _selection.height() > 1) {
Finish(true);
} else {
_drawing = false;
UpdateSelection();
}
}
return true;
case QEvent::KeyPress:
event->accept();
if (static_cast<QKeyEvent *>(event)->key() == Qt::Key_Escape) {
Finish(false);
}
return true;
case QEvent::ShortcutOverride:
event->accept();
return true;
case QEvent::Close:
Finish(false);
return true;
default:
return QObject::eventFilter(watched, event);
}
}
void ScreenRegionSelection::UpdateSelection()
{
// Use the same global coordinate system as the Cursor condition.
_selection = QRect(_start, QCursor::pos()).normalized();
for (auto &overlay : _overlays) {
overlay->update();
}
}
void ScreenRegionSelection::Finish(bool accept)
{
if (_finished) {
return;
}
_finished = true;
for (auto &overlay : _overlays) {
overlay->hide();
}
if (accept) {
emit RegionSelected(_selection);
}
emit Finished();
deleteLater();
}
} // namespace advss

View File

@@ -0,0 +1,38 @@
#pragma once
#include <QObject>
#include <QPoint>
#include <QRect>
#include <QWidget>
#include <memory>
#include <vector>
namespace advss {
class ScreenRegionSelection : public QObject {
Q_OBJECT
public:
explicit ScreenRegionSelection(QWidget *parent);
~ScreenRegionSelection() override;
signals:
void RegionSelected(const QRect &region);
void Finished();
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
private:
void UpdateSelection();
void Finish(bool accept);
std::vector<std::unique_ptr<QWidget>> _overlays;
QPoint _start;
QRect _selection;
bool _drawing = false;
bool _finished = false;
};
} // namespace advss