Skip to content

Commit 62a8220

Browse files
Warchamp7Lain-B
andcommitted
frontend: Replace add source dropdown with dialog
Co-Authored-By: Lain <[email protected]>
1 parent fc21c0f commit 62a8220

23 files changed

+2601
-522
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
@@ -65,6 +65,8 @@ target_sources(
6565
utility/StartMultiTrackVideoStreamingGuard.hpp
6666
utility/SurfaceEventFilter.hpp
6767
utility/system-info.hpp
68+
utility/ThumbnailManager.cpp
69+
utility/ThumbnailManager.hpp
6870
utility/undo_stack.cpp
6971
utility/undo_stack.hpp
7072
utility/VCamConfig.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 <[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+
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 <[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: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
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+
44+
void FlowLayout::addItem(QLayoutItem *item)
45+
{
46+
itemList.append(item);
47+
}
48+
49+
int FlowLayout::horizontalSpacing() const
50+
{
51+
if (m_hSpace >= 0) {
52+
return m_hSpace;
53+
} else {
54+
return smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
55+
}
56+
}
57+
58+
int FlowLayout::verticalSpacing() const
59+
{
60+
if (m_vSpace >= 0) {
61+
return m_vSpace;
62+
} else {
63+
return smartSpacing(QStyle::PM_LayoutVerticalSpacing);
64+
}
65+
}
66+
67+
int FlowLayout::count() const
68+
{
69+
return itemList.size();
70+
}
71+
72+
QLayoutItem *FlowLayout::itemAt(int index) const
73+
{
74+
return itemList.value(index);
75+
}
76+
77+
QLayoutItem *FlowLayout::takeAt(int index)
78+
{
79+
if (index >= 0 && index < itemList.size()) {
80+
return itemList.takeAt(index);
81+
}
82+
return nullptr;
83+
}
84+
85+
Qt::Orientations FlowLayout::expandingDirections() const
86+
{
87+
return {};
88+
}
89+
90+
bool FlowLayout::hasHeightForWidth() const
91+
{
92+
return true;
93+
}
94+
95+
int FlowLayout::heightForWidth(int width) const
96+
{
97+
int height = doLayout(QRect(0, 0, width, 0), true);
98+
return height;
99+
}
100+
101+
void FlowLayout::setGeometry(const QRect &rect)
102+
{
103+
QLayout::setGeometry(rect);
104+
doLayout(rect, false);
105+
}
106+
107+
QSize FlowLayout::sizeHint() const
108+
{
109+
return minimumSize();
110+
}
111+
112+
QSize FlowLayout::minimumSize() const
113+
{
114+
QSize size;
115+
for (const QLayoutItem *item : std::as_const(itemList)) {
116+
size = size.expandedTo(item->minimumSize());
117+
}
118+
119+
const QMargins margins = contentsMargins();
120+
size += QSize(margins.left() + margins.right(), margins.top() + margins.bottom());
121+
return size;
122+
}
123+
124+
int FlowLayout::doLayout(const QRect &rect, bool testOnly) const
125+
{
126+
int left, top, right, bottom;
127+
getContentsMargins(&left, &top, &right, &bottom);
128+
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
129+
int x = effectiveRect.x();
130+
int y = effectiveRect.y();
131+
int lineHeight = 0;
132+
133+
for (QLayoutItem *item : std::as_const(itemList)) {
134+
const QWidget *wid = item->widget();
135+
int spaceX = horizontalSpacing();
136+
if (spaceX == -1) {
137+
spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton,
138+
Qt::Horizontal);
139+
}
140+
141+
int spaceY = verticalSpacing();
142+
if (spaceY == -1) {
143+
spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton,
144+
Qt::Vertical);
145+
}
146+
147+
int nextX = x + item->sizeHint().width() + spaceX;
148+
if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
149+
x = effectiveRect.x();
150+
y = y + lineHeight + spaceY;
151+
nextX = x + item->sizeHint().width() + spaceX;
152+
lineHeight = 0;
153+
}
154+
155+
if (!testOnly) {
156+
item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
157+
}
158+
159+
x = nextX;
160+
lineHeight = qMax(lineHeight, item->sizeHint().height());
161+
}
162+
return y + lineHeight - rect.y() + bottom;
163+
}
164+
165+
int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const
166+
{
167+
QObject *parent = this->parent();
168+
if (!parent) {
169+
return -1;
170+
} else if (parent->isWidgetType()) {
171+
QWidget *pw = static_cast<QWidget *>(parent);
172+
return pw->style()->pixelMetric(pm, nullptr, pw);
173+
} else {
174+
return static_cast<QLayout *>(parent)->spacing();
175+
}
176+
}

0 commit comments

Comments
 (0)