Skip to content

Commit 861cc5f

Browse files
Warchamp7Lain-B
andcommitted
frontend: Replace add source dropdown with dialog
Co-Authored-By: Lain <134130700+Lain-B@users.noreply.github.com>
1 parent f80d35f commit 861cc5f

26 files changed

Lines changed: 2634 additions & 520 deletions

frontend/cmake/ui-components.cmake

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ target_sources(
3030
components/DisplayCaptureToolbar.cpp
3131
components/DisplayCaptureToolbar.hpp
3232
components/EditWidget.hpp
33+
components/FlowFrame.cpp
34+
components/FlowFrame.hpp
35+
components/FlowLayout.cpp
36+
components/FlowLayout.hpp
3337
components/FocusList.cpp
3438
components/FocusList.hpp
3539
components/GameCaptureToolbar.cpp
@@ -57,6 +61,8 @@ target_sources(
5761
components/SceneTree.hpp
5862
components/SilentUpdateCheckBox.hpp
5963
components/SilentUpdateSpinBox.hpp
64+
components/SourceSelectButton.cpp
65+
components/SourceSelectButton.hpp
6066
components/SourceToolbar.cpp
6167
components/SourceToolbar.hpp
6268
components/SourceTree.cpp

frontend/cmake/ui-utility.cmake

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ target_sources(
4747
utility/RemuxQueueModel.hpp
4848
utility/RemuxWorker.cpp
4949
utility/RemuxWorker.hpp
50+
utility/ResizeSignaler.hpp
5051
utility/SceneRenameDelegate.cpp
5152
utility/SceneRenameDelegate.hpp
5253
utility/ScreenshotObj.cpp
@@ -56,6 +57,8 @@ target_sources(
5657
utility/SimpleOutput.hpp
5758
utility/StartMultiTrackVideoStreamingGuard.hpp
5859
utility/SurfaceEventFilter.hpp
60+
utility/ThumbnailManager.cpp
61+
utility/ThumbnailManager.hpp
5962
utility/VCamConfig.hpp
6063
utility/VolumeMeterTimer.cpp
6164
utility/VolumeMeterTimer.hpp

frontend/components/FlowFrame.cpp

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/******************************************************************************
2+
Copyright (C) 2025 by Taylor Giampaolo <warchamp7@obsproject.com>
3+
4+
This program is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 2 of the License, or
7+
(at your option) any later version.
8+
9+
This program is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
******************************************************************************/
17+
18+
#include <components/FlowFrame.hpp>
19+
20+
#include <QKeyEvent>
21+
#include <QScrollArea>
22+
23+
FlowFrame::FlowFrame(QWidget *parent) : QFrame(parent)
24+
{
25+
layout = new FlowLayout(this);
26+
setLayout(layout);
27+
}
28+
29+
void FlowFrame::keyPressEvent(QKeyEvent *event)
30+
{
31+
QWidget *focused = focusWidget();
32+
if (!focused) {
33+
return;
34+
}
35+
36+
int index = -1;
37+
for (int i = 0; i < layout->count(); ++i) {
38+
if (!layout->itemAt(i)->widget()) {
39+
continue;
40+
}
41+
42+
auto focusProxy = layout->itemAt(i)->widget()->focusProxy();
43+
if (layout->itemAt(i)->widget() == focused || focusProxy == focused) {
44+
if (focusProxy == focused) {
45+
focused = layout->itemAt(i)->widget();
46+
}
47+
48+
index = i;
49+
break;
50+
}
51+
}
52+
53+
if (index == -1) {
54+
return;
55+
}
56+
57+
const QRect focusedRect = focused->geometry();
58+
QWidget *nextFocus = nullptr;
59+
60+
switch (event->key()) {
61+
case Qt::Key_Right:
62+
case Qt::Key_Down:
63+
case Qt::Key_Left:
64+
case Qt::Key_Up: {
65+
// Find next widget in the given direction
66+
int bestDistance = INT_MAX;
67+
for (int i = 0; i < layout->count(); ++i) {
68+
if (i == index) {
69+
continue;
70+
}
71+
72+
QWidget *widget = layout->itemAt(i)->widget();
73+
const QRect rect = widget->geometry();
74+
75+
bool isCandidate = false;
76+
int distance = INT_MAX;
77+
78+
switch (event->key()) {
79+
case Qt::Key_Right:
80+
if (rect.left() > focusedRect.right()) {
81+
distance = (rect.left() - focusedRect.right()) +
82+
qAbs(rect.center().y() - focusedRect.center().y());
83+
isCandidate = true;
84+
}
85+
break;
86+
case Qt::Key_Left:
87+
if (rect.right() < focusedRect.left()) {
88+
distance = (focusedRect.left() - rect.right()) +
89+
qAbs(rect.center().y() - focusedRect.center().y());
90+
isCandidate = true;
91+
}
92+
break;
93+
case Qt::Key_Down:
94+
if (rect.top() > focusedRect.bottom()) {
95+
distance = (rect.top() - focusedRect.bottom()) +
96+
qAbs(rect.center().x() - focusedRect.center().x());
97+
isCandidate = true;
98+
}
99+
break;
100+
case Qt::Key_Up:
101+
if (rect.bottom() < focusedRect.top()) {
102+
distance = (focusedRect.top() - rect.bottom()) +
103+
qAbs(rect.center().x() - focusedRect.center().x());
104+
isCandidate = true;
105+
}
106+
break;
107+
}
108+
109+
if (isCandidate && distance < bestDistance) {
110+
bestDistance = distance;
111+
nextFocus = widget;
112+
}
113+
}
114+
break;
115+
}
116+
default:
117+
QWidget::keyPressEvent(event);
118+
return;
119+
}
120+
121+
if (nextFocus) {
122+
nextFocus->setFocus();
123+
124+
QWidget *scrollParent = nextFocus->parentWidget();
125+
while (scrollParent) {
126+
QScrollArea *scrollArea = qobject_cast<QScrollArea *>(scrollParent);
127+
if (scrollArea) {
128+
scrollArea->ensureWidgetVisible(nextFocus, 20, 20);
129+
break;
130+
}
131+
scrollParent = scrollParent->parentWidget();
132+
}
133+
}
134+
}

frontend/components/FlowFrame.hpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/******************************************************************************
2+
Copyright (C) 2025 by Taylor Giampaolo <warchamp7@obsproject.com>
3+
4+
This program is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 2 of the License, or
7+
(at your option) any later version.
8+
9+
This program is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
******************************************************************************/
17+
18+
#pragma once
19+
20+
#include "FlowLayout.hpp"
21+
22+
#include <QFrame>
23+
24+
class FlowFrame : public QFrame {
25+
Q_OBJECT
26+
27+
public:
28+
explicit FlowFrame(QWidget *parent = nullptr);
29+
30+
FlowLayout *flowLayout() const { return layout; }
31+
32+
protected:
33+
void keyPressEvent(QKeyEvent *event) override;
34+
35+
private:
36+
FlowLayout *layout;
37+
};

frontend/components/FlowLayout.cpp

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/******************************************************************************
2+
Example provided by Qt
3+
<https://doc.qt.io/qt-6/qtwidgets-layouts-flowlayout-example.html>
4+
5+
Copyright (C) 2016 The Qt Company Ltd.
6+
SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
7+
******************************************************************************/
8+
9+
#include "FlowLayout.hpp"
10+
11+
#include <QWidget>
12+
13+
FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing)
14+
: QLayout(parent),
15+
m_hSpace(hSpacing),
16+
m_vSpace(vSpacing)
17+
{
18+
setContentsMargins(margin, margin, margin, margin);
19+
}
20+
21+
FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing) : m_hSpace(hSpacing), m_vSpace(vSpacing)
22+
{
23+
setContentsMargins(margin, margin, margin, margin);
24+
}
25+
26+
FlowLayout::~FlowLayout()
27+
{
28+
QLayoutItem *item;
29+
while ((item = takeAt(0))) {
30+
delete item;
31+
}
32+
}
33+
34+
void FlowLayout::addItem(QLayoutItem *item)
35+
{
36+
itemList.append(item);
37+
}
38+
39+
int FlowLayout::horizontalSpacing() const
40+
{
41+
if (m_hSpace >= 0) {
42+
return m_hSpace;
43+
} else {
44+
return smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
45+
}
46+
}
47+
48+
int FlowLayout::verticalSpacing() const
49+
{
50+
if (m_vSpace >= 0) {
51+
return m_vSpace;
52+
} else {
53+
return smartSpacing(QStyle::PM_LayoutVerticalSpacing);
54+
}
55+
}
56+
57+
int FlowLayout::count() const
58+
{
59+
return itemList.size();
60+
}
61+
62+
QLayoutItem *FlowLayout::itemAt(int index) const
63+
{
64+
return itemList.value(index);
65+
}
66+
67+
QLayoutItem *FlowLayout::takeAt(int index)
68+
{
69+
if (index >= 0 && index < itemList.size()) {
70+
return itemList.takeAt(index);
71+
}
72+
return nullptr;
73+
}
74+
75+
Qt::Orientations FlowLayout::expandingDirections() const
76+
{
77+
return {};
78+
}
79+
80+
bool FlowLayout::hasHeightForWidth() const
81+
{
82+
return true;
83+
}
84+
85+
int FlowLayout::heightForWidth(int width) const
86+
{
87+
int height = doLayout(QRect(0, 0, width, 0), true);
88+
return height;
89+
}
90+
91+
void FlowLayout::setGeometry(const QRect &rect)
92+
{
93+
QLayout::setGeometry(rect);
94+
doLayout(rect, false);
95+
}
96+
97+
QSize FlowLayout::sizeHint() const
98+
{
99+
return minimumSize();
100+
}
101+
102+
QSize FlowLayout::minimumSize() const
103+
{
104+
QSize size;
105+
for (const QLayoutItem *item : std::as_const(itemList)) {
106+
size = size.expandedTo(item->minimumSize());
107+
}
108+
109+
const QMargins margins = contentsMargins();
110+
size += QSize(margins.left() + margins.right(), margins.top() + margins.bottom());
111+
return size;
112+
}
113+
114+
int FlowLayout::doLayout(const QRect &rect, bool testOnly) const
115+
{
116+
int left, top, right, bottom;
117+
getContentsMargins(&left, &top, &right, &bottom);
118+
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
119+
int x = effectiveRect.x();
120+
int y = effectiveRect.y();
121+
int lineHeight = 0;
122+
123+
for (QLayoutItem *item : std::as_const(itemList)) {
124+
const QWidget *wid = item->widget();
125+
int spaceX = horizontalSpacing();
126+
if (spaceX == -1) {
127+
spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton,
128+
Qt::Horizontal);
129+
}
130+
131+
int spaceY = verticalSpacing();
132+
if (spaceY == -1) {
133+
spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton,
134+
Qt::Vertical);
135+
}
136+
137+
int nextX = x + item->sizeHint().width() + spaceX;
138+
if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
139+
x = effectiveRect.x();
140+
y = y + lineHeight + spaceY;
141+
nextX = x + item->sizeHint().width() + spaceX;
142+
lineHeight = 0;
143+
}
144+
145+
if (!testOnly) {
146+
item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
147+
}
148+
149+
x = nextX;
150+
lineHeight = qMax(lineHeight, item->sizeHint().height());
151+
}
152+
return y + lineHeight - rect.y() + bottom;
153+
}
154+
155+
int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const
156+
{
157+
QObject *parent = this->parent();
158+
if (!parent) {
159+
return -1;
160+
} else if (parent->isWidgetType()) {
161+
QWidget *pw = static_cast<QWidget *>(parent);
162+
return pw->style()->pixelMetric(pm, nullptr, pw);
163+
} else {
164+
return static_cast<QLayout *>(parent)->spacing();
165+
}
166+
}

0 commit comments

Comments
 (0)