diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 76942b12..8422baf6 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -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" diff --git a/plugins/base/CMakeLists.txt b/plugins/base/CMakeLists.txt index 4543c418..0a33baca 100644 --- a/plugins/base/CMakeLists.txt +++ b/plugins/base/CMakeLists.txt @@ -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 diff --git a/plugins/base/macro-condition-cursor.cpp b/plugins/base/macro-condition-cursor.cpp index b6417d97..402ba4b1 100644 --- a/plugins/base/macro-condition-cursor.cpp +++ b/plugins/base/macro-condition-cursor.cpp @@ -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 @@ -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 &))); QWidget::connect(_frameToggle, SIGNAL(clicked()), this, SLOT(ToggleFrame())); + connect(_drawFrame, &QPushButton::clicked, this, + &MacroConditionCursorEdit::DrawFrame, Qt::QueuedConnection); std::unordered_map 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 ®ion) { + 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(); diff --git a/plugins/base/macro-condition-cursor.hpp b/plugins/base/macro-condition-cursor.hpp index 17295c02..3cff3894 100644 --- a/plugins/base/macro-condition-cursor.hpp +++ b/plugins/base/macro-condition-cursor.hpp @@ -74,6 +74,7 @@ private slots: void MaxYChanged(const NumberVariable &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; diff --git a/plugins/base/utils/screen-region-selection.cpp b/plugins/base/utils/screen-region-selection.cpp new file mode 100644 index 00000000..a46106e6 --- /dev/null +++ b/plugins/base/utils/screen-region-selection.cpp @@ -0,0 +1,165 @@ +#include "screen-region-selection.hpp" +#include "obs-module-helper.hpp" + +#include +#include +#include +#include +#include +#include +#include + +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( + 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(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(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(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(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 diff --git a/plugins/base/utils/screen-region-selection.hpp b/plugins/base/utils/screen-region-selection.hpp new file mode 100644 index 00000000..394dd332 --- /dev/null +++ b/plugins/base/utils/screen-region-selection.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace advss { + +class ScreenRegionSelection : public QObject { + Q_OBJECT + +public: + explicit ScreenRegionSelection(QWidget *parent); + ~ScreenRegionSelection() override; + +signals: + void RegionSelected(const QRect ®ion); + void Finished(); + +protected: + bool eventFilter(QObject *watched, QEvent *event) override; + +private: + void UpdateSelection(); + void Finish(bool accept); + + std::vector> _overlays; + QPoint _start; + QRect _selection; + bool _drawing = false; + bool _finished = false; +}; + +} // namespace advss