From 694680e8c09f760740029921b1d66306ce6ce0e7 Mon Sep 17 00:00:00 2001 From: Warchamp7 Date: Mon, 26 Jan 2026 14:39:20 -0500 Subject: [PATCH] frontend: Revert Add Source dialog update This reverts commit 5fc24769130a9c0850452fb30adbb452c8662840. --- frontend/cmake/ui-components.cmake | 6 - frontend/cmake/ui-utility.cmake | 3 - frontend/components/FlowFrame.cpp | 134 ---- frontend/components/FlowFrame.hpp | 37 - frontend/components/FlowLayout.cpp | 170 ---- frontend/components/FlowLayout.hpp | 40 - frontend/components/SourceSelectButton.cpp | 185 ----- frontend/components/SourceSelectButton.hpp | 68 -- frontend/data/locale/en-US.ini | 17 +- frontend/data/themes/Yami.obt | 28 - frontend/data/themes/Yami_Acri.ovt | 11 - frontend/data/themes/Yami_Classic.ovt | 13 - frontend/data/themes/Yami_Light.ovt | 32 +- frontend/data/themes/Yami_Rachni.ovt | 6 - frontend/dialogs/OBSBasicSourceSelect.cpp | 880 ++++++--------------- frontend/dialogs/OBSBasicSourceSelect.hpp | 60 +- frontend/forms/OBSBasicSourceSelect.ui | 740 ++--------------- frontend/utility/ResizeSignaler.hpp | 39 - frontend/utility/ScreenshotObj.cpp | 242 ++---- frontend/utility/ScreenshotObj.hpp | 42 +- frontend/utility/ThumbnailManager.cpp | 256 ------ frontend/utility/ThumbnailManager.hpp | 106 --- frontend/widgets/OBSBasic.cpp | 2 - frontend/widgets/OBSBasic.hpp | 15 +- frontend/widgets/OBSBasic_Dropfiles.cpp | 9 - frontend/widgets/OBSBasic_SceneItems.cpp | 157 ++-- 26 files changed, 546 insertions(+), 2752 deletions(-) delete mode 100644 frontend/components/FlowFrame.cpp delete mode 100644 frontend/components/FlowFrame.hpp delete mode 100644 frontend/components/FlowLayout.cpp delete mode 100644 frontend/components/FlowLayout.hpp delete mode 100644 frontend/components/SourceSelectButton.cpp delete mode 100644 frontend/components/SourceSelectButton.hpp delete mode 100644 frontend/utility/ResizeSignaler.hpp delete mode 100644 frontend/utility/ThumbnailManager.cpp delete mode 100644 frontend/utility/ThumbnailManager.hpp diff --git a/frontend/cmake/ui-components.cmake b/frontend/cmake/ui-components.cmake index 34f4c0e7f..0ddfe9d1f 100644 --- a/frontend/cmake/ui-components.cmake +++ b/frontend/cmake/ui-components.cmake @@ -36,10 +36,6 @@ target_sources( components/DisplayCaptureToolbar.cpp components/DisplayCaptureToolbar.hpp components/EditWidget.hpp - components/FlowFrame.cpp - components/FlowFrame.hpp - components/FlowLayout.cpp - components/FlowLayout.hpp components/FocusList.cpp components/FocusList.hpp components/GameCaptureToolbar.cpp @@ -67,8 +63,6 @@ target_sources( components/SceneTree.hpp components/SilentUpdateCheckBox.hpp components/SilentUpdateSpinBox.hpp - components/SourceSelectButton.cpp - components/SourceSelectButton.hpp components/SourceToolbar.cpp components/SourceToolbar.hpp components/SourceTree.cpp diff --git a/frontend/cmake/ui-utility.cmake b/frontend/cmake/ui-utility.cmake index aa9c6cd58..8b02d5ec7 100644 --- a/frontend/cmake/ui-utility.cmake +++ b/frontend/cmake/ui-utility.cmake @@ -49,7 +49,6 @@ target_sources( utility/RemuxQueueModel.hpp utility/RemuxWorker.cpp utility/RemuxWorker.hpp - utility/ResizeSignaler.hpp utility/SceneRenameDelegate.cpp utility/SceneRenameDelegate.hpp utility/ScreenshotObj.cpp @@ -59,8 +58,6 @@ target_sources( utility/SimpleOutput.hpp utility/StartMultiTrackVideoStreamingGuard.hpp utility/SurfaceEventFilter.hpp - utility/ThumbnailManager.cpp - utility/ThumbnailManager.hpp utility/VCamConfig.hpp utility/audio-encoders.cpp utility/audio-encoders.hpp diff --git a/frontend/components/FlowFrame.cpp b/frontend/components/FlowFrame.cpp deleted file mode 100644 index 3974f1d85..000000000 --- a/frontend/components/FlowFrame.cpp +++ /dev/null @@ -1,134 +0,0 @@ -/****************************************************************************** - Copyright (C) 2025 by Taylor Giampaolo - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -******************************************************************************/ - -#include - -#include -#include - -FlowFrame::FlowFrame(QWidget *parent) : QFrame(parent) -{ - layout = new FlowLayout(this); - setLayout(layout); -} - -void FlowFrame::keyPressEvent(QKeyEvent *event) -{ - QWidget *focused = focusWidget(); - if (!focused) { - return; - } - - int index = -1; - for (int i = 0; i < layout->count(); ++i) { - if (!layout->itemAt(i)->widget()) { - continue; - } - - auto focusProxy = layout->itemAt(i)->widget()->focusProxy(); - if (layout->itemAt(i)->widget() == focused || focusProxy == focused) { - if (focusProxy == focused) { - focused = layout->itemAt(i)->widget(); - } - - index = i; - break; - } - } - - if (index == -1) { - return; - } - - const QRect focusedRect = focused->geometry(); - QWidget *nextFocus = nullptr; - - switch (event->key()) { - case Qt::Key_Right: - case Qt::Key_Down: - case Qt::Key_Left: - case Qt::Key_Up: { - // Find next widget in the given direction - int bestDistance = INT_MAX; - for (int i = 0; i < layout->count(); ++i) { - if (i == index) { - continue; - } - - QWidget *widget = layout->itemAt(i)->widget(); - const QRect rect = widget->geometry(); - - bool isCandidate = false; - int distance = INT_MAX; - - switch (event->key()) { - case Qt::Key_Right: - if (rect.left() > focusedRect.right()) { - distance = (rect.left() - focusedRect.right()) + - qAbs(rect.center().y() - focusedRect.center().y()); - isCandidate = true; - } - break; - case Qt::Key_Left: - if (rect.right() < focusedRect.left()) { - distance = (focusedRect.left() - rect.right()) + - qAbs(rect.center().y() - focusedRect.center().y()); - isCandidate = true; - } - break; - case Qt::Key_Down: - if (rect.top() > focusedRect.bottom()) { - distance = (rect.top() - focusedRect.bottom()) + - qAbs(rect.center().x() - focusedRect.center().x()); - isCandidate = true; - } - break; - case Qt::Key_Up: - if (rect.bottom() < focusedRect.top()) { - distance = (focusedRect.top() - rect.bottom()) + - qAbs(rect.center().x() - focusedRect.center().x()); - isCandidate = true; - } - break; - } - - if (isCandidate && distance < bestDistance) { - bestDistance = distance; - nextFocus = widget; - } - } - break; - } - default: - QWidget::keyPressEvent(event); - return; - } - - if (nextFocus) { - nextFocus->setFocus(); - - QWidget *scrollParent = nextFocus->parentWidget(); - while (scrollParent) { - QScrollArea *scrollArea = qobject_cast(scrollParent); - if (scrollArea) { - scrollArea->ensureWidgetVisible(nextFocus, 20, 20); - break; - } - scrollParent = scrollParent->parentWidget(); - } - } -} diff --git a/frontend/components/FlowFrame.hpp b/frontend/components/FlowFrame.hpp deleted file mode 100644 index 0ad1164ce..000000000 --- a/frontend/components/FlowFrame.hpp +++ /dev/null @@ -1,37 +0,0 @@ -/****************************************************************************** - Copyright (C) 2025 by Taylor Giampaolo - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -******************************************************************************/ - -#pragma once - -#include "FlowLayout.hpp" - -#include - -class FlowFrame : public QFrame { - Q_OBJECT - -public: - explicit FlowFrame(QWidget *parent = nullptr); - - FlowLayout *flowLayout() const { return layout; } - -protected: - void keyPressEvent(QKeyEvent *event) override; - -private: - FlowLayout *layout; -}; diff --git a/frontend/components/FlowLayout.cpp b/frontend/components/FlowLayout.cpp deleted file mode 100644 index 448e168f9..000000000 --- a/frontend/components/FlowLayout.cpp +++ /dev/null @@ -1,170 +0,0 @@ -/****************************************************************************** - Example provided by Qt - - - Copyright (C) 2016 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -******************************************************************************/ - -#include "FlowLayout.hpp" - -#include - -FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing) - : QLayout(parent), - m_hSpace(hSpacing), - m_vSpace(vSpacing) -{ - setContentsMargins(margin, margin, margin, margin); -} - -FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing) : m_hSpace(hSpacing), m_vSpace(vSpacing) -{ - setContentsMargins(margin, margin, margin, margin); -} - -FlowLayout::~FlowLayout() -{ - QLayoutItem *item; - while ((item = takeAt(0))) { - delete item; - } -} - -void FlowLayout::addItem(QLayoutItem *item) -{ - itemList.append(item); -} - -int FlowLayout::horizontalSpacing() const -{ - if (m_hSpace >= 0) { - return m_hSpace; - } else { - return smartSpacing(QStyle::PM_LayoutHorizontalSpacing); - } -} - -int FlowLayout::verticalSpacing() const -{ - if (m_vSpace >= 0) { - return m_vSpace; - } else { - return smartSpacing(QStyle::PM_LayoutVerticalSpacing); - } -} - -int FlowLayout::count() const -{ - return itemList.size(); -} - -QLayoutItem *FlowLayout::itemAt(int index) const -{ - return itemList.value(index); -} - -QLayoutItem *FlowLayout::takeAt(int index) -{ - if (index >= 0 && index < itemList.size()) { - return itemList.takeAt(index); - } - return nullptr; -} - -Qt::Orientations FlowLayout::expandingDirections() const -{ - return {}; -} - -bool FlowLayout::hasHeightForWidth() const -{ - return true; -} - -int FlowLayout::heightForWidth(int width) const -{ - int height = doLayout(QRect(0, 0, width, 0), true); - return height; -} - -void FlowLayout::setGeometry(const QRect &rect) -{ - QLayout::setGeometry(rect); - doLayout(rect, false); -} - -QSize FlowLayout::sizeHint() const -{ - return minimumSize(); -} - -QSize FlowLayout::minimumSize() const -{ - QSize size; - for (const QLayoutItem *item : std::as_const(itemList)) { - size = size.expandedTo(item->minimumSize()); - } - - const QMargins margins = contentsMargins(); - size += QSize(margins.left() + margins.right(), margins.top() + margins.bottom()); - return size; -} - -int FlowLayout::doLayout(const QRect &rect, bool testOnly) const -{ - int left; - int top; - int right; - int bottom; - - getContentsMargins(&left, &top, &right, &bottom); - QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom); - int x = effectiveRect.x(); - int y = effectiveRect.y(); - int lineHeight = 0; - - for (QLayoutItem *item : std::as_const(itemList)) { - const QWidget *wid = item->widget(); - int spaceX = horizontalSpacing(); - if (spaceX == -1) { - spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, - Qt::Horizontal); - } - - int spaceY = verticalSpacing(); - if (spaceY == -1) { - spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, - Qt::Vertical); - } - - int nextX = x + item->sizeHint().width() + spaceX; - if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) { - x = effectiveRect.x(); - y = y + lineHeight + spaceY; - nextX = x + item->sizeHint().width() + spaceX; - lineHeight = 0; - } - - if (!testOnly) { - item->setGeometry(QRect(QPoint(x, y), item->sizeHint())); - } - - x = nextX; - lineHeight = qMax(lineHeight, item->sizeHint().height()); - } - return y + lineHeight - rect.y() + bottom; -} - -int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const -{ - QObject *parent = this->parent(); - if (!parent) { - return -1; - } else if (parent->isWidgetType()) { - QWidget *pw = static_cast(parent); - return pw->style()->pixelMetric(pm, nullptr, pw); - } else { - return static_cast(parent)->spacing(); - } -} diff --git a/frontend/components/FlowLayout.hpp b/frontend/components/FlowLayout.hpp deleted file mode 100644 index 9d3cb32be..000000000 --- a/frontend/components/FlowLayout.hpp +++ /dev/null @@ -1,40 +0,0 @@ -/****************************************************************************** - Example provided by Qt - - - Copyright (C) 2016 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -******************************************************************************/ - -#pragma once - -#include -#include - -class FlowLayout : public QLayout { -public: - explicit FlowLayout(QWidget *parent, int margin = -1, int hSpacing = -1, int vSpacing = -1); - explicit FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1); - ~FlowLayout(); - - void addItem(QLayoutItem *item) override; - int horizontalSpacing() const; - int verticalSpacing() const; - Qt::Orientations expandingDirections() const override; - bool hasHeightForWidth() const override; - int heightForWidth(int) const override; - int count() const override; - QLayoutItem *itemAt(int index) const override; - QSize minimumSize() const override; - void setGeometry(const QRect &rect) override; - QSize sizeHint() const override; - QLayoutItem *takeAt(int index) override; - -private: - int doLayout(const QRect &rect, bool testOnly) const; - int smartSpacing(QStyle::PixelMetric pm) const; - - QList itemList; - int m_hSpace; - int m_vSpace; -}; diff --git a/frontend/components/SourceSelectButton.cpp b/frontend/components/SourceSelectButton.cpp deleted file mode 100644 index dd08dfaa3..000000000 --- a/frontend/components/SourceSelectButton.cpp +++ /dev/null @@ -1,185 +0,0 @@ -/****************************************************************************** - Copyright (C) 2025 by Taylor Giampaolo - Lain Bailey - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -******************************************************************************/ - -#include "SourceSelectButton.hpp" - -#include -#include - -#include -#include -#include -#include -#include - -SourceSelectButton::SourceSelectButton(obs_source_t *source_, QWidget *parent) : QFrame(parent) -{ - OBSSource source = source_; - weakSource = OBSGetWeakRef(source); - const char *sourceName = obs_source_get_name(source); - - setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - - button = new QPushButton(this); - button->setCheckable(true); - button->setAttribute(Qt::WA_Moved); - button->setAccessibleName(sourceName); - button->show(); - - layout = new QVBoxLayout(); - layout->setContentsMargins(0, 0, 0, 0); - layout->setSpacing(0); - setLayout(layout); - - label = new QLabel(sourceName); - label->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); - label->setAttribute(Qt::WA_TransparentForMouseEvents); - label->setObjectName("name"); - - image = new QLabel(this); - image->setObjectName("thumbnail"); - image->setAttribute(Qt::WA_TransparentForMouseEvents); - image->setMinimumSize(160, 90); - image->setMaximumSize(160, 90); - image->setAlignment(Qt::AlignCenter); - std::optional cachedThumbnail = OBSBasic::Get()->thumbnails()->getCachedThumbnail(source); - - if (cachedThumbnail.has_value()) { - thumbnailUpdated(*cachedThumbnail); - } else { - setDefaultThumbnail(); - } - - layout->addWidget(image); - layout->addWidget(label); - - button->setFixedSize(width(), height()); - button->move(0, 0); - - setFocusPolicy(Qt::StrongFocus); - setFocusProxy(button); - - connect(button, &QAbstractButton::pressed, this, &SourceSelectButton::buttonPressed); -} - -SourceSelectButton::~SourceSelectButton() {} - -QPointer SourceSelectButton::getButton() -{ - return button; -} - -QString SourceSelectButton::text() -{ - return label->text(); -} - -void SourceSelectButton::resizeEvent(QResizeEvent *) -{ - button->setFixedSize(width(), height()); - button->move(0, 0); -} - -void SourceSelectButton::moveEvent(QMoveEvent *) -{ - button->setFixedSize(width(), height()); - button->move(0, 0); -} - -void SourceSelectButton::buttonPressed() -{ - dragStartPosition = QCursor::pos(); -} - -void SourceSelectButton::setDefaultThumbnail() -{ - OBSSource source = OBSGetStrongRef(weakSource); - if (source) { - const char *id = obs_source_get_id(source); - QIcon icon = OBSBasic::Get()->GetSourceIcon(id); - image->setPixmap(icon.pixmap(45, 45)); - } -} - -void SourceSelectButton::mouseMoveEvent(QMouseEvent *event) -{ - if (!(event->buttons() & Qt::LeftButton)) { - return; - } - - if ((event->pos() - dragStartPosition).manhattanLength() < QApplication::startDragDistance()) { - return; - } - - QMimeData *mimeData = new QMimeData; - OBSSource source = OBSGetStrongRef(weakSource); - if (source) { - std::string uuid = obs_source_get_uuid(source); - mimeData->setData("application/x-obs-source-uuid", uuid.c_str()); - - QDrag *drag = new QDrag(this); - drag->setMimeData(mimeData); - drag->setPixmap(this->grab()); - drag->exec(Qt::CopyAction); - } -} - -void SourceSelectButton::setRectVisible(bool visible) -{ - OBSSource source = OBSGetStrongRef(weakSource); - if (!source) { - return; - } - - if (rectVisible != visible) { - rectVisible = visible; - - if (visible) { - uint32_t flags = obs_source_get_output_flags(source); - bool hasVideo = (flags & OBS_SOURCE_VIDEO) == OBS_SOURCE_VIDEO; - if (hasVideo) { - thumbnail = OBSBasic::Get()->thumbnails()->getThumbnail(source); - connect(thumbnail.get(), &Thumbnail::updateThumbnail, this, - &SourceSelectButton::thumbnailUpdated); - thumbnailUpdated(thumbnail->getPixmap()); - } - } else { - thumbnail.reset(); - } - } - - if (preload && !rectVisible) { - OBSBasic::Get()->thumbnails()->preloadThumbnail(source, this, - [=](QPixmap pixmap) { thumbnailUpdated(pixmap); }); - } - preload = false; -} - -void SourceSelectButton::setPreload(bool preload) -{ - this->preload = preload; -} - -void SourceSelectButton::thumbnailUpdated(QPixmap pixmap) -{ - if (!pixmap.isNull()) { - image->setPixmap(pixmap.scaled(160, 90, Qt::KeepAspectRatio, Qt::SmoothTransformation)); - } else { - setDefaultThumbnail(); - } -} diff --git a/frontend/components/SourceSelectButton.hpp b/frontend/components/SourceSelectButton.hpp deleted file mode 100644 index 083acfa38..000000000 --- a/frontend/components/SourceSelectButton.hpp +++ /dev/null @@ -1,68 +0,0 @@ -/****************************************************************************** - Copyright (C) 2025 by Taylor Giampaolo - Lain Bailey - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -******************************************************************************/ - -#pragma once - -#include - -#include -#include -#include -#include -#include - -class QLabel; -class Thumbnail; - -class SourceSelectButton : public QFrame { - Q_OBJECT - -public: - SourceSelectButton(obs_source_t *source, QWidget *parent = nullptr); - ~SourceSelectButton(); - - QPointer getButton(); - QString text(); - - void setRectVisible(bool visible); - void setPreload(bool preload); - -protected: - void resizeEvent(QResizeEvent *event) override; - void moveEvent(QMoveEvent *event) override; - void mouseMoveEvent(QMouseEvent *event) override; - void buttonPressed(); - -private: - OBSWeakSource weakSource; - std::shared_ptr thumbnail; - QPointer image; - - QPushButton *button = nullptr; - QVBoxLayout *layout = nullptr; - QLabel *label = nullptr; - bool preload = true; - bool rectVisible = false; - - void setDefaultThumbnail(); - - QPoint dragStartPosition; - -private slots: - void thumbnailUpdated(QPixmap pixmap); -}; diff --git a/frontend/data/locale/en-US.ini b/frontend/data/locale/en-US.ini index b58cda8ee..8ed58032e 100644 --- a/frontend/data/locale/en-US.ini +++ b/frontend/data/locale/en-US.ini @@ -637,23 +637,15 @@ RenameProfile.Title="Rename Profile" Basic.Main.MixerRename.Title="Rename Audio Source" Basic.Main.MixerRename.Text="Please enter the name of the audio source" + # preview window disabled Basic.Main.PreviewDisabled="Preview is currently disabled" # add source dialog -Basic.SourceSelect="Add Source" -Basic.SourceSelect.SelectType="Source Type" -Basic.SourceSelect.Recent="Recently Added" -Basic.SourceSelect.NewSource="Create a New Source" -Basic.SourceSelect.Existing="Add an Existing Source" -Basic.SourceSelect.ExistingButton="Add Existing" -Basic.SourceSelect.ExistingButton.Multiple="Add %1 Existing" -Basic.SourceSelect.CreateButton="Create New" +Basic.SourceSelect="Create/Select Source" +Basic.SourceSelect.CreateNew="Create new" +Basic.SourceSelect.AddExisting="Add Existing" Basic.SourceSelect.AddVisible="Make source visible" -Basic.SourceSelect.NoExisting="No existing %1 sources" -Basic.SourceSelect.Accessible.SourceName="Source Name" -Basic.SourceSelect.Accessible.Existing="Add an Existing Source" -Basic.SourceSelect.Deprecated.Create="This source type is marked as deprecated and does allow creating new sources." # source box Basic.Main.Sources.Visibility="Visibility" @@ -791,7 +783,6 @@ Basic.Main.ShowContextBar="Show Source Toolbar" Basic.Main.HideContextBar="Hide Source Toolbar" Basic.Main.StopVirtualCam="Stop Virtual Camera" Basic.Main.Group="Group %1" -Basic.Main.NewGroup="New Group" Basic.Main.GroupItems="Group Selected Items" Basic.Main.Ungroup="Ungroup" Basic.Main.GridMode="Grid Mode" diff --git a/frontend/data/themes/Yami.obt b/frontend/data/themes/Yami.obt index 224e3397b..e3bc0bc82 100644 --- a/frontend/data/themes/Yami.obt +++ b/frontend/data/themes/Yami.obt @@ -2415,34 +2415,6 @@ OBSBasicAdvAudio #scrollAreaWidgetContents { border: 1px solid var(--input_border_hover); } -/* Add Source Dialog */ -SourceSelectButton { - text-align: center; - padding: var(--padding_base) var(--padding_base); - margin: var(--spacing_base); -} - -SourceSelectButton QLabel { - padding: var(--padding_large) 0; - text-align: center; -} - -SourceSelectButton #thumbnail { - background: var(--grey6); - border: 1px solid var(--grey4); - padding: 0; - margin-top: var(--spacing_base); -} - -SourceSelectButton QPushButton { - background: var(--grey5); -} - -SourceSelectButton QPushButton:checked:focus, -SourceSelectButton QPushButton:focus { - border-color: var(--white3); -} - /* Idian Widgets */ idian--Group { border-radius: var(--border_radius); diff --git a/frontend/data/themes/Yami_Acri.ovt b/frontend/data/themes/Yami_Acri.ovt index 9d4981d93..8d054a4d0 100644 --- a/frontend/data/themes/Yami_Acri.ovt +++ b/frontend/data/themes/Yami_Acri.ovt @@ -234,14 +234,3 @@ idian--ToggleSwitch { qproperty-background_checked: var(--button_bg); qproperty-background_checked_hover: var(--primary_light); } - -/* Add Source Dialog */ -SourceSelectButton QPushButton, -SourceSelectButton #thumbnail { - border-color: var(--grey3); -} - -SourceSelectButton QPushButton:checked { - background: var(--button_bg_red); - border-color: var(--button_bg_red_hover); -} diff --git a/frontend/data/themes/Yami_Classic.ovt b/frontend/data/themes/Yami_Classic.ovt index bbb7b2dd5..b1da40145 100644 --- a/frontend/data/themes/Yami_Classic.ovt +++ b/frontend/data/themes/Yami_Classic.ovt @@ -22,7 +22,6 @@ --primary: rgb(25,52,76); --primary_light: rgb(33,71,109); - --primary_dark: rgb(19, 40, 58); /* Layout */ --padding_large: min(max(0px, calc(1px * var(--padding_base_value))), 5px); @@ -84,13 +83,6 @@ --padding_menu_y: calc(3px + calc(1 * var(--padding_base_value))); } -/* --------------------- */ -/* General Styling Hints */ - -.dialog-frame { - background: var(--bg_window); -} - QStatusBar { background-color: var(--bg_window); } @@ -288,8 +280,3 @@ VolumeMeter { OBSBasicStats { background: var(--bg_window); } - -/* Add Source Dialog */ -SourceSelectButton QPushButton { - background-color: var(--grey7); -} diff --git a/frontend/data/themes/Yami_Light.ovt b/frontend/data/themes/Yami_Light.ovt index 550db0321..2cbcbe2f7 100644 --- a/frontend/data/themes/Yami_Light.ovt +++ b/frontend/data/themes/Yami_Light.ovt @@ -10,7 +10,7 @@ --grey1: rgb(140,140,140); --grey2: rgb(254,254,254); --grey3: rgb(254,254,254); - --grey4: rgb(245,245,245); + --grey4: rgb(243,243,243); --grey5: rgb(236,236,236); --grey6: rgb(229,229,229); --grey7: rgb(211,211,211); @@ -18,13 +18,13 @@ --primary: rgb(140,181,255); --primary_light: rgb(178,207,255); - --primary_dark: rgb(122, 164, 243); + --primary_dark: rgb(22,31,65); --bg_window: var(--grey7); --bg_base: var(--grey6); --bg_preview: var(--grey8); - --text: #030303 + --text: var(--black1); --text_light: var(--black3); --text_muted: var(--black4); @@ -34,10 +34,7 @@ --input_bg_hover: var(--grey3); --input_bg_focus: var(--grey3); - --input_border_hover: var(--black5); - --button_bg_disabled: var(--grey7); - --button_border_hover: var(--black5); --separator_hover: var(--black1); @@ -47,13 +44,6 @@ --scrollbar_border: var(--grey7); } -/* --------------------- */ -/* General Styling Hints */ - -.button-primary:hover { - border-color: var(--button_border_hover); -} - VolumeMeter { qproperty-backgroundNominalColor: var(--green4); qproperty-backgroundWarningColor: var(--yellow4); @@ -364,19 +354,3 @@ idian--ExpandButton::indicator { idian--ExpandButton::indicator:checked { image: url(theme:Light/up.svg); } - -/* Add Source Dialog */ -SourceSelectButton QPushButton, -SourceSelectButton #thumbnail { - border-color: var(--grey3); -} - -SourceSelectButton QPushButton:checked { - background: var(--primary); - border-color: var(--primary_light); -} - -SourceSelectButton QPushButton:checked:focus, -SourceSelectButton QPushButton:focus { - border-color: var(--black3); -} diff --git a/frontend/data/themes/Yami_Rachni.ovt b/frontend/data/themes/Yami_Rachni.ovt index f79bf42c0..d51a0d2ae 100644 --- a/frontend/data/themes/Yami_Rachni.ovt +++ b/frontend/data/themes/Yami_Rachni.ovt @@ -233,9 +233,3 @@ VolumeMeter { qproperty-majorTickColor: palette(window-text); qproperty-minorTickColor: palette(mid); } - -/* Add Source Dialog */ -SourceSelectButton QPushButton, -SourceSelectButton #thumbnail { - border-color: var(--grey3); -} diff --git a/frontend/dialogs/OBSBasicSourceSelect.cpp b/frontend/dialogs/OBSBasicSourceSelect.cpp index ab8d366f2..44c4ee766 100644 --- a/frontend/dialogs/OBSBasicSourceSelect.cpp +++ b/frontend/dialogs/OBSBasicSourceSelect.cpp @@ -1,6 +1,5 @@ /****************************************************************************** Copyright (C) 2023 by Lain Bailey - Copyright (C) 2025 by Taylor Giampaolo This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,19 +17,12 @@ #include "OBSBasicSourceSelect.hpp" -#include -#include -#include - -#include "qt-wrappers.hpp" - -#include -#include +#include #include "moc_OBSBasicSourceSelect.cpp" struct AddSourceData { - // Input data + /* Input data */ obs_source_t *source; bool visible; obs_transform_info *transform = nullptr; @@ -46,47 +38,85 @@ struct AddSourceData { uint32_t hide_transition_duration = 300; OBSData private_settings; - // Return data + /* Return data */ obs_sceneitem_t *scene_item = nullptr; }; -namespace { -QString getSourceDisplayName(QString type) +bool OBSBasicSourceSelect::EnumSources(void *data, obs_source_t *source) { - if (type == "scene") { - return QTStr("Basic.Scene"); - } + if (obs_source_is_hidden(source)) + return true; - const char *inputChar = obs_get_latest_input_type_id(type.toUtf8().constData()); - const char *displayChar = obs_source_get_display_name(inputChar); - std::string displayId = (displayChar) ? displayChar : ""; + OBSBasicSourceSelect *window = static_cast(data); + const char *name = obs_source_get_name(source); + const char *id = obs_source_get_unversioned_id(source); - if (!displayId.empty()) { - return QString::fromStdString(displayId); - } else { - return QString(); - } + if (strcmp(id, window->id) == 0) + window->ui->sourceList->addItem(QT_UTF8(name)); + + return true; } -std::string getNewSourceName(std::string_view name) +bool OBSBasicSourceSelect::EnumGroups(void *data, obs_source_t *source) { - std::string newName{name}; - int suffix = 1; + OBSBasicSourceSelect *window = static_cast(data); + const char *name = obs_source_get_name(source); + const char *id = obs_source_get_unversioned_id(source); - for (;;) { - OBSSourceAutoRelease existing_source = obs_get_source_by_name(newName.c_str()); - if (!existing_source) { - break; - } + if (strcmp(id, window->id) == 0) { + OBSBasic *main = OBSBasic::Get(); + OBSScene scene = main->GetCurrentScene(); - char nextName[256]; - std::snprintf(nextName, sizeof(nextName), "%s %d", name.data(), ++suffix); - newName = nextName; + obs_sceneitem_t *existing = obs_scene_get_group(scene, name); + if (!existing) + window->ui->sourceList->addItem(QT_UTF8(name)); } - return newName; + return true; +} + +void OBSBasicSourceSelect::OBSSourceAdded(void *data, calldata_t *calldata) +{ + OBSBasicSourceSelect *window = static_cast(data); + obs_source_t *source = (obs_source_t *)calldata_ptr(calldata, "source"); + + QMetaObject::invokeMethod(window, "SourceAdded", Q_ARG(OBSSource, source)); +} + +void OBSBasicSourceSelect::OBSSourceRemoved(void *data, calldata_t *calldata) +{ + OBSBasicSourceSelect *window = static_cast(data); + obs_source_t *source = (obs_source_t *)calldata_ptr(calldata, "source"); + + QMetaObject::invokeMethod(window, "SourceRemoved", Q_ARG(OBSSource, source)); +} + +void OBSBasicSourceSelect::SourceAdded(OBSSource source) +{ + const char *name = obs_source_get_name(source); + const char *sourceId = obs_source_get_unversioned_id(source); + + if (strcmp(sourceId, id) != 0) + return; + + ui->sourceList->addItem(name); +} + +void OBSBasicSourceSelect::SourceRemoved(OBSSource source) +{ + const char *name = obs_source_get_name(source); + const char *sourceId = obs_source_get_unversioned_id(source); + + if (strcmp(sourceId, id) != 0) + return; + + QList items = ui->sourceList->findItems(name, Qt::MatchFixedString); + + if (!items.count()) + return; + + delete items[0]; } -} // namespace static void AddSource(void *_data, obs_scene_t *scene) { @@ -95,21 +125,16 @@ static void AddSource(void *_data, obs_scene_t *scene) sceneitem = obs_scene_add(scene, data->source); - if (data->transform != nullptr) { + if (data->transform != nullptr) obs_sceneitem_set_info2(sceneitem, data->transform); - } - if (data->crop != nullptr) { + if (data->crop != nullptr) obs_sceneitem_set_crop(sceneitem, data->crop); - } - if (data->blend_method != nullptr) { + if (data->blend_method != nullptr) obs_sceneitem_set_blending_method(sceneitem, *data->blend_method); - } - if (data->blend_mode != nullptr) { + if (data->blend_mode != nullptr) obs_sceneitem_set_blending_mode(sceneitem, *data->blend_mode); - } - if (data->scale_type != nullptr) { + if (data->scale_type != nullptr) obs_sceneitem_set_scale_filter(sceneitem, *data->scale_type); - } if (data->show_transition_id && *data->show_transition_id) { OBSSourceAutoRelease source = obs_source_create(data->show_transition_id, data->show_transition_id, @@ -140,23 +165,40 @@ static void AddSource(void *_data, obs_scene_t *scene) data->scene_item = sceneitem; } +char *get_new_source_name(const char *name, const char *format) +{ + struct dstr new_name = {0}; + int inc = 0; + + dstr_copy(&new_name, name); + + for (;;) { + OBSSourceAutoRelease existing_source = obs_get_source_by_name(new_name.array); + if (!existing_source) + break; + + dstr_printf(&new_name, format, name, ++inc + 1); + } + + return new_name.array; +} + static void AddExisting(OBSSource source, bool visible, bool duplicate, SourceCopyInfo *info = nullptr) { OBSBasic *main = OBSBasic::Get(); OBSScene scene = main->GetCurrentScene(); - if (!scene) { + if (!scene) return; - } if (duplicate) { OBSSource from = source; - std::string new_name = getNewSourceName(obs_source_get_name(source)); - source = obs_source_duplicate(from, new_name.c_str(), false); + char *new_name = get_new_source_name(obs_source_get_name(source), "%s %d"); + source = obs_source_duplicate(from, new_name, false); obs_source_release(source); + bfree(new_name); - if (!source) { + if (!source) return; - } } AddSourceData data; @@ -197,9 +239,8 @@ bool AddNew(QWidget *parent, const char *id, const char *name, const bool visibl OBSBasic *main = OBSBasic::Get(); OBSScene scene = main->GetCurrentScene(); bool success = false; - if (!scene) { + if (!scene) return false; - } OBSSourceAutoRelease source = obs_get_source_by_name(name); if (source && parent) { @@ -221,7 +262,7 @@ bool AddNew(QWidget *parent, const char *id, const char *name, const bool visibl newSource = source; newSceneItem = data.scene_item; - // Set monitoring if source monitors by default + /* set monitoring if source monitors by default */ uint32_t flags = obs_source_get_output_flags(source); if ((flags & OBS_SOURCE_MONITOR_BY_DEFAULT) != 0) { obs_source_set_monitoring_type(source, OBS_MONITORING_TYPE_MONITOR_ONLY); @@ -234,579 +275,18 @@ bool AddNew(QWidget *parent, const char *id, const char *name, const bool visibl return success; } -OBSBasicSourceSelect::OBSBasicSourceSelect(OBSBasic *parent, undo_stack &undo_s) - : QDialog(parent), - ui(new Ui::OBSBasicSourceSelect), - undo_s(undo_s) -{ - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - - ui->setupUi(this); - - existingFlowLayout = ui->existingListFrame->flowLayout(); - existingFlowLayout->setContentsMargins(0, 0, 0, 0); - existingFlowLayout->setSpacing(0); - - /* The scroll viewport is not accessible via Designer, so we have to disable autoFillBackground here. - * - * Additionally when Qt calls setWidget on a scrollArea to set the contents widget, it force sets - * autoFillBackground to true overriding whatever is set in Designer so we have to do that here too. - */ - ui->existingScrollArea->viewport()->setAutoFillBackground(false); - ui->existingScrollContents->setAutoFillBackground(false); - - auto resizeSignaler = new ResizeSignaler(ui->existingScrollArea); - ui->existingScrollArea->installEventFilter(resizeSignaler); - - connect(resizeSignaler, &ResizeSignaler::resized, this, &OBSBasicSourceSelect::checkSourceVisibility); - connect(ui->existingScrollArea->verticalScrollBar(), &QScrollBar::valueChanged, this, - &OBSBasicSourceSelect::checkSourceVisibility); - connect(ui->existingScrollArea->horizontalScrollBar(), &QScrollBar::valueChanged, this, - &OBSBasicSourceSelect::checkSourceVisibility); - - ui->createNewFrame->setVisible(false); - ui->deprecatedCreateLabel->setVisible(false); - ui->deprecatedCreateLabel->setProperty("class", "text-muted"); - - getSourceTypes(); - getSources(); - - updateExistingSources(16); - - connect(ui->sourceTypeList, &QListWidget::itemDoubleClicked, this, &OBSBasicSourceSelect::createNewSource); - connect(ui->newSourceName, &QLineEdit::returnPressed, this, &OBSBasicSourceSelect::createNewSource); - connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); - - connect(ui->addExistingButton, &QAbstractButton::clicked, this, &OBSBasicSourceSelect::addSelectedSources); - connect(this, &OBSBasicSourceSelect::selectedItemsChanged, this, [=]() { - ui->addExistingButton->setEnabled(selectedItems.size() > 0); - if (selectedItems.size() > 0) { - ui->addExistingButton->setText( - QTStr("Basic.SourceSelect.ExistingButton.Multiple").arg(selectedItems.size())); - } else { - ui->addExistingButton->setText(QTStr("Basic.SourceSelect.ExistingButton")); - } - }); - - App()->DisableHotkeys(); -} - -OBSBasicSourceSelect::~OBSBasicSourceSelect() -{ - App()->UpdateHotkeyFocusSetting(); -} - -void OBSBasicSourceSelect::checkSourceVisibility() -{ - QList buttons = sourceButtons->buttons(); - - // Allow some room for previous/next rows to make scrolling a bit more seamless - QRect scrollAreaRect(QPoint(0, 0), ui->existingScrollArea->size()); - scrollAreaRect.setTop(scrollAreaRect.top() - Thumbnail::size.width()); - scrollAreaRect.setBottom(scrollAreaRect.bottom() + Thumbnail::size.height()); - - for (QAbstractButton *button : buttons) { - SourceSelectButton *sourceButton = qobject_cast(button->parent()); - if (sourceButton) { - QRect buttonRect = button->rect(); - buttonRect.moveTo(button->mapTo(ui->existingScrollArea, buttonRect.topLeft())); - - if (scrollAreaRect.intersects(buttonRect)) { - sourceButton->setPreload(true); - } else { - sourceButton->setPreload(false); - } - } - } - - scrollAreaRect = QRect(QPoint(0, 0), ui->existingScrollArea->size()); - - for (QAbstractButton *button : buttons) { - SourceSelectButton *sourceButton = qobject_cast(button->parent()); - if (sourceButton) { - QRect buttonRect = button->rect(); - buttonRect.moveTo(button->mapTo(ui->existingScrollArea, buttonRect.topLeft())); - - if (scrollAreaRect.intersects(buttonRect)) { - sourceButton->setRectVisible(true); - } else { - sourceButton->setRectVisible(false); - } - } - } -} - -void OBSBasicSourceSelect::getSources() -{ - sources.clear(); - - obs_enum_sources(enumSourcesCallback, this); - emit sourcesUpdated(); -} - -void OBSBasicSourceSelect::updateExistingSources(int limit) -{ - delete sourceButtons; - sourceButtons = new QButtonGroup(this); - sourceButtons->setExclusive(false); - - std::vector matchingSources{}; - std::copy_if(sources.begin(), sources.end(), std::back_inserter(matchingSources), [this](obs_source_t *source) { - if (!source || obs_source_removed(source)) { - return false; - } - - const char *id = obs_source_get_unversioned_id(source); - QString stringId = QString(id); - - if (stringId.compare("group") == 0) { - return false; - } - - if (sourceTypeId.compare(stringId) == 0 || sourceTypeId.isNull()) { - return true; - } - - return false; - }); - - QWidget *prevTabWidget = ui->sourceTypeList; - - auto createSourceButton = [this, &prevTabWidget](obs_source_t *source) { - SourceSelectButton *newButton = new SourceSelectButton(source, ui->existingListFrame); - std::string name = obs_source_get_name(source); - - existingFlowLayout->addWidget(newButton); - sourceButtons->addButton(newButton->getButton()); - - if (!prevTabWidget) { - setTabOrder(ui->existingListFrame, newButton->getButton()); - } else { - setTabOrder(prevTabWidget, newButton->getButton()); - } - - prevTabWidget = newButton->getButton(); - }; - - bool isReverseListOrder = sourceTypeId.isNull(); - size_t iterationLimit = limit > 0 ? std::min(static_cast(limit), matchingSources.size()) - : matchingSources.size(); - if (isReverseListOrder) { - std::for_each(matchingSources.rbegin(), matchingSources.rbegin() + iterationLimit, createSourceButton); - } else { - std::for_each(matchingSources.begin(), matchingSources.begin() + iterationLimit, createSourceButton); - } - - setTabOrder(prevTabWidget, ui->addExistingContainer); - - connect(sourceButtons, &QButtonGroup::buttonToggled, this, &OBSBasicSourceSelect::sourceButtonToggled); - - ui->existingListFrame->adjustSize(); - QTimer::singleShot(10, this, [this] { checkSourceVisibility(); }); -} - -bool OBSBasicSourceSelect::enumSourcesCallback(void *data, obs_source_t *source) -{ - if (obs_source_is_hidden(source)) { - return true; - } - - OBSBasicSourceSelect *window = static_cast(data); - - window->sources.push_back(source); - - return true; -} - -bool OBSBasicSourceSelect::enumGroupsCallback(void *data, obs_source_t *source) -{ - OBSBasicSourceSelect *window = static_cast(data); - const char *name = obs_source_get_name(source); - const char *id = obs_source_get_unversioned_id(source); - - if (window->sourceTypeId.compare(QString(id)) == 0) { - OBSBasic *main = OBSBasic::Get(); - OBSScene scene = main->GetCurrentScene(); - - obs_sceneitem_t *existing = obs_scene_get_group(scene, name); - if (!existing) { - QPushButton *button = new QPushButton(name); - connect(button, &QPushButton::clicked, window, &OBSBasicSourceSelect::addSelectedSources); - } - } - - return true; -} - -void OBSBasicSourceSelect::OBSSourceAdded(void *data, calldata_t *calldata) -{ - OBSBasicSourceSelect *window = static_cast(data); - obs_source_t *source = (obs_source_t *)calldata_ptr(calldata, "source"); - - QMetaObject::invokeMethod(window, "SourceAdded", Q_ARG(OBSSource, source)); -} - -void OBSBasicSourceSelect::getSourceTypes() -{ - OBSBasic *main = qobject_cast(App()->GetMainWindow()); - - const char *unversioned_type; - const char *type; - - size_t idx = 0; - - while (obs_enum_input_types2(idx++, &type, &unversioned_type)) { - const char *name = obs_source_get_display_name(type); - uint32_t caps = obs_get_source_output_flags(type); - - if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) { - continue; - } - - QListWidgetItem *newItem = new QListWidgetItem(ui->sourceTypeList); - newItem->setData(Qt::DisplayRole, name); - newItem->setData(UNVERSIONED_ID_ROLE, unversioned_type); - - if ((caps & OBS_SOURCE_DEPRECATED) != 0) { - newItem->setData(DEPRECATED_ROLE, true); - } else { - newItem->setData(DEPRECATED_ROLE, false); - - QIcon icon; - icon = main->GetSourceIcon(type); - newItem->setIcon(icon); - } - } - - QListWidgetItem *newItem = new QListWidgetItem(ui->sourceTypeList); - newItem->setData(Qt::DisplayRole, Str("Basic.Scene")); - newItem->setData(UNVERSIONED_ID_ROLE, "scene"); - - QIcon icon; - icon = main->GetSceneIcon(); - newItem->setIcon(icon); - - ui->sourceTypeList->sortItems(); - - // Shift Deprecated sources to the bottom - QList deprecatedItems; - for (int i = 0; i < ui->sourceTypeList->count(); i++) { - QListWidgetItem *item = ui->sourceTypeList->item(i); - if (!item) { - break; - } - - bool isDeprecated = item->data(DEPRECATED_ROLE).toBool(); - if (isDeprecated) { - ui->sourceTypeList->takeItem(i); - deprecatedItems.append(item); - } - } - - for (auto &item : deprecatedItems) { - ui->sourceTypeList->addItem(item); - } - - QListWidgetItem *allSources = new QListWidgetItem(); - allSources->setData(Qt::DisplayRole, Str("Basic.SourceSelect.Recent")); - allSources->setData(UNVERSIONED_ID_ROLE, QVariant()); - ui->sourceTypeList->insertItem(0, allSources); - - ui->sourceTypeList->setCurrentItem(allSources); - ui->sourceTypeList->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); - - connect(ui->sourceTypeList, &QListWidget::currentItemChanged, this, &OBSBasicSourceSelect::sourceTypeSelected); -} - -void OBSBasicSourceSelect::setSelectedSourceType(QListWidgetItem *item) -{ - setSelectedSource(nullptr); - QLayout *layout = ui->existingListFrame->flowLayout(); - - // Clear existing buttons when switching types - QLayoutItem *child = nullptr; - while ((child = layout->takeAt(0)) != nullptr) { - if (child->widget()) { - child->widget()->deleteLater(); - } - delete child; - } - - QVariant unversionedIdData = item->data(UNVERSIONED_ID_ROLE); - QVariant deprecatedData = item->data(DEPRECATED_ROLE); - - if (unversionedIdData.isNull()) { - setSelectedSource(nullptr); - sourceTypeId.clear(); - ui->createNewFrame->setVisible(false); - updateExistingSources(16); - return; - } - - QString type = unversionedIdData.toString(); - if (type.compare(sourceTypeId) == 0) { - return; - } - - ui->createNewFrame->setVisible(true); - - bool isDeprecatedType = deprecatedData.toBool(); - ui->newSourceName->setVisible(!isDeprecatedType); - ui->createNewSource->setVisible(!isDeprecatedType); - - ui->deprecatedCreateLabel->setVisible(isDeprecatedType); - - sourceTypeId = type; - - QString placeHolderText{getSourceDisplayName(sourceTypeId)}; - - QString text{placeHolderText}; - int i = 2; - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(text)))) { - text = QString("%1 %2").arg(placeHolderText).arg(i++); - } - - ui->newSourceName->setText(text); - ui->newSourceName->selectAll(); - - if (sourceTypeId.compare("scene") == 0) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - OBSSource curSceneSource = main->GetCurrentSceneSource(); - - delete sourceButtons; - sourceButtons = new QButtonGroup(this); - - int count = main->ui->scenes->count(); - QWidget *prevTabItem = ui->sourceTypeList; - for (int i = 0; i < count; i++) { - QListWidgetItem *item = main->ui->scenes->item(i); - OBSScene scene = GetOBSRef(item); - OBSSource sceneSource = obs_scene_get_source(scene); - - if (curSceneSource == sceneSource) { - continue; - } - - SourceSelectButton *newButton = new SourceSelectButton(sceneSource, ui->existingListFrame); - existingFlowLayout->addWidget(newButton); - sourceButtons->addButton(newButton->getButton()); - - setTabOrder(prevTabItem, newButton->getButton()); - prevTabItem = newButton->getButton(); - } - connect(sourceButtons, &QButtonGroup::buttonToggled, this, &OBSBasicSourceSelect::sourceButtonToggled); - - QTimer::singleShot(100, this, [this] { checkSourceVisibility(); }); - - ui->createNewFrame->setVisible(false); - - } else if (sourceTypeId.compare("group") == 0) { - obs_enum_sources(enumGroupsCallback, this); - } else { - updateExistingSources(); - } - - if (layout->count() == 0) { - QLabel *noExisting = new QLabel(); - noExisting->setText(QTStr("Basic.SourceSelect.NoExisting").arg(getSourceDisplayName(sourceTypeId))); - noExisting->setProperty("class", "text-muted"); - layout->addWidget(noExisting); - } -} - -void OBSBasicSourceSelect::OBSSourceRemoved(void *data, calldata_t *calldata) -{ - OBSBasicSourceSelect *window = static_cast(data); - obs_source_t *source = (obs_source_t *)calldata_ptr(calldata, "source"); - - QMetaObject::invokeMethod(window, "SourceRemoved", Q_ARG(OBSSource, source)); -} - -void OBSBasicSourceSelect::sourceButtonToggled(QAbstractButton *button, bool checked) -{ - SourceSelectButton *buttonParent = dynamic_cast(button->parentWidget()); - - Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers(); - bool ctrlDown = (modifiers & Qt::ControlModifier); - bool shiftDown = (modifiers & Qt::ShiftModifier); - - if (!buttonParent) { - clearSelectedItems(); - return; - } - - int selectedIndex = existingFlowLayout->indexOf(buttonParent); - - if (ctrlDown && !shiftDown) { - if (checked) { - addSelectedItem(buttonParent); - } else { - removeSelectedItem(buttonParent); - } - - lastSelectedIndex = existingFlowLayout->indexOf(buttonParent); - return; - } else if (shiftDown) { - if (!ctrlDown) { - clearSelectedItems(); - } - sourceButtons->blockSignals(true); - int start = std::min(selectedIndex, lastSelectedIndex); - int end = std::max(selectedIndex, lastSelectedIndex); - for (int i = start; i <= end; i++) { - auto item = existingFlowLayout->itemAt(i); - if (!item) { - continue; - } - - auto widget = item->widget(); - if (!widget) { - continue; - } - - auto entry = dynamic_cast(widget); - if (entry) { - entry->getButton()->setChecked(true); - addSelectedItem(entry); - } - } - sourceButtons->blockSignals(false); - } else { - lastSelectedIndex = existingFlowLayout->indexOf(buttonParent); - - bool reselectItem = selectedItems.size() > 1; - clearSelectedItems(); - if (checked) { - addSelectedItem(buttonParent); - } else if (reselectItem) { - button->setChecked(true); - addSelectedItem(buttonParent); - } - } -} - -void OBSBasicSourceSelect::sourceDropped(QString uuid) -{ - OBSSourceAutoRelease source = obs_get_source_by_uuid(uuid.toStdString().c_str()); - if (source) { - const char *name = obs_source_get_name(source); - bool visible = ui->sourceVisible->isChecked(); - - addExistingSource(name, visible); - } -} - -void OBSBasicSourceSelect::setSelectedSource(SourceSelectButton *button) -{ - clearSelectedItems(); - addSelectedItem(button); -} - -void OBSBasicSourceSelect::addSelectedItem(SourceSelectButton *button) -{ - if (button == nullptr) { - return; - } - - auto it = std::find(selectedItems.begin(), selectedItems.end(), button); - if (it == selectedItems.end()) { - selectedItems.push_back(button); - emit selectedItemsChanged(); - } -} - -void OBSBasicSourceSelect::removeSelectedItem(SourceSelectButton *button) -{ - if (button == nullptr) { - return; - } - - auto it = std::find(selectedItems.begin(), selectedItems.end(), button); - if (it != selectedItems.end()) { - selectedItems.erase(it); - emit selectedItemsChanged(); - } -} - -void OBSBasicSourceSelect::clearSelectedItems() -{ - if (selectedItems.size() == 0) { - return; - } - - sourceButtons->blockSignals(true); - for (auto &item : selectedItems) { - item->getButton()->setChecked(false); - } - sourceButtons->blockSignals(false); - selectedItems.clear(); - emit selectedItemsChanged(); -} - -void OBSBasicSourceSelect::createNewSource() +void OBSBasicSourceSelect::on_buttonBox_accepted() { + bool useExisting = ui->selectExisting->isChecked(); bool visible = ui->sourceVisible->isChecked(); - if (ui->newSourceName->text().isEmpty()) { - return; - } + if (useExisting) { + QListWidgetItem *item = ui->sourceList->currentItem(); + if (!item) + return; - if (sourceTypeId.isNull()) { - return; - } - - if (sourceTypeId.compare("scene") == 0) { - return; - } - - OBSSceneItem item; - std::string sourceType = sourceTypeId.toStdString(); - const char *id = sourceType.c_str(); - if (!AddNew(this, id, QT_TO_UTF8(ui->newSourceName->text()), visible, newSource, item)) { - return; - } - - if (newSource && strcmp(obs_source_get_id(newSource.Get()), "group") != 0) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - std::string scene_name = obs_source_get_name(main->GetCurrentSceneSource()); - auto undo = [scene_name, main](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_source_remove(source); - - OBSSourceAutoRelease scene_source = obs_get_source_by_name(scene_name.c_str()); - main->SetCurrentScene(scene_source.Get(), true); - }; - OBSDataAutoRelease wrapper = obs_data_create(); - obs_data_set_string(wrapper, "id", id); - obs_data_set_int(wrapper, "item_id", obs_sceneitem_get_id(item)); - obs_data_set_string(wrapper, "name", ui->newSourceName->text().toUtf8().constData()); - obs_data_set_bool(wrapper, "visible", visible); - - auto redo = [scene_name, main](const std::string &data) { - OBSSourceAutoRelease scene_source = obs_get_source_by_name(scene_name.c_str()); - main->SetCurrentScene(scene_source.Get(), true); - - OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); - OBSSource source; - OBSSceneItem item; - AddNew(NULL, obs_data_get_string(dat, "id"), obs_data_get_string(dat, "name"), - obs_data_get_bool(dat, "visible"), source, item); - obs_sceneitem_set_id(item, (int64_t)obs_data_get_int(dat, "item_id")); - }; - undo_s.add_action(QTStr("Undo.Add").arg(ui->newSourceName->text()), undo, redo, - std::string(obs_source_get_name(newSource)), std::string(obs_data_get_json(wrapper))); - - main->CreatePropertiesWindow(newSource); - } - close(); -} - -void OBSBasicSourceSelect::addExistingSource(QString name, bool visible) -{ - OBSSourceAutoRelease source = obs_get_source_by_name(name.toStdString().c_str()); - if (source) { - AddExisting(source.Get(), visible, false); + QString source_name = item->text(); + AddExisting(QT_TO_UTF8(source_name), visible, false); OBSBasic *main = OBSBasic::Get(); const char *scene_name = obs_source_get_name(main->GetCurrentSceneSource()); @@ -829,48 +309,150 @@ void OBSBasicSourceSelect::addExistingSource(QString name, bool visible) obs_scene_release(scene); }; - auto redo = [scene_name, main, name, visible](const std::string &) { + auto redo = [scene_name, main, source_name, visible](const std::string &) { obs_source_t *scene_source = obs_get_source_by_name(scene_name); main->SetCurrentScene(scene_source, true); obs_source_release(scene_source); - AddExisting(QT_TO_UTF8(name), visible, false); + AddExisting(QT_TO_UTF8(source_name), visible, false); }; - undo_s.add_action(QTStr("Undo.Add").arg(name), undo, redo, "", ""); - } -} + undo_s.add_action(QTStr("Undo.Add").arg(source_name), undo, redo, "", ""); + } else { + if (ui->sourceName->text().isEmpty()) { + OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); + return; + } -void OBSBasicSourceSelect::on_createNewSource_clicked(bool) -{ - createNewSource(); -} + OBSSceneItem item; + if (!AddNew(this, id, QT_TO_UTF8(ui->sourceName->text()), visible, newSource, item)) + return; -void OBSBasicSourceSelect::addSelectedSources() -{ - if (selectedItems.size() == 0) { - return; + OBSBasic *main = OBSBasic::Get(); + std::string scene_name = obs_source_get_name(main->GetCurrentSceneSource()); + auto undo = [scene_name, main](const std::string &data) { + OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); + obs_source_remove(source); + + OBSSourceAutoRelease scene_source = obs_get_source_by_name(scene_name.c_str()); + main->SetCurrentScene(scene_source.Get(), true); + }; + OBSDataAutoRelease wrapper = obs_data_create(); + obs_data_set_string(wrapper, "id", id); + obs_data_set_int(wrapper, "item_id", obs_sceneitem_get_id(item)); + obs_data_set_string(wrapper, "name", ui->sourceName->text().toUtf8().constData()); + obs_data_set_bool(wrapper, "visible", visible); + + auto redo = [scene_name, main](const std::string &data) { + OBSSourceAutoRelease scene_source = obs_get_source_by_name(scene_name.c_str()); + main->SetCurrentScene(scene_source.Get(), true); + + OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); + OBSSource source; + OBSSceneItem item; + AddNew(NULL, obs_data_get_string(dat, "id"), obs_data_get_string(dat, "name"), + obs_data_get_bool(dat, "visible"), source, item); + obs_sceneitem_set_id(item, (int64_t)obs_data_get_int(dat, "item_id")); + }; + undo_s.add_action(QTStr("Undo.Add").arg(ui->sourceName->text()), undo, redo, + std::string(obs_source_get_name(newSource)), std::string(obs_data_get_json(wrapper))); } - bool visible = ui->sourceVisible->isChecked(); - - for (auto &item : selectedItems) { - QString sourceName = item->text(); - addExistingSource(sourceName, visible); - } - close(); + done(DialogCode::Accepted); } -void OBSBasicSourceSelect::sourceTypeSelected(QListWidgetItem *current, QListWidgetItem *) +void OBSBasicSourceSelect::on_buttonBox_rejected() { - setSelectedSourceType(current); + done(DialogCode::Rejected); +} + +static inline const char *GetSourceDisplayName(const char *id) +{ + if (strcmp(id, "scene") == 0) + return Str("Basic.Scene"); + else if (strcmp(id, "group") == 0) + return Str("Group"); + const char *v_id = obs_get_latest_input_type_id(id); + return obs_source_get_display_name(v_id); +} + +OBSBasicSourceSelect::OBSBasicSourceSelect(OBSBasic *parent, const char *id_, undo_stack &undo_s) + : QDialog(parent), + ui(new Ui::OBSBasicSourceSelect), + id(id_), + undo_s(undo_s) +{ + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + + ui->setupUi(this); + + ui->sourceList->setAttribute(Qt::WA_MacShowFocusRect, false); + + QString placeHolderText{QT_UTF8(GetSourceDisplayName(id))}; + + QString text{placeHolderText}; + int i = 2; + OBSSourceAutoRelease source = nullptr; + while ((source = obs_get_source_by_name(QT_TO_UTF8(text)))) { + text = QString("%1 %2").arg(placeHolderText).arg(i++); + } + + ui->sourceName->setText(text); + ui->sourceName->setFocus(); //Fixes deselect of text. + ui->sourceName->selectAll(); + + installEventFilter(CreateShortcutFilter()); + + connect(ui->createNew, &QRadioButton::pressed, this, [&]() { + QPushButton *button = ui->buttonBox->button(QDialogButtonBox::Ok); + if (!button->isEnabled()) + button->setEnabled(true); + }); + connect(ui->selectExisting, &QRadioButton::pressed, this, [&]() { + QPushButton *button = ui->buttonBox->button(QDialogButtonBox::Ok); + bool enabled = ui->sourceList->selectedItems().size() != 0; + if (button->isEnabled() != enabled) + button->setEnabled(enabled); + }); + connect(ui->sourceList, &QListWidget::itemSelectionChanged, this, [&]() { + QPushButton *button = ui->buttonBox->button(QDialogButtonBox::Ok); + if (!button->isEnabled()) + button->setEnabled(true); + }); + + if (strcmp(id_, "scene") == 0) { + OBSBasic *main = OBSBasic::Get(); + OBSSource curSceneSource = main->GetCurrentSceneSource(); + + ui->selectExisting->setChecked(true); + ui->createNew->setChecked(false); + ui->createNew->setEnabled(false); + ui->sourceName->setEnabled(false); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + + int count = main->ui->scenes->count(); + for (int i = 0; i < count; i++) { + QListWidgetItem *item = main->ui->scenes->item(i); + OBSScene scene = GetOBSRef(item); + OBSSource sceneSource = obs_scene_get_source(scene); + + if (curSceneSource == sceneSource) + continue; + + const char *name = obs_source_get_name(sceneSource); + ui->sourceList->addItem(QT_UTF8(name)); + } + } else if (strcmp(id_, "group") == 0) { + obs_enum_sources(EnumGroups, this); + } else { + obs_enum_sources(EnumSources, this); + } } void OBSBasicSourceSelect::SourcePaste(SourceCopyInfo &info, bool dup) { OBSSource source = OBSGetStrongRef(info.weak_source); - if (!source) { + if (!source) return; - } AddExisting(source, info.visible, dup, &info); } diff --git a/frontend/dialogs/OBSBasicSourceSelect.hpp b/frontend/dialogs/OBSBasicSourceSelect.hpp index b857f0c06..077d8d298 100644 --- a/frontend/dialogs/OBSBasicSourceSelect.hpp +++ b/frontend/dialogs/OBSBasicSourceSelect.hpp @@ -1,6 +1,5 @@ /****************************************************************************** Copyright (C) 2023 by Lain Bailey - Copyright (C) 2025 by Taylor Giampaolo This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,75 +17,38 @@ #pragma once -#include #include "ui_OBSBasicSourceSelect.h" -#include -#include #include #include -#include -#include +#include -constexpr int UNVERSIONED_ID_ROLE = Qt::UserRole + 1; -constexpr int DEPRECATED_ROLE = Qt::UserRole + 2; +#include class OBSBasicSourceSelect : public QDialog { Q_OBJECT private: std::unique_ptr ui; - QString sourceTypeId; + const char *id; undo_stack &undo_s; - QPointer sourceButtons; - - std::vector sources; - std::vector groups; - - QPointer existingFlowLayout = nullptr; - - void getSources(); - void updateExistingSources(int limit = 0); - - static bool enumSourcesCallback(void *data, obs_source_t *source); - static bool enumGroupsCallback(void *data, obs_source_t *source); + static bool EnumSources(void *data, obs_source_t *source); + static bool EnumGroups(void *data, obs_source_t *source); static void OBSSourceRemoved(void *data, calldata_t *calldata); static void OBSSourceAdded(void *data, calldata_t *calldata); - void getSourceTypes(); - void setSelectedSourceType(QListWidgetItem *item); +private slots: + void on_buttonBox_accepted(); + void on_buttonBox_rejected(); - int lastSelectedIndex = -1; - std::vector selectedItems; - void setSelectedSource(SourceSelectButton *button); - void addSelectedItem(SourceSelectButton *button); - void removeSelectedItem(SourceSelectButton *button); - void clearSelectedItems(); - - void createNewSource(); - void addExistingSource(QString name, bool visible); - - void checkSourceVisibility(); - -signals: - void sourcesUpdated(); - void selectedItemsChanged(); - -public slots: - void on_createNewSource_clicked(bool checked); - void addSelectedSources(); - - void sourceTypeSelected(QListWidgetItem *current, QListWidgetItem *previous); - - void sourceButtonToggled(QAbstractButton *button, bool checked); - void sourceDropped(QString uuid); + void SourceAdded(OBSSource source); + void SourceRemoved(OBSSource source); public: - OBSBasicSourceSelect(OBSBasic *parent, undo_stack &undo_s); - ~OBSBasicSourceSelect(); + OBSBasicSourceSelect(OBSBasic *parent, const char *id, undo_stack &undo_s); OBSSource newSource; diff --git a/frontend/forms/OBSBasicSourceSelect.ui b/frontend/forms/OBSBasicSourceSelect.ui index 87320bc09..07f64ab47 100644 --- a/frontend/forms/OBSBasicSourceSelect.ui +++ b/frontend/forms/OBSBasicSourceSelect.ui @@ -3,674 +3,106 @@ OBSBasicSourceSelect - Qt::NonModal + Qt::WindowModal 0 0 - 1000 - 614 + 352 + 314 - - - 0 - 0 - - - - - 16777215 - 1000 - - Basic.SourceSelect - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - + - - - - 0 - 0 - + + + + + Basic.SourceSelect.CreateNew + + + true + + + + + + + + + + Basic.SourceSelect.AddExisting + + + + + + + false + + + true + + + + + + + Basic.SourceSelect.AddVisible + + + true + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - QFrame::NoFrame - - - QFrame::Plain - - - 0 - - - dialog-container - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - true - - - - 0 - 0 - - - - Basic.SourceSelect.SelectType - - - QFrame::NoFrame - - - QFrame::Plain - - - 0 - - - dialog-frame - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 0 - 460 - - - - Basic.SourceSelect.SelectType - - - QFrame::NoFrame - - - QFrame::Plain - - - 0 - - - QAbstractScrollArea::AdjustToContentsOnFirstShow - - - QAbstractItemView::NoEditTriggers - - - false - - - QAbstractItemView::ScrollPerPixel - - - QAbstractItemView::ScrollPerPixel - - - QListView::Adjust - - - - - - - - - - Qt::Vertical - - - QSizePolicy::Minimum - - - - 17 - 4 - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Plain - - - 0 - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Plain - - - 0 - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Basic.SourceSelect.AddExisting - - - QFrame::Plain - - - 0 - - - dialog-container dialog-frame - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - Basic.SourceSelect.Existing - - - text-title - - - - - - - Qt::NoFocus - - - QFrame::NoFrame - - - QFrame::Plain - - - 0 - - - Qt::ScrollBarAlwaysOff - - - QAbstractScrollArea::AdjustToContents - - - true - - - - - 0 - 0 - 800 - 510 - - - - true - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Plain - - - 0 - - - - - - - Qt::Vertical - - - - 20 - 10 - - - - - - - - - - - - - 0 - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 40 - 10 - - - - - - - - Qt::Horizontal - - - - 259 - 10 - - - - - - - - false - - - - 0 - 0 - - - - Basic.SourceSelect.ExistingButton - - - button-primary - - - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Plain - - - 0 - - - dialog-container dialog-frame - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Basic.SourceSelect.NewSource - - - text-title - - - - - - - QFrame::NoFrame - - - QFrame::Plain - - - 0 - - - Basic.SourceSelect.Deprecated.Create - - - - - - - QFrame::NoFrame - - - QFrame::Plain - - - 0 - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - Basic.SourceSelect.Accessible.SourceName - - - - - - - - 0 - 0 - - - - Basic.SourceSelect.CreateButton - - - button-primary margin-left - - - - - - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Plain - - - 0 - - - dialog-container - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Basic.SourceSelect.AddVisible - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - QDialogButtonBox::Cancel - - - false - - - - - - - - - - - - - FlowFrame - QFrame -
components/FlowFrame.hpp
- 1 -
-
- - sourceTypeList - existingScrollArea - addExistingButton - newSourceName - createNewSource - sourceVisible - - - - - + + + + createNew + toggled(bool) + sourceName + setEnabled(bool) + + + 79 + 29 + + + 108 + 53 + + + + + selectExisting + toggled(bool) + sourceList + setEnabled(bool) + + + 51 + 80 + + + 65 + 128 + + + + diff --git a/frontend/utility/ResizeSignaler.hpp b/frontend/utility/ResizeSignaler.hpp deleted file mode 100644 index f3337f734..000000000 --- a/frontend/utility/ResizeSignaler.hpp +++ /dev/null @@ -1,39 +0,0 @@ -/****************************************************************************** - Copyright (C) 2025 by Taylor Giampaolo - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -******************************************************************************/ - -#pragma once - -#include - -class ResizeSignaler : public QObject { - Q_OBJECT - -public: - inline ResizeSignaler(QObject *parent) : QObject(parent) {} - -signals: - void resized(); - -protected: - bool eventFilter(QObject *object, QEvent *event) override - { - if (event->type() == QEvent::Resize) { - emit resized(); - } - return QObject::eventFilter(object, event); - } -}; diff --git a/frontend/utility/ScreenshotObj.cpp b/frontend/utility/ScreenshotObj.cpp index 3ff65be99..0fff65d6f 100644 --- a/frontend/utility/ScreenshotObj.cpp +++ b/frontend/utility/ScreenshotObj.cpp @@ -1,6 +1,5 @@ /****************************************************************************** Copyright (C) 2023 by Lain Bailey - Copyright (C) 2025 by Taylor Giampaolo This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,7 +16,6 @@ ******************************************************************************/ #include "ScreenshotObj.hpp" -#include "display-helpers.hpp" #include @@ -32,23 +30,11 @@ #include "moc_ScreenshotObj.cpp" -namespace { -void renderTick(void *param, float) -{ - auto *self = static_cast(param); - if (self->stage() == ScreenshotObj::Stage::Finished) { - return; - } - - obs_enter_graphics(); - self->processStage(); - obs_leave_graphics(); -} -} // namespace +static void ScreenshotTick(void *param, float); ScreenshotObj::ScreenshotObj(obs_source_t *source) : weakSource(OBSGetWeakRef(source)) { - obs_add_tick_callback(renderTick, this); + obs_add_tick_callback(ScreenshotTick, this); } ScreenshotObj::~ScreenshotObj() @@ -58,26 +44,40 @@ ScreenshotObj::~ScreenshotObj() gs_texrender_destroy(texrender); obs_leave_graphics(); - obs_remove_tick_callback(renderTick, this); + obs_remove_tick_callback(ScreenshotTick, this); + + if (th.joinable()) { + th.join(); + + if (cx && cy) { + OBSBasic *main = OBSBasic::Get(); + main->ShowStatusBarMessage( + QTStr("Basic.StatusBar.ScreenshotSavedTo").arg(QT_UTF8(path.c_str()))); + + main->lastScreenshot = path; + + main->OnEvent(OBS_FRONTEND_EVENT_SCREENSHOT_TAKEN); + } + } } -void ScreenshotObj::renderScreenshot() +void ScreenshotObj::Screenshot() { - OBSSourceAutoRelease source = OBSGetStrongRef(weakSource); + OBSSource source = OBSGetStrongRef(weakSource); if (source) { - sourceWidth = obs_source_get_width(source); - sourceHeight = obs_source_get_height(source); + cx = obs_source_get_width(source); + cy = obs_source_get_height(source); } else { obs_video_info ovi; obs_get_video_info(&ovi); - sourceWidth = ovi.base_width; - sourceHeight = ovi.base_height; + cx = ovi.base_width; + cy = ovi.base_height; } - if (!sourceWidth || !sourceHeight) { - blog(LOG_WARNING, "Cannot render source, invalid target size"); - obs_remove_tick_callback(renderTick, this); + if (!cx || !cy) { + blog(LOG_WARNING, "Cannot screenshot, invalid target size"); + obs_remove_tick_callback(ScreenshotTick, this); deleteLater(); return; } @@ -94,34 +94,15 @@ void ScreenshotObj::renderScreenshot() #endif const enum gs_color_format format = gs_get_format_from_space(space); - outputWidth = customSize.isValid() ? customSize.width() : sourceWidth; - outputHeight = customSize.isValid() ? customSize.height() : sourceHeight; - texrender = gs_texrender_create(format, GS_ZS_NONE); - stagesurf = gs_stagesurface_create(outputWidth, outputHeight, format); + stagesurf = gs_stagesurface_create(cx, cy, format); - if (gs_texrender_begin_with_color_space(texrender, outputWidth, outputHeight, space)) { + if (gs_texrender_begin_with_color_space(texrender, cx, cy, space)) { vec4 zero; vec4_zero(&zero); - int x; - int y; - int scaledWidth; - int scaledHeight; - float scale; - - GetScaleAndCenterPos(sourceWidth, sourceHeight, outputWidth, outputHeight, x, y, scale); - - scaledWidth = int(scale * float(sourceWidth)); - scaledHeight = int(scale * float(sourceHeight)); - gs_clear(GS_CLEAR_COLOR, &zero, 0.0f, 0); - - gs_viewport_push(); - gs_projection_push(); - - gs_ortho(0.0f, (float)sourceWidth, 0.0f, (float)sourceHeight, -100.0f, 100.0f); - gs_set_viewport(x, y, scaledWidth, scaledHeight); + gs_ortho(0.0f, (float)cx, 0.0f, (float)cy, -100.0f, 100.0f); gs_blend_state_push(); gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO); @@ -134,60 +115,35 @@ void ScreenshotObj::renderScreenshot() obs_render_main_texture(); } - gs_projection_pop(); - gs_viewport_pop(); - gs_blend_state_pop(); gs_texrender_end(texrender); } } -void ScreenshotObj::processStage() -{ - switch (stage_) { - case Stage::Render: - renderScreenshot(); - stage_ = Stage::Download; - break; - case Stage::Download: - downloadData(); - stage_ = Stage::Output; - break; - case Stage::Output: - copyData(); - QMetaObject::invokeMethod(this, &ScreenshotObj::handleSave, Qt::QueuedConnection); - obs_remove_tick_callback(renderTick, this); - stage_ = Stage::Finished; - break; - case Stage::Finished: - break; - } -} - -void ScreenshotObj::downloadData() +void ScreenshotObj::Download() { gs_stage_texture(stagesurf, gs_texrender_get_texture(texrender)); } -void ScreenshotObj::copyData() +void ScreenshotObj::Copy() { uint8_t *videoData = nullptr; uint32_t videoLinesize = 0; if (gs_stagesurface_map(stagesurf, &videoData, &videoLinesize)) { if (gs_stagesurface_get_color_format(stagesurf) == GS_RGBA16F) { - const uint32_t linesize = outputWidth * 8; - half_bytes.reserve(outputWidth * outputHeight * 8); + const uint32_t linesize = cx * 8; + half_bytes.reserve(cx * cy * 8); - for (uint32_t y = 0; y < outputHeight; y++) { + for (uint32_t y = 0; y < cy; y++) { const uint8_t *const line = videoData + (y * videoLinesize); half_bytes.insert(half_bytes.end(), line, line + linesize); } } else { - image = QImage(outputWidth, outputHeight, QImage::Format::Format_RGBX8888); + image = QImage(cx, cy, QImage::Format::Format_RGBX8888); int linesize = image.bytesPerLine(); - for (int y = 0; y < (int)outputHeight; y++) + for (int y = 0; y < (int)cy; y++) memcpy(image.scanLine(y), videoData + (y * videoLinesize), linesize); } @@ -195,13 +151,8 @@ void ScreenshotObj::copyData() } } -void ScreenshotObj::saveToFile() +void ScreenshotObj::Save() { - if (!outputToFile) { - QMetaObject::invokeMethod(this, &ScreenshotObj::onFinished, Qt::QueuedConnection); - return; - } - OBSBasic *main = OBSBasic::Get(); config_t *config = main->Config(); @@ -220,10 +171,7 @@ void ScreenshotObj::saveToFile() path = GetOutputFilename(rec_path, ext, noSpace, overwriteIfExists, GetFormatString(filenameFormat, "Screenshot", nullptr).c_str()); - th = std::thread([this] { - muxFile(); - QMetaObject::invokeMethod(this, &ScreenshotObj::onFinished, Qt::QueuedConnection); - }); + th = std::thread([this] { MuxAndFinish(); }); } #ifdef _WIN32 @@ -237,44 +185,36 @@ static HRESULT SaveJxrImage(LPCWSTR path, uint8_t *pixels, uint32_t cx, uint32_t value.vt = VT_BOOL; value.bVal = TRUE; HRESULT hr = options->Write(1, &bag, &value); - if (FAILED(hr)) { + if (FAILED(hr)) return hr; - } hr = frameEncode->Initialize(options); - if (FAILED(hr)) { + if (FAILED(hr)) return hr; - } hr = frameEncode->SetSize(cx, cy); - if (FAILED(hr)) { + if (FAILED(hr)) return hr; - } hr = frameEncode->SetResolution(72, 72); - if (FAILED(hr)) { + if (FAILED(hr)) return hr; - } WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat64bppRGBAHalf; hr = frameEncode->SetPixelFormat(&pixelFormat); - if (FAILED(hr)) { + if (FAILED(hr)) return hr; - } - if (memcmp(&pixelFormat, &GUID_WICPixelFormat64bppRGBAHalf, sizeof(WICPixelFormatGUID)) != 0) { + if (memcmp(&pixelFormat, &GUID_WICPixelFormat64bppRGBAHalf, sizeof(WICPixelFormatGUID)) != 0) return E_FAIL; - } hr = frameEncode->WritePixels(cy, cx * 8, cx * cy * 8, pixels); - if (FAILED(hr)) { + if (FAILED(hr)) return hr; - } hr = frameEncode->Commit(); - if (FAILED(hr)) { + if (FAILED(hr)) return hr; - } return S_OK; } @@ -284,50 +224,43 @@ static HRESULT SaveJxr(LPCWSTR path, uint8_t *pixels, uint32_t cx, uint32_t cy) Microsoft::WRL::ComPtr factory; HRESULT hr = CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(factory.GetAddressOf())); - if (FAILED(hr)) { + if (FAILED(hr)) return hr; - } Microsoft::WRL::ComPtr stream; hr = factory->CreateStream(stream.GetAddressOf()); - if (FAILED(hr)) { + if (FAILED(hr)) return hr; - } hr = stream->InitializeFromFilename(path, GENERIC_WRITE); - if (FAILED(hr)) { + if (FAILED(hr)) return hr; - } Microsoft::WRL::ComPtr encoder = NULL; hr = factory->CreateEncoder(GUID_ContainerFormatWmp, NULL, encoder.GetAddressOf()); - if (FAILED(hr)) { + if (FAILED(hr)) return hr; - } hr = encoder->Initialize(stream.Get(), WICBitmapEncoderNoCache); - if (FAILED(hr)) { + if (FAILED(hr)) return hr; - } Microsoft::WRL::ComPtr frameEncode; Microsoft::WRL::ComPtr options; hr = encoder->CreateNewFrame(frameEncode.GetAddressOf(), options.GetAddressOf()); - if (FAILED(hr)) { + if (FAILED(hr)) return hr; - } hr = SaveJxrImage(path, pixels, cx, cy, frameEncode.Get(), options.Get()); - if (FAILED(hr)) { + if (FAILED(hr)) return hr; - } encoder->Commit(); return S_OK; } #endif // #ifdef _WIN32 -void ScreenshotObj::muxFile() +void ScreenshotObj::MuxAndFinish() { if (half_bytes.empty()) { image.save(QT_UTF8(path.c_str())); @@ -337,50 +270,45 @@ void ScreenshotObj::muxFile() wchar_t *path_w = nullptr; os_utf8_to_wcs_ptr(path.c_str(), 0, &path_w); if (path_w) { - SaveJxr(path_w, half_bytes.data(), outputWidth, outputHeight); + SaveJxr(path_w, half_bytes.data(), cx, cy); bfree(path_w); } -#endif - } -} - -void ScreenshotObj::onFinished() -{ - if (th.joinable()) { - th.join(); +#endif // #ifdef _WIN32 } - if (outputWidth && outputHeight) { - if (outputToFile) { - OBSBasic *main = OBSBasic::Get(); - main->ShowStatusBarMessage( - QTStr("Basic.StatusBar.ScreenshotSavedTo").arg(QT_UTF8(path.c_str()))); - main->lastScreenshot = path; - main->OnEvent(OBS_FRONTEND_EVENT_SCREENSHOT_TAKEN); - } + deleteLater(); +} - emit imageReady(image.copy()); +#define STAGE_SCREENSHOT 0 +#define STAGE_DOWNLOAD 1 +#define STAGE_COPY_AND_SAVE 2 +#define STAGE_FINISH 3 + +static void ScreenshotTick(void *param, float) +{ + ScreenshotObj *data = static_cast(param); + + if (data->stage == STAGE_FINISH) { + return; } - this->deleteLater(); -} + obs_enter_graphics(); -void ScreenshotObj::setSize(QSize size) -{ - customSize = size; -} + switch (data->stage) { + case STAGE_SCREENSHOT: + data->Screenshot(); + break; + case STAGE_DOWNLOAD: + data->Download(); + break; + case STAGE_COPY_AND_SAVE: + data->Copy(); + QMetaObject::invokeMethod(data, "Save"); + obs_remove_tick_callback(ScreenshotTick, data); + break; + } -void ScreenshotObj::setSize(int width, int height) -{ - setSize(QSize(width, height)); -} + obs_leave_graphics(); -void ScreenshotObj::setSaveToFile(bool save) -{ - outputToFile = save; -} - -void ScreenshotObj::handleSave() -{ - saveToFile(); + data->stage++; } diff --git a/frontend/utility/ScreenshotObj.hpp b/frontend/utility/ScreenshotObj.hpp index 56e128a08..01a4fefd8 100644 --- a/frontend/utility/ScreenshotObj.hpp +++ b/frontend/utility/ScreenshotObj.hpp @@ -1,6 +1,5 @@ /****************************************************************************** Copyright (C) 2023 by Lain Bailey - Copyright (C) 2025 by Taylor Giampaolo This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -31,26 +30,10 @@ class ScreenshotObj : public QObject { public: ScreenshotObj(obs_source_t *source); ~ScreenshotObj() override; - - enum class Stage { Render, Download, Output, Finished }; - - void processStage(); - void renderScreenshot(); - void downloadData(); - void copyData(); - void saveToFile(); - void muxFile(); - void onFinished(); - - Stage stage() { return stage_; } - void setStage(Stage stage) { stage_ = stage; } - - void setSize(QSize size); - void setSize(int width, int height); - void setSaveToFile(bool save); - -private: - Stage stage_ = Stage::Render; + void Screenshot(); + void Download(); + void Copy(); + void MuxAndFinish(); gs_texrender_t *texrender = nullptr; gs_stagesurf_t *stagesurf = nullptr; @@ -58,19 +41,12 @@ private: std::string path; QImage image; std::vector half_bytes; - QSize customSize; - uint32_t sourceWidth = 0; - uint32_t sourceHeight = 0; - uint32_t outputWidth = 0; - uint32_t outputHeight = 0; - + uint32_t cx; + uint32_t cy; std::thread th; - std::shared_ptr imagePtr; - bool outputToFile = true; -signals: - void imageReady(QImage image); + int stage = 0; -private slots: - void handleSave(); +public slots: + void Save(); }; diff --git a/frontend/utility/ThumbnailManager.cpp b/frontend/utility/ThumbnailManager.cpp deleted file mode 100644 index 412b831fb..000000000 --- a/frontend/utility/ThumbnailManager.cpp +++ /dev/null @@ -1,256 +0,0 @@ -/****************************************************************************** - Copyright (C) 2025 by Taylor Giampaolo - Lain Bailey - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -******************************************************************************/ - -#include "ThumbnailManager.hpp" - -#include -#include - -#include "display-helpers.hpp" - -#include - -constexpr int MIN_THUMBNAIL_UPDATE_INTERVAL_MS = 100; -constexpr int MIN_SOURCE_UPDATE_INTERVAL_MS = 5000; - -ThumbnailItem::ThumbnailItem(std::string uuid, OBSSource source) : uuid(uuid), weakSource(OBSGetWeakRef(source)) {} - -void ThumbnailItem::init(std::weak_ptr weakActiveItem) -{ - auto thumbnailManager = OBSBasic::Get()->thumbnails(); - if (!thumbnailManager) { - return; - } - - auto it = thumbnailManager->cachedThumbnails.find(uuid); - if (it != thumbnailManager->cachedThumbnails.end()) { - auto &cachedItem = it->second; - pixmap = cachedItem.pixmap.value_or(QPixmap()); - cachedItem.pixmap.reset(); - cachedItem.weakActiveItem = std::move(weakActiveItem); - } -} - -ThumbnailItem::~ThumbnailItem() -{ - auto thumbnailManager = OBSBasic::Get()->thumbnails(); - if (!thumbnailManager) { - return; - } - - auto &cachedItem = thumbnailManager->cachedThumbnails[uuid]; - cachedItem.pixmap = pixmap; - cachedItem.weakActiveItem.reset(); -} - -void ThumbnailItem::imageUpdated(QImage image) -{ - QPixmap newPixmap; - if (!image.isNull()) { - newPixmap = QPixmap::fromImage(image); - } - - pixmap = newPixmap; - emit updateThumbnail(pixmap); -} - -void Thumbnail::thumbnailUpdated(QPixmap pixmap) -{ - emit updateThumbnail(pixmap); -} - -ThumbnailManager::ThumbnailManager(QObject *parent) : QObject(parent) -{ - connect(&updateTimer, &QTimer::timeout, this, &ThumbnailManager::updateTick); -} - -ThumbnailManager::~ThumbnailManager() {} - -std::shared_ptr ThumbnailManager::getThumbnail(OBSSource source) -{ - std::string uuid = obs_source_get_uuid(source); - - for (auto it = thumbnails.begin(); it != thumbnails.end(); ++it) { - auto item = it->lock(); - if (item && item->uuid == uuid) { - return std::make_shared(item); - } - } - - std::shared_ptr thumbnail; - if ((obs_source_get_output_flags(source) & OBS_SOURCE_VIDEO) != 0) { - auto item = std::make_shared(uuid, source); - item->init(std::weak_ptr(item)); - - thumbnail = std::make_shared(item); - connect(item.get(), &ThumbnailItem::updateThumbnail, thumbnail.get(), &Thumbnail::thumbnailUpdated); - - newThumbnails.push_back(std::weak_ptr(item)); - } - - updateIntervalChanged(thumbnails.size()); - return thumbnail; -} - -bool ThumbnailManager::updatePixmap(std::shared_ptr &sharedPointerItem) -{ - ThumbnailItem *item = sharedPointerItem.get(); - - OBSSource source = OBSGetStrongRef(item->weakSource); - if (!source) { - return true; - } - - QPixmap pixmap; - item->pixmap = pixmap; - - if (source) { - uint32_t sourceWidth = obs_source_get_width(source); - uint32_t sourceHeight = obs_source_get_height(source); - - if (sourceWidth == 0 || sourceHeight == 0) { - return true; - } - - auto obj = new ScreenshotObj(source); - obj->setSaveToFile(false); - obj->setSize(Thumbnail::size); - - connect(obj, &ScreenshotObj::imageReady, item, &ThumbnailItem::imageUpdated); - } - - return true; -} - -void ThumbnailManager::updateIntervalChanged(size_t newCount) -{ - int intervalMS = MIN_THUMBNAIL_UPDATE_INTERVAL_MS; - if (newThumbnails.size() == 0 && newCount > 0) { - int count = (int)newCount; - intervalMS = MIN_SOURCE_UPDATE_INTERVAL_MS / count; - if (intervalMS < MIN_THUMBNAIL_UPDATE_INTERVAL_MS) { - intervalMS = MIN_THUMBNAIL_UPDATE_INTERVAL_MS; - } - } - - updateTimer.start(intervalMS); -} - -void ThumbnailManager::updateTick() -{ - std::shared_ptr item; - bool changed = false; - bool newThumbnail = false; - - while (newThumbnails.size() > 0) { - changed = true; - item = newThumbnails.front().lock(); - - newThumbnails.pop_front(); - if (item) { - newThumbnail = true; - break; - } - } - - if (!item) { - while (thumbnails.size() > 0) { - item = thumbnails.front().lock(); - thumbnails.pop_front(); - if (item) { - break; - } else { - changed = true; - } - } - } - if (changed && newThumbnails.size() == 0) { - updateIntervalChanged(thumbnails.size() + (item ? 1 : 0)); - } - if (!item) { - return; - } - - if (updatePixmap(item)) { - thumbnails.push_back(std::weak_ptr(item)); - } else { - thumbnails.push_front(std::weak_ptr(item)); - } -} - -std::optional ThumbnailManager::getCachedThumbnail(OBSSource source) -{ - if (!source) { - return std::nullopt; - } - - std::string uuid = obs_source_get_uuid(source); - auto it = cachedThumbnails.find(uuid); - if (it != cachedThumbnails.end()) { - auto &cachedItem = it->second; - if (cachedItem.pixmap.has_value()) { - return cachedItem.pixmap; - } - - auto activeItem = cachedItem.weakActiveItem.lock(); - return activeItem ? std::make_optional(activeItem->pixmap) : std::nullopt; - } - - return std::nullopt; -} - -void ThumbnailManager::preloadThumbnail(OBSSource source, QObject *object, std::function callback) -{ - if (!source) { - return; - } - - std::string uuid = obs_source_get_uuid(source); - - if (cachedThumbnails.find(uuid) == cachedThumbnails.end()) { - uint32_t sourceWidth = obs_source_get_width(source); - uint32_t sourceHeight = obs_source_get_height(source); - - cachedThumbnails[uuid].pixmap = QPixmap(); - if (sourceWidth == 0 || sourceHeight == 0) { - return; - } - - auto obj = new ScreenshotObj(source); - obj->setSaveToFile(false); - obj->setSize(Thumbnail::size); - - QPointer safeObject = qobject_cast(object); - connect(obj, &ScreenshotObj::imageReady, this, [=](QImage image) { - QPixmap pixmap; - if (!image.isNull()) { - pixmap = QPixmap::fromImage(image); - } - cachedThumbnails[uuid].pixmap = pixmap; - - QMetaObject::invokeMethod( - safeObject, - [safeObject, callback, pixmap]() { - if (safeObject) { - callback(pixmap); - } - }, - Qt::QueuedConnection); - }); - } -} diff --git a/frontend/utility/ThumbnailManager.hpp b/frontend/utility/ThumbnailManager.hpp deleted file mode 100644 index 1c7fea2b2..000000000 --- a/frontend/utility/ThumbnailManager.hpp +++ /dev/null @@ -1,106 +0,0 @@ -/****************************************************************************** - Copyright (C) 2025 by Taylor Giampaolo - Lain Bailey - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -******************************************************************************/ - -#pragma once - -#include - -#include -#include -#include -#include - -#include -#include - -class ThumbnailItem : public QObject { - Q_OBJECT - - friend class ThumbnailManager; - friend class Thumbnail; - - std::string uuid; - OBSWeakSource weakSource; - QPixmap pixmap; - - void init(std::weak_ptr weakActiveItem); - void imageUpdated(QImage image); - -public: - ThumbnailItem(std::string uuid, OBSSource source); - ~ThumbnailItem(); - - inline bool isNull() const { return !weakSource || obs_weak_source_expired(weakSource); } - inline const std::string &getUuid() const { return uuid; } - -signals: - void updateThumbnail(QPixmap pixmap); -}; - -class Thumbnail : public QObject { - Q_OBJECT - - friend class ThumbnailManager; - - std::shared_ptr item; - -private slots: - void thumbnailUpdated(QPixmap pixmap); - -public: - inline Thumbnail(std::shared_ptr item) : item(item) {} - - inline QPixmap getPixmap() const { return item->pixmap; } - - static constexpr QSize size = {320, 180}; - -signals: - void updateThumbnail(QPixmap pixmap); -}; - -class ThumbnailManager : public QObject { - Q_OBJECT - - friend class ThumbnailItem; - - struct CachedItem { - std::optional pixmap; - std::weak_ptr weakActiveItem; - }; - - std::deque> newThumbnails; - std::deque> thumbnails; - std::unordered_map cachedThumbnails; - QTimer updateTimer; - - bool updatePixmap(std::shared_ptr &item); - void updateTick(); - - void updateIntervalChanged(size_t newCount); - -public: - explicit ThumbnailManager(QObject *parent = nullptr); - ~ThumbnailManager(); - - std::shared_ptr getThumbnail(OBSSource source); - std::optional getCachedThumbnail(OBSSource source); - void preloadThumbnail(OBSSource source, QObject *object, std::function callback); - -private: - Q_DISABLE_COPY_MOVE(ThumbnailManager); -}; diff --git a/frontend/widgets/OBSBasic.cpp b/frontend/widgets/OBSBasic.cpp index bef205694..f08527cbc 100644 --- a/frontend/widgets/OBSBasic.cpp +++ b/frontend/widgets/OBSBasic.cpp @@ -243,8 +243,6 @@ OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new api = InitializeAPIInterface(this); - thumbnailManager = new ThumbnailManager(this); - ui->setupUi(this); ui->previewDisabledWidget->setVisible(false); diff --git a/frontend/widgets/OBSBasic.hpp b/frontend/widgets/OBSBasic.hpp index 8a3d84eab..2561a7c10 100644 --- a/frontend/widgets/OBSBasic.hpp +++ b/frontend/widgets/OBSBasic.hpp @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -53,7 +52,6 @@ class OBSBasicAdvAudio; class OBSBasicFilters; class OBSBasicInteraction; class OBSBasicProperties; -class OBSBasicSourceSelect; class OBSBasicTransform; class OBSLogViewer; class OBSMissingFiles; @@ -284,8 +282,6 @@ private: // TODO: Remove, orphaned instance method void LoadProject(); - ThumbnailManager *thumbnailManager = nullptr; - public slots: void close(); void UpdatePatronJson(const QString &text, const QString &error); @@ -320,8 +316,6 @@ public: inline bool isClosePromptOpen() { return isClosePromptOpen_; } void closeWindow(); - ThumbnailManager *thumbnails() const { return thumbnailManager; } - protected: bool isReadyToClose(); bool promptToClose(); @@ -477,9 +471,6 @@ private: void dragMoveEvent(QDragMoveEvent *event) override; void dropEvent(QDropEvent *event) override; -signals: - void sourceUuidDropped(QString uuid); - /* ------------------------------------- * MARK: - OBSBasic_Hotkeys * ------------------------------------- @@ -575,7 +566,6 @@ private: QPointer advAudioWindow; QPointer filters; QPointer about; - QPointer addWindow; QPointer logView; QPointer stats; QPointer remux; @@ -1178,8 +1168,11 @@ private: static void SourceRemoved(void *data, calldata_t *params); static void SourceRenamed(void *data, calldata_t *params); + void AddSource(const char *id); + QMenu *CreateAddSourcePopupMenu(); + void AddSourcePopupMenu(const QPoint &pos); + private slots: - void AddSourceDialog(); void RenameSources(OBSSource source, QString newName, QString prevName); void ReorderSources(OBSScene scene); diff --git a/frontend/widgets/OBSBasic_Dropfiles.cpp b/frontend/widgets/OBSBasic_Dropfiles.cpp index 4e28ecfe4..53c2f3e4f 100644 --- a/frontend/widgets/OBSBasic_Dropfiles.cpp +++ b/frontend/widgets/OBSBasic_Dropfiles.cpp @@ -214,10 +214,6 @@ void OBSBasic::AddDropSource(const char *data, DropType image) void OBSBasic::dragEnterEvent(QDragEnterEvent *event) { - if (event->mimeData()->hasFormat("application/x-obs-source-uuid")) { - event->acceptProposedAction(); - } - // refuse drops of our own widgets if (event->source() != nullptr) { event->setDropAction(Qt::IgnoreAction); @@ -328,10 +324,5 @@ void OBSBasic::dropEvent(QDropEvent *event) } } else if (mimeData->hasText()) { AddDropSource(QT_TO_UTF8(mimeData->text()), DropType_RawText); - } else if (event->mimeData()->hasFormat("application/x-obs-source-uuid")) { - QString uuid = QString::fromUtf8(event->mimeData()->data("application/x-obs-source-uuid")); - - emit sourceUuidDropped(uuid); - event->acceptProposedAction(); } } diff --git a/frontend/widgets/OBSBasic_SceneItems.cpp b/frontend/widgets/OBSBasic_SceneItems.cpp index 79dd80bbd..84eed637d 100644 --- a/frontend/widgets/OBSBasic_SceneItems.cpp +++ b/frontend/widgets/OBSBasic_SceneItems.cpp @@ -49,25 +49,6 @@ void setHiddenInMixer(obs_source_t *source, bool hidden) OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); obs_data_set_bool(priv_settings, "mixer_hidden", hidden); } - -std::string getNewSourceName(std::string_view name) -{ - std::string newName{name}; - int suffix = 1; - - for (;;) { - OBSSourceAutoRelease existing_source = obs_get_source_by_name(newName.c_str()); - if (!existing_source) { - break; - } - - char nextName[256]; - std::snprintf(nextName, sizeof(nextName), "%s (%d)", name.data(), ++suffix); - newName = nextName; - } - - return newName; -} } // namespace static inline bool HasAudioDevices(const char *source_id) @@ -219,6 +200,8 @@ void OBSBasic::SourceRenamed(void *data, calldata_t *params) blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName); } +extern char *get_new_source_name(const char *name, const char *format); + void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel) { bool disable = deviceId && strcmp(deviceId, "disabled") == 0; @@ -239,11 +222,11 @@ void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, cons } } else if (!disable) { - std::string name = getNewSourceName(deviceDesc); + BPtr name = get_new_source_name(deviceDesc, "%s (%d)"); settings = obs_data_create(); obs_data_set_string(settings, "device_id", deviceId); - source = obs_source_create(sourceId, name.c_str(), settings, nullptr); + source = obs_source_create(sourceId, name, settings, nullptr); obs_set_output_source(channel, source); } @@ -587,14 +570,10 @@ void OBSBasic::CreateSourcePopupMenu(int idx, bool preview) } // Add new source - QAction *addSource = popup.addAction(QTStr("AddSource"), this, SLOT(AddSourceDialog())); - popup.addAction(addSource); - popup.addSeparator(); - - if (!preview && !sourceSelected) { - QAction *addGroup = new QAction(QTStr("Basic.Main.NewGroup"), this); - connect(addGroup, &QAction::triggered, ui->sources, &SourceTree::AddGroup); - popup.addAction(addGroup); + QPointer addSourceMenu = CreateAddSourcePopupMenu(); + if (addSourceMenu) { + popup.addMenu(addSourceMenu); + popup.addSeparator(); } // Preview menu entries @@ -710,12 +689,14 @@ void OBSBasic::CreateSourcePopupMenu(int idx, bool preview) // Source grouping if (ui->sources->MultipleBaseSelected()) { + popup.addSeparator(); popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources, &SourceTree::GroupSelectedItems); - popup.addSeparator(); + } else if (ui->sources->GroupsSelected()) { - popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups); popup.addSeparator(); + popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups); } + popup.addSeparator(); popup.addAction(ui->actionCopySource); popup.addAction(ui->actionPasteRef); @@ -795,27 +776,115 @@ static inline bool should_show_properties(obs_source_t *source, const char *id) return true; } -void OBSBasic::AddSourceDialog() +void OBSBasic::AddSource(const char *id) { - QAction *action = qobject_cast(sender()); - if (!action) { + if (id && *id) { + OBSBasicSourceSelect sourceSelect(this, id, undo_s); + sourceSelect.exec(); + if (should_show_properties(sourceSelect.newSource, id)) { + CreatePropertiesWindow(sourceSelect.newSource); + } + } +} + +QMenu *OBSBasic::CreateAddSourcePopupMenu() +{ + const char *unversioned_type; + const char *type; + bool foundValues = false; + bool foundDeprecated = false; + size_t idx = 0; + + QMenu *popup = new QMenu(QTStr("AddSource"), this); + QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup); + + auto getActionAfter = [](QMenu *menu, const QString &name) { + QList actions = menu->actions(); + + for (QAction *menuAction : actions) { + if (menuAction->text().compare(name, Qt::CaseInsensitive) >= 0) + return menuAction; + } + + return (QAction *)nullptr; + }; + + auto addSource = [this, getActionAfter](QMenu *popup, const char *type, const char *name) { + QString qname = QT_UTF8(name); + QAction *popupItem = new QAction(qname, this); + connect(popupItem, &QAction::triggered, this, [this, type]() { AddSource(type); }); + + QIcon icon; + + if (strcmp(type, "scene") == 0) + icon = GetSceneIcon(); + else + icon = GetSourceIcon(type); + + popupItem->setIcon(icon); + + QAction *after = getActionAfter(popup, qname); + popup->insertAction(after, popupItem); + }; + + while (obs_enum_input_types2(idx++, &type, &unversioned_type)) { + const char *name = obs_source_get_display_name(type); + uint32_t caps = obs_get_source_output_flags(type); + + if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) + continue; + + if ((caps & OBS_SOURCE_DEPRECATED) == 0) { + addSource(popup, unversioned_type, name); + } else { + addSource(deprecated, unversioned_type, name); + foundDeprecated = true; + } + foundValues = true; + } + + addSource(popup, "scene", Str("Basic.Scene")); + + popup->addSeparator(); + QAction *addGroup = new QAction(QTStr("Group"), this); + addGroup->setIcon(GetGroupIcon()); + connect(addGroup, &QAction::triggered, this, [this]() { AddSource("group"); }); + popup->addAction(addGroup); + + if (!foundDeprecated) { + delete deprecated; + deprecated = nullptr; + } + + if (!foundValues) { + delete popup; + popup = nullptr; + + } else if (foundDeprecated) { + popup->addSeparator(); + popup->addMenu(deprecated); + } + + return popup; +} + +void OBSBasic::AddSourcePopupMenu(const QPoint &pos) +{ + if (!GetCurrentScene()) { + // Tell the user he needs a scene first (help beginners). + OBSMessageBox::information(this, QTStr("Basic.Main.AddSourceHelp.Title"), + QTStr("Basic.Main.AddSourceHelp.Text")); return; } - if (addWindow) { - addWindow->close(); - } - - addWindow = new OBSBasicSourceSelect(this, undo_s); - addWindow->show(); - - addWindow->setAttribute(Qt::WA_DeleteOnClose, true); - connect(this, &OBSBasic::sourceUuidDropped, addWindow, &OBSBasicSourceSelect::sourceDropped); + QScopedPointer popup(CreateAddSourcePopupMenu()); + if (popup) + popup->exec(pos); } void OBSBasic::on_actionAddSource_triggered() { - AddSourceDialog(); + AddSourcePopupMenu(QCursor::pos()); } static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param)