Skip to content

Commit 5bf01aa

Browse files
committed
frontend: Replace add source dropdown with dialog
1 parent 523ddee commit 5bf01aa

21 files changed

+2396
-506
lines changed

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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ target_sources(
6363
utility/StartMultiTrackVideoStreamingGuard.hpp
6464
utility/SurfaceEventFilter.hpp
6565
utility/system-info.hpp
66+
utility/ThumbnailManager.cpp
67+
utility/ThumbnailManager.hpp
6668
utility/undo_stack.cpp
6769
utility/undo_stack.hpp
6870
utility/VCamConfig.hpp

frontend/components/FlowFrame.cpp

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/******************************************************************************
2+
Copyright (C) 2025 by Taylor Giampaolo <[email protected]>
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+
int index = -1;
36+
for (int i = 0; i < layout->count(); ++i) {
37+
if (!layout->itemAt(i)->widget()) {
38+
continue;
39+
}
40+
41+
auto focusProxy = layout->itemAt(i)->widget()->focusProxy();
42+
if (layout->itemAt(i)->widget() == focused || focusProxy == focused) {
43+
if (focusProxy == focused) {
44+
focused = layout->itemAt(i)->widget();
45+
}
46+
47+
index = i;
48+
break;
49+
}
50+
}
51+
52+
if (index == -1)
53+
return;
54+
55+
const QRect focusedRect = focused->geometry();
56+
QWidget *nextFocus = nullptr;
57+
58+
switch (event->key()) {
59+
case Qt::Key_Right:
60+
case Qt::Key_Down:
61+
case Qt::Key_Left:
62+
case Qt::Key_Up: {
63+
/* Find next widget in the given direction */
64+
int bestDistance = INT_MAX;
65+
for (int i = 0; i < layout->count(); ++i) {
66+
if (i == index)
67+
continue;
68+
69+
QWidget *widget = layout->itemAt(i)->widget();
70+
const QRect rect = widget->geometry();
71+
72+
bool isCandidate = false;
73+
int distance = INT_MAX;
74+
75+
switch (event->key()) {
76+
case Qt::Key_Right:
77+
if (rect.left() > focusedRect.right()) {
78+
distance = (rect.left() - focusedRect.right()) +
79+
qAbs(rect.center().y() - focusedRect.center().y());
80+
isCandidate = true;
81+
}
82+
break;
83+
case Qt::Key_Left:
84+
if (rect.right() < focusedRect.left()) {
85+
distance = (focusedRect.left() - rect.right()) +
86+
qAbs(rect.center().y() - focusedRect.center().y());
87+
isCandidate = true;
88+
}
89+
break;
90+
case Qt::Key_Down:
91+
if (rect.top() > focusedRect.bottom()) {
92+
distance = (rect.top() - focusedRect.bottom()) +
93+
qAbs(rect.center().x() - focusedRect.center().x());
94+
isCandidate = true;
95+
}
96+
break;
97+
case Qt::Key_Up:
98+
if (rect.bottom() < focusedRect.top()) {
99+
distance = (focusedRect.top() - rect.bottom()) +
100+
qAbs(rect.center().x() - focusedRect.center().x());
101+
isCandidate = true;
102+
}
103+
break;
104+
}
105+
106+
if (isCandidate && distance < bestDistance) {
107+
bestDistance = distance;
108+
nextFocus = widget;
109+
}
110+
}
111+
break;
112+
}
113+
default:
114+
QWidget::keyPressEvent(event);
115+
return;
116+
}
117+
118+
if (nextFocus) {
119+
nextFocus->setFocus();
120+
121+
QWidget *scrollParent = nextFocus->parentWidget();
122+
while (scrollParent) {
123+
QScrollArea *scrollArea = qobject_cast<QScrollArea *>(scrollParent);
124+
if (scrollArea) {
125+
scrollArea->ensureWidgetVisible(nextFocus, 20, 20);
126+
break;
127+
}
128+
scrollParent = scrollParent->parentWidget();
129+
}
130+
}
131+
}

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 <[email protected]>
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: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/******************************************************************************
2+
Example provided by Qt
3+
<https://doc.qt.io/qt-6/qtwidgets-layouts-flowlayout-example.html>
4+
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU General Public License as published by
7+
the Free Software Foundation, either version 2 of the License, or
8+
(at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU General Public License for more details.
14+
15+
You should have received a copy of the GNU General Public License
16+
along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
******************************************************************************/
18+
19+
#include "FlowLayout.hpp"
20+
21+
#include <QWidget>
22+
23+
FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing)
24+
: QLayout(parent),
25+
m_hSpace(hSpacing),
26+
m_vSpace(vSpacing)
27+
{
28+
setContentsMargins(margin, margin, margin, margin);
29+
}
30+
31+
FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing) : m_hSpace(hSpacing), m_vSpace(vSpacing)
32+
{
33+
setContentsMargins(margin, margin, margin, margin);
34+
}
35+
36+
FlowLayout::~FlowLayout()
37+
{
38+
QLayoutItem *item;
39+
while ((item = takeAt(0)))
40+
delete item;
41+
}
42+
43+
void FlowLayout::addItem(QLayoutItem *item)
44+
{
45+
itemList.append(item);
46+
}
47+
48+
int FlowLayout::horizontalSpacing() const
49+
{
50+
if (m_hSpace >= 0) {
51+
return m_hSpace;
52+
} else {
53+
return smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
54+
}
55+
}
56+
57+
int FlowLayout::verticalSpacing() const
58+
{
59+
if (m_vSpace >= 0) {
60+
return m_vSpace;
61+
} else {
62+
return smartSpacing(QStyle::PM_LayoutVerticalSpacing);
63+
}
64+
}
65+
66+
int FlowLayout::count() const
67+
{
68+
return itemList.size();
69+
}
70+
71+
QLayoutItem *FlowLayout::itemAt(int index) const
72+
{
73+
return itemList.value(index);
74+
}
75+
76+
QLayoutItem *FlowLayout::takeAt(int index)
77+
{
78+
if (index >= 0 && index < itemList.size())
79+
return itemList.takeAt(index);
80+
return nullptr;
81+
}
82+
83+
Qt::Orientations FlowLayout::expandingDirections() const
84+
{
85+
return {};
86+
}
87+
88+
bool FlowLayout::hasHeightForWidth() const
89+
{
90+
return true;
91+
}
92+
93+
int FlowLayout::heightForWidth(int width) const
94+
{
95+
int height = doLayout(QRect(0, 0, width, 0), true);
96+
return height;
97+
}
98+
99+
void FlowLayout::setGeometry(const QRect &rect)
100+
{
101+
QLayout::setGeometry(rect);
102+
doLayout(rect, false);
103+
}
104+
105+
QSize FlowLayout::sizeHint() const
106+
{
107+
return minimumSize();
108+
}
109+
110+
QSize FlowLayout::minimumSize() const
111+
{
112+
QSize size;
113+
for (const QLayoutItem *item : std::as_const(itemList))
114+
size = size.expandedTo(item->minimumSize());
115+
116+
const QMargins margins = contentsMargins();
117+
size += QSize(margins.left() + margins.right(), margins.top() + margins.bottom());
118+
return size;
119+
}
120+
121+
int FlowLayout::doLayout(const QRect &rect, bool testOnly) const
122+
{
123+
int left, top, right, bottom;
124+
getContentsMargins(&left, &top, &right, &bottom);
125+
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
126+
int x = effectiveRect.x();
127+
int y = effectiveRect.y();
128+
int lineHeight = 0;
129+
130+
for (QLayoutItem *item : std::as_const(itemList)) {
131+
const QWidget *wid = item->widget();
132+
int spaceX = horizontalSpacing();
133+
if (spaceX == -1)
134+
spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton,
135+
Qt::Horizontal);
136+
int spaceY = verticalSpacing();
137+
if (spaceY == -1)
138+
spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton,
139+
Qt::Vertical);
140+
141+
int nextX = x + item->sizeHint().width() + spaceX;
142+
if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
143+
x = effectiveRect.x();
144+
y = y + lineHeight + spaceY;
145+
nextX = x + item->sizeHint().width() + spaceX;
146+
lineHeight = 0;
147+
}
148+
149+
if (!testOnly)
150+
item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
151+
152+
x = nextX;
153+
lineHeight = qMax(lineHeight, item->sizeHint().height());
154+
}
155+
return y + lineHeight - rect.y() + bottom;
156+
}
157+
158+
int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const
159+
{
160+
QObject *parent = this->parent();
161+
if (!parent) {
162+
return -1;
163+
} else if (parent->isWidgetType()) {
164+
QWidget *pw = static_cast<QWidget *>(parent);
165+
return pw->style()->pixelMetric(pm, nullptr, pw);
166+
} else {
167+
return static_cast<QLayout *>(parent)->spacing();
168+
}
169+
}

0 commit comments

Comments
 (0)