Expandable sidebar
This commit is contained in:
parent
4bf830763f
commit
e326eabbf8
4 changed files with 125 additions and 17 deletions
|
|
@ -10,8 +10,10 @@
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QSplitter>
|
#include <QSplitter>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
#include <QEvent>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include <QScrollArea>
|
#include <QScrollArea>
|
||||||
|
#include <QSizePolicy>
|
||||||
|
|
||||||
#include <QtNodes/GraphicsViewStyle>
|
#include <QtNodes/GraphicsViewStyle>
|
||||||
#include <QtNodes/NodeStyle>
|
#include <QtNodes/NodeStyle>
|
||||||
|
|
@ -59,12 +61,14 @@ GraphEditorWidget::GraphEditorWidget(Potato::PipeWireController *controller, QWi
|
||||||
m_view = new QtNodes::GraphicsView(m_scene);
|
m_view = new QtNodes::GraphicsView(m_scene);
|
||||||
m_scene->setBackgroundBrush(QColor(28, 30, 34));
|
m_scene->setBackgroundBrush(QColor(28, 30, 34));
|
||||||
|
|
||||||
auto *splitter = new QSplitter(this);
|
m_splitter = new QSplitter(this);
|
||||||
splitter->setOrientation(Qt::Horizontal);
|
m_splitter->setOrientation(Qt::Horizontal);
|
||||||
splitter->addWidget(m_view);
|
m_splitter->addWidget(m_view);
|
||||||
|
|
||||||
auto *meterPanel = new QWidget(splitter);
|
auto *meterPanel = new QWidget(m_splitter);
|
||||||
meterPanel->setStyleSheet("background-color: #1f2126; border-left: 1px solid #2b2f38;");
|
meterPanel->setStyleSheet("background-color: #1f2126; border-left: 1px solid #2b2f38;");
|
||||||
|
meterPanel->setMinimumWidth(260);
|
||||||
|
meterPanel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
|
||||||
|
|
||||||
auto *meterLayout = new QVBoxLayout(meterPanel);
|
auto *meterLayout = new QVBoxLayout(meterPanel);
|
||||||
meterLayout->setContentsMargins(20, 24, 20, 20);
|
meterLayout->setContentsMargins(20, 24, 20, 20);
|
||||||
|
|
@ -98,14 +102,13 @@ GraphEditorWidget::GraphEditorWidget(Potato::PipeWireController *controller, QWi
|
||||||
|
|
||||||
meterLayout->addStretch();
|
meterLayout->addStretch();
|
||||||
|
|
||||||
meterPanel->setFixedWidth(320);
|
m_splitter->addWidget(meterPanel);
|
||||||
splitter->addWidget(meterPanel);
|
m_splitter->setStretchFactor(0, 1);
|
||||||
splitter->setStretchFactor(0, 1);
|
m_splitter->setStretchFactor(1, 0);
|
||||||
splitter->setStretchFactor(1, 0);
|
|
||||||
|
|
||||||
auto *layout = new QVBoxLayout(this);
|
auto *layout = new QVBoxLayout(this);
|
||||||
layout->setContentsMargins(0, 0, 0, 0);
|
layout->setContentsMargins(0, 0, 0, 0);
|
||||||
layout->addWidget(splitter);
|
layout->addWidget(m_splitter);
|
||||||
setLayout(layout);
|
setLayout(layout);
|
||||||
|
|
||||||
connect(m_model, &PipeWireGraphModel::connectionCreated,
|
connect(m_model, &PipeWireGraphModel::connectionCreated,
|
||||||
|
|
@ -142,6 +145,7 @@ GraphEditorWidget::GraphEditorWidget(Potato::PipeWireController *controller, QWi
|
||||||
connect(autoArrangeAction, &QAction::triggered, [this]() {
|
connect(autoArrangeAction, &QAction::triggered, [this]() {
|
||||||
m_model->autoArrange();
|
m_model->autoArrange();
|
||||||
m_view->zoomFitAll();
|
m_view->zoomFitAll();
|
||||||
|
updateLayoutState();
|
||||||
m_model->saveLayout();
|
m_model->saveLayout();
|
||||||
});
|
});
|
||||||
m_view->addAction(autoArrangeAction);
|
m_view->addAction(autoArrangeAction);
|
||||||
|
|
@ -154,6 +158,7 @@ GraphEditorWidget::GraphEditorWidget(Potato::PipeWireController *controller, QWi
|
||||||
defaultPath,
|
defaultPath,
|
||||||
QString("Layout Files (*.json)"));
|
QString("Layout Files (*.json)"));
|
||||||
if (!filePath.isEmpty()) {
|
if (!filePath.isEmpty()) {
|
||||||
|
updateLayoutState();
|
||||||
m_model->saveLayoutAs(filePath);
|
m_model->saveLayoutAs(filePath);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -163,6 +168,7 @@ GraphEditorWidget::GraphEditorWidget(Potato::PipeWireController *controller, QWi
|
||||||
connect(resetLayoutAction, &QAction::triggered, [this]() {
|
connect(resetLayoutAction, &QAction::triggered, [this]() {
|
||||||
m_model->resetLayout();
|
m_model->resetLayout();
|
||||||
m_view->zoomFitAll();
|
m_view->zoomFitAll();
|
||||||
|
updateLayoutState();
|
||||||
});
|
});
|
||||||
m_view->addAction(resetLayoutAction);
|
m_view->addAction(resetLayoutAction);
|
||||||
|
|
||||||
|
|
@ -182,12 +188,18 @@ GraphEditorWidget::GraphEditorWidget(Potato::PipeWireController *controller, QWi
|
||||||
|
|
||||||
QObject::connect(qApp, &QCoreApplication::aboutToQuit, [this]() {
|
QObject::connect(qApp, &QCoreApplication::aboutToQuit, [this]() {
|
||||||
if (m_model) {
|
if (m_model) {
|
||||||
const QPointF center = m_view->mapToScene(m_view->viewport()->rect().center());
|
updateLayoutState();
|
||||||
m_model->setViewState(m_view->getScale(), center);
|
|
||||||
m_model->saveLayout();
|
m_model->saveLayout();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QList<int> splitterSizes;
|
||||||
|
if (m_model->splitterSizes(splitterSizes)) {
|
||||||
|
m_splitter->setSizes(splitterSizes);
|
||||||
|
} else {
|
||||||
|
m_splitter->setSizes({1200, 320});
|
||||||
|
}
|
||||||
|
|
||||||
m_meterTimer = new QTimer(this);
|
m_meterTimer = new QTimer(this);
|
||||||
m_meterTimer->setInterval(33);
|
m_meterTimer->setInterval(33);
|
||||||
m_meterTimer->setTimerType(Qt::PreciseTimer);
|
m_meterTimer->setTimerType(Qt::PreciseTimer);
|
||||||
|
|
@ -224,8 +236,7 @@ void GraphEditorWidget::syncGraph()
|
||||||
|
|
||||||
void GraphEditorWidget::refreshGraph()
|
void GraphEditorWidget::refreshGraph()
|
||||||
{
|
{
|
||||||
const QPointF center = m_view->mapToScene(m_view->viewport()->rect().center());
|
updateLayoutState();
|
||||||
m_model->setViewState(m_view->getScale(), center);
|
|
||||||
m_ignoreCreate.clear();
|
m_ignoreCreate.clear();
|
||||||
m_ignoreDelete.clear();
|
m_ignoreDelete.clear();
|
||||||
m_connectionToLinkId.clear();
|
m_connectionToLinkId.clear();
|
||||||
|
|
@ -234,6 +245,7 @@ void GraphEditorWidget::refreshGraph()
|
||||||
m_model->reset();
|
m_model->reset();
|
||||||
syncGraph();
|
syncGraph();
|
||||||
m_view->zoomFitAll();
|
m_view->zoomFitAll();
|
||||||
|
updateLayoutState();
|
||||||
m_model->saveLayout();
|
m_model->saveLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -266,6 +278,10 @@ void GraphEditorWidget::onNodeRemoved(uint32_t nodeId)
|
||||||
|
|
||||||
if (m_nodeMeterRows.contains(nodeId)) {
|
if (m_nodeMeterRows.contains(nodeId)) {
|
||||||
QWidget *row = m_nodeMeterRows.take(nodeId);
|
QWidget *row = m_nodeMeterRows.take(nodeId);
|
||||||
|
if (m_nodeMeterLabels.contains(nodeId)) {
|
||||||
|
QLabel *label = m_nodeMeterLabels.take(nodeId);
|
||||||
|
label->removeEventFilter(this);
|
||||||
|
}
|
||||||
m_nodeMeters.remove(nodeId);
|
m_nodeMeters.remove(nodeId);
|
||||||
row->deleteLater();
|
row->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
@ -390,6 +406,19 @@ void GraphEditorWidget::updateMeter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GraphEditorWidget::updateLayoutState()
|
||||||
|
{
|
||||||
|
if (!m_model || !m_view) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QPointF center = m_view->mapToScene(m_view->viewport()->rect().center());
|
||||||
|
m_model->setViewState(m_view->getScale(), center);
|
||||||
|
if (m_splitter) {
|
||||||
|
m_model->setSplitterSizes(m_splitter->sizes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GraphEditorWidget::refreshNodeMeter(uint32_t nodeId, const Potato::NodeInfo &node)
|
void GraphEditorWidget::refreshNodeMeter(uint32_t nodeId, const Potato::NodeInfo &node)
|
||||||
{
|
{
|
||||||
if (m_nodeMeterRows.contains(nodeId)) {
|
if (m_nodeMeterRows.contains(nodeId)) {
|
||||||
|
|
@ -419,10 +448,9 @@ void GraphEditorWidget::refreshNodeMeter(uint32_t nodeId, const Potato::NodeInfo
|
||||||
auto *label = new QLabel(title, row);
|
auto *label = new QLabel(title, row);
|
||||||
label->setStyleSheet("color: #c7cfdd; font-size: 11px; border: none;");
|
label->setStyleSheet("color: #c7cfdd; font-size: 11px; border: none;");
|
||||||
label->setToolTip(title);
|
label->setToolTip(title);
|
||||||
const int labelWidth = 250;
|
label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||||
label->setFixedWidth(labelWidth);
|
label->setProperty("fullTitle", title);
|
||||||
QFontMetrics metrics(label->font());
|
label->installEventFilter(this);
|
||||||
label->setText(metrics.elidedText(title, Qt::ElideRight, labelWidth));
|
|
||||||
|
|
||||||
auto *meter = new AudioLevelMeter(row);
|
auto *meter = new AudioLevelMeter(row);
|
||||||
meter->setMinimumHeight(70);
|
meter->setMinimumHeight(70);
|
||||||
|
|
@ -436,4 +464,33 @@ void GraphEditorWidget::refreshNodeMeter(uint32_t nodeId, const Potato::NodeInfo
|
||||||
|
|
||||||
m_nodeMeters.insert(nodeId, meter);
|
m_nodeMeters.insert(nodeId, meter);
|
||||||
m_nodeMeterRows.insert(nodeId, row);
|
m_nodeMeterRows.insert(nodeId, row);
|
||||||
|
m_nodeMeterLabels.insert(nodeId, label);
|
||||||
|
updateNodeMeterLabel(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphEditorWidget::updateNodeMeterLabel(QLabel *label)
|
||||||
|
{
|
||||||
|
if (!label) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString title = label->property("fullTitle").toString();
|
||||||
|
if (title.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int width = label->width() > 0 ? label->width() : label->sizeHint().width();
|
||||||
|
QFontMetrics metrics(label->font());
|
||||||
|
label->setText(metrics.elidedText(title, Qt::ElideRight, width));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GraphEditorWidget::eventFilter(QObject *object, QEvent *event)
|
||||||
|
{
|
||||||
|
if (auto *label = qobject_cast<QLabel*>(object)) {
|
||||||
|
if (event->type() == QEvent::Resize || event->type() == QEvent::Show) {
|
||||||
|
updateNodeMeterLabel(label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return QWidget::eventFilter(object, event);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,11 @@
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
class AudioLevelMeter;
|
class AudioLevelMeter;
|
||||||
|
class QLabel;
|
||||||
class QTimer;
|
class QTimer;
|
||||||
class QScrollArea;
|
class QScrollArea;
|
||||||
class QVBoxLayout;
|
class QVBoxLayout;
|
||||||
|
class QSplitter;
|
||||||
|
|
||||||
class GraphEditorWidget : public QWidget
|
class GraphEditorWidget : public QWidget
|
||||||
{
|
{
|
||||||
|
|
@ -37,12 +39,16 @@ private slots:
|
||||||
private:
|
private:
|
||||||
void syncGraph();
|
void syncGraph();
|
||||||
void refreshGraph();
|
void refreshGraph();
|
||||||
|
void updateLayoutState();
|
||||||
|
void updateNodeMeterLabel(QLabel *label);
|
||||||
QString connectionKey(const QtNodes::ConnectionId &connectionId) const;
|
QString connectionKey(const QtNodes::ConnectionId &connectionId) const;
|
||||||
|
bool eventFilter(QObject *object, QEvent *event) override;
|
||||||
|
|
||||||
Potato::PipeWireController *m_controller = nullptr;
|
Potato::PipeWireController *m_controller = nullptr;
|
||||||
PipeWireGraphModel *m_model = nullptr;
|
PipeWireGraphModel *m_model = nullptr;
|
||||||
QtNodes::BasicGraphicsScene *m_scene = nullptr;
|
QtNodes::BasicGraphicsScene *m_scene = nullptr;
|
||||||
QtNodes::GraphicsView *m_view = nullptr;
|
QtNodes::GraphicsView *m_view = nullptr;
|
||||||
|
QSplitter *m_splitter = nullptr;
|
||||||
|
|
||||||
QSet<QString> m_ignoreCreate;
|
QSet<QString> m_ignoreCreate;
|
||||||
QSet<QString> m_ignoreDelete;
|
QSet<QString> m_ignoreDelete;
|
||||||
|
|
@ -55,4 +61,5 @@ private:
|
||||||
QVBoxLayout *m_meterListLayout = nullptr;
|
QVBoxLayout *m_meterListLayout = nullptr;
|
||||||
QMap<uint32_t, AudioLevelMeter*> m_nodeMeters;
|
QMap<uint32_t, AudioLevelMeter*> m_nodeMeters;
|
||||||
QMap<uint32_t, QWidget*> m_nodeMeterRows;
|
QMap<uint32_t, QWidget*> m_nodeMeterRows;
|
||||||
|
QMap<uint32_t, QLabel*> m_nodeMeterLabels;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -549,6 +549,9 @@ void PipeWireGraphModel::autoArrange()
|
||||||
void PipeWireGraphModel::loadLayout()
|
void PipeWireGraphModel::loadLayout()
|
||||||
{
|
{
|
||||||
m_layoutByStableId.clear();
|
m_layoutByStableId.clear();
|
||||||
|
m_hasViewState = false;
|
||||||
|
m_hasSplitterSizes = false;
|
||||||
|
m_splitterSizes.clear();
|
||||||
const QString path = layoutFilePath();
|
const QString path = layoutFilePath();
|
||||||
if (path.isEmpty()) {
|
if (path.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -578,6 +581,19 @@ void PipeWireGraphModel::loadLayout()
|
||||||
m_viewCenter = QPointF(x, y);
|
m_viewCenter = QPointF(x, y);
|
||||||
m_hasViewState = true;
|
m_hasViewState = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QJsonArray splitter = root.value("splitter").toArray();
|
||||||
|
if (!splitter.isEmpty()) {
|
||||||
|
QList<int> sizes;
|
||||||
|
sizes.reserve(splitter.size());
|
||||||
|
for (const auto &value : splitter) {
|
||||||
|
sizes.append(value.toInt());
|
||||||
|
}
|
||||||
|
if (!sizes.isEmpty()) {
|
||||||
|
m_splitterSizes = sizes;
|
||||||
|
m_hasSplitterSizes = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -786,6 +802,13 @@ void PipeWireGraphModel::writeLayoutToFile(const QString &path) const
|
||||||
view["center_x"] = m_viewCenter.x();
|
view["center_x"] = m_viewCenter.x();
|
||||||
view["center_y"] = m_viewCenter.y();
|
view["center_y"] = m_viewCenter.y();
|
||||||
root["view"] = view;
|
root["view"] = view;
|
||||||
|
if (m_hasSplitterSizes && !m_splitterSizes.isEmpty()) {
|
||||||
|
QJsonArray splitter;
|
||||||
|
for (const auto size : m_splitterSizes) {
|
||||||
|
splitter.append(size);
|
||||||
|
}
|
||||||
|
root["splitter"] = splitter;
|
||||||
|
}
|
||||||
|
|
||||||
QFile file(path);
|
QFile file(path);
|
||||||
QDir().mkpath(QFileInfo(path).absolutePath());
|
QDir().mkpath(QFileInfo(path).absolutePath());
|
||||||
|
|
@ -826,4 +849,20 @@ bool PipeWireGraphModel::viewState(double &scale, QPointF ¢er) const
|
||||||
center = m_viewCenter;
|
center = m_viewCenter;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PipeWireGraphModel::setSplitterSizes(const QList<int> &sizes)
|
||||||
|
{
|
||||||
|
m_splitterSizes = sizes;
|
||||||
|
m_hasSplitterSizes = !sizes.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PipeWireGraphModel::splitterSizes(QList<int> &sizes) const
|
||||||
|
{
|
||||||
|
if (!m_hasSplitterSizes) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sizes = m_splitterSizes;
|
||||||
|
return !sizes.isEmpty();
|
||||||
|
}
|
||||||
#include <QtNodes/StyleCollection>
|
#include <QtNodes/StyleCollection>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
|
#include <QList>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
@ -67,6 +68,8 @@ public:
|
||||||
QString defaultLayoutPath() const;
|
QString defaultLayoutPath() const;
|
||||||
void setViewState(double scale, const QPointF ¢er);
|
void setViewState(double scale, const QPointF ¢er);
|
||||||
bool viewState(double &scale, QPointF ¢er) const;
|
bool viewState(double &scale, QPointF ¢er) const;
|
||||||
|
void setSplitterSizes(const QList<int> &sizes);
|
||||||
|
bool splitterSizes(QList<int> &sizes) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QtNodes::ConnectionId connectionFromPipeWire(const Potato::LinkInfo &link, bool *ok) const;
|
QtNodes::ConnectionId connectionFromPipeWire(const Potato::LinkInfo &link, bool *ok) const;
|
||||||
|
|
@ -90,4 +93,6 @@ private:
|
||||||
QPointF m_viewCenter = QPointF(0, 0);
|
QPointF m_viewCenter = QPointF(0, 0);
|
||||||
double m_viewScale = 1.0;
|
double m_viewScale = 1.0;
|
||||||
bool m_hasViewState = false;
|
bool m_hasViewState = false;
|
||||||
|
QList<int> m_splitterSizes;
|
||||||
|
bool m_hasSplitterSizes = false;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue