diff --git a/frontend/cmake/ui-widgets.cmake b/frontend/cmake/ui-widgets.cmake index 4eeec39409f3a3..f039785ccfd8f9 100644 --- a/frontend/cmake/ui-widgets.cmake +++ b/frontend/cmake/ui-widgets.cmake @@ -56,6 +56,8 @@ target_sources( widgets/OBSProjector.hpp widgets/OBSQTDisplay.cpp widgets/OBSQTDisplay.hpp + widgets/OBSSourceWidget.cpp + widgets/OBSSourceWidget.hpp widgets/StatusBarWidget.cpp widgets/StatusBarWidget.hpp widgets/VolControl.cpp diff --git a/frontend/forms/OBSBasicFilters.ui b/frontend/forms/OBSBasicFilters.ui index c9cefaefc973bb..06c18efdf0b2d6 100644 --- a/frontend/forms/OBSBasicFilters.ui +++ b/frontend/forms/OBSBasicFilters.ui @@ -27,7 +27,7 @@ - + 0 0 @@ -249,7 +249,7 @@ - + 0 0 diff --git a/frontend/widgets/OBSSourceWidget.cpp b/frontend/widgets/OBSSourceWidget.cpp new file mode 100644 index 00000000000000..417eb2141f5f7e --- /dev/null +++ b/frontend/widgets/OBSSourceWidget.cpp @@ -0,0 +1,256 @@ +#include "OBSSourceWidget.hpp" + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "moc_OBSSourceWidget.cpp" + +OBSSourceWidget::OBSSourceWidget(QWidget *parent) : QFrame(parent), fixedAspectRatio(0.0) +{ + layout = new QVBoxLayout(); + setLayout(layout); + + layout->setContentsMargins(0, 0, 0, 0); + setMinimumSize(QSize(240, 135)); + + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); + + if (window()) { + window()->installEventFilter(this); + } + + if (parent) { + parent->installEventFilter(this); + } + + QObject *checkParent = parent; + + while (checkParent) { + QScrollArea *scrollParent = qobject_cast(checkParent); + if (scrollParent && scrollParent->widget()) { + scrollParent->widget()->installEventFilter(this); + } + + if (!checkParent->parent() || checkParent->parent() == checkParent) { + break; + } + + checkParent = checkParent->parent(); + } +} + +OBSSourceWidget::OBSSourceWidget(QWidget *parent, obs_source_t *source) : OBSSourceWidget(parent) +{ + setSource(source); +} + +void OBSSourceWidget::setFixedAspectRatio(double ratio) +{ + if (ratio > 0.0) { + fixedAspectRatio = ratio; + } else { + fixedAspectRatio = 0; + } +} + +void OBSSourceWidget::setSource(obs_source_t *source) +{ + if (!sourceView) { + sourceView = new OBSSourceWidgetView(this, source); + layout->addWidget(sourceView); + + connect(sourceView, &OBSSourceWidgetView::viewReady, this, &OBSSourceWidget::resizeSourceView); + } + + sourceView->setSource(source); +} + +void OBSSourceWidget::resizeSourceView() +{ + if (!sourceView) { + return; + } + + if (sourceView->sourceWidth() <= 0 || sourceView->sourceHeight() <= 0) { + return; + } + + double aspectRatio = fixedAspectRatio > 0 + ? fixedAspectRatio + : (double)sourceView->sourceWidth() / (double)sourceView->sourceHeight(); + + // Widget only expands in one direction + bool singleExpandDirection = (sizePolicy().horizontalPolicy() & QSizePolicy::ExpandFlag) != + (sizePolicy().verticalPolicy() & QSizePolicy::ExpandFlag); + + int scaledWidth = std::floor(height() / aspectRatio); + int scaledHeight = std::floor(width() / aspectRatio); + + if (fixedAspectRatio) { + setMaximumWidth(QWIDGETSIZE_MAX); + setMaximumHeight(scaledHeight); + } else if (singleExpandDirection) { + setMaximumWidth(QWIDGETSIZE_MAX); + setMaximumHeight(QWIDGETSIZE_MAX); + + if ((sizePolicy().horizontalPolicy() & QSizePolicy::ExpandFlag) == QSizePolicy::ExpandFlag) { + setMaximumHeight(scaledHeight); + } else { + setMaximumWidth(scaledWidth); + } + } + + QWindow *nativeWindow = sourceView->windowHandle(); + QRegion visible = sourceView->visibleRegion(); + if (nativeWindow) { + QPoint position = sourceView->mapTo(sourceView->nativeParentWidget(), QPoint()); + nativeWindow->setGeometry(QRect(position, sourceView->geometry().size())); + + if (!visible.isNull()) { + if (visible.boundingRect().width() > 0 && visible.boundingRect().height() > 0) { + nativeWindow->setMask(visible.boundingRect()); + } + } else { + nativeWindow->setMask(QRegion(0, 0, 1, 1)); + } + } +} + +bool OBSSourceWidget::eventFilter(QObject *, QEvent *event) +{ + if (event->type() == QEvent::Resize) { + resizeSourceView(); + } else if (event->type() == QEvent::Move) { + resizeSourceView(); + } + + return false; +} + +void OBSSourceWidget::moveEvent(QMoveEvent *event) +{ + QFrame::moveEvent(event); + resizeSourceView(); +} + +void OBSSourceWidget::resizeEvent(QResizeEvent *event) +{ + QFrame::resizeEvent(event); + resizeSourceView(); +} + +OBSSourceWidget::~OBSSourceWidget() {} + +OBSSourceWidgetView::OBSSourceWidgetView(OBSSourceWidget *widget, obs_source_t *source) + : OBSQTDisplay(widget, Qt::Widget) +{ + setSource(source); + show(); +} + +OBSSourceWidgetView::~OBSSourceWidgetView() +{ + obs_display_remove_draw_callback(GetDisplay(), OBSRender, this); + + OBSSource source = GetSource(); + if (source) { + obs_source_dec_showing(source); + } +} + +void OBSSourceWidgetView::setSourceWidth(int width) +{ + if (sourceWidth() == width) { + return; + } + + sourceWidth_ = width; + emit viewReady(); +} + +void OBSSourceWidgetView::setSourceHeight(int height) +{ + if (sourceHeight() == height) { + return; + } + + sourceHeight_ = height; + emit viewReady(); +} + +OBSSource OBSSourceWidgetView::GetSource() +{ + return OBSGetStrongRef(weakSource); +} + +void OBSSourceWidgetView::OBSRender(void *data, uint32_t cx, uint32_t cy) +{ + OBSSourceWidgetView *view = reinterpret_cast(data); + + OBSSource source = view->GetSource(); + if (!source) { + return; + } + + uint32_t sourceCX = std::max(obs_source_get_width(source), 1u); + uint32_t sourceCY = std::max(obs_source_get_height(source), 1u); + + int x, y; + int newCX, newCY; + float scale; + + GetScaleAndCenterPos(sourceCX, sourceCY, cx, cy, x, y, scale); + + newCX = int(scale * float(sourceCX)); + newCY = int(scale * float(sourceCY)); + + gs_viewport_push(); + gs_projection_push(); + const bool previous = gs_set_linear_srgb(true); + + gs_ortho(0.0f, float(sourceCX), 0.0f, float(sourceCY), -100.0f, 100.0f); + gs_set_viewport(x, y, newCX, newCY); + obs_source_video_render(source); + + gs_set_linear_srgb(previous); + gs_projection_pop(); + gs_viewport_pop(); + + view->setSourceWidth(sourceCX); + view->setSourceHeight(sourceCY); +} + +void OBSSourceWidgetView::setSource(obs_source_t *source) +{ + if (weakSource) { + obs_source_t *prevSource = OBSGetStrongRef(weakSource); + if (prevSource) { + obs_source_dec_showing(prevSource); + } + } + + weakSource = OBSGetWeakRef(source); + obs_source_inc_showing(source); + + enum obs_source_type type = obs_source_get_type(source); + bool drawable_type = type == OBS_SOURCE_TYPE_INPUT || type == OBS_SOURCE_TYPE_SCENE; + + auto addDrawCallback = [this]() { + obs_display_add_draw_callback(GetDisplay(), OBSRender, this); + }; + + uint32_t caps = obs_source_get_output_flags(source); + if ((caps & OBS_SOURCE_VIDEO) != 0) { + if (drawable_type) { + connect(this, &OBSQTDisplay::DisplayCreated, addDrawCallback); + } + } +} diff --git a/frontend/widgets/OBSSourceWidget.hpp b/frontend/widgets/OBSSourceWidget.hpp new file mode 100644 index 00000000000000..901f9b0c66b786 --- /dev/null +++ b/frontend/widgets/OBSSourceWidget.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include "OBSQTDisplay.hpp" + +#include +#include + +class OBSSourceWidgetView; + +class OBSSourceWidget : public QFrame { + Q_OBJECT + +private: + OBSSourceWidgetView *sourceView = nullptr; + QVBoxLayout *layout; + + double fixedAspectRatio; + + void moveEvent(QMoveEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + +public: + OBSSourceWidget(QWidget *parent); + OBSSourceWidget(QWidget *parent, obs_source_t *source); + ~OBSSourceWidget(); + + void setFixedAspectRatio(double ratio); + void setSource(obs_source_t *source); + + void resizeSourceView(); + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; +}; + +class OBSSourceWidgetView : public OBSQTDisplay { + Q_OBJECT + +private: + OBSWeakSource weakSource = nullptr; + + static void OBSRender(void *data, uint32_t cx, uint32_t cy); + + QRect prevGeometry; + + int32_t sourceWidth_; + int32_t sourceHeight_; + +public: + OBSSourceWidgetView(OBSSourceWidget *parent, obs_source_t *source); + ~OBSSourceWidgetView(); + + void setSource(obs_source_t *source); + void setSourceWidth(int width); + void setSourceHeight(int height); + int sourceWidth() { return sourceWidth_; } + int sourceHeight() { return sourceHeight_; } + + OBSSource GetSource(); + +signals: + void viewReady(); +};