From e326eabbf88fda9d31dc4b456944236856a7bc13 Mon Sep 17 00:00:00 2001 From: Joey Yakimowich-Payne Date: Tue, 27 Jan 2026 17:53:50 -0700 Subject: [PATCH] Expandable sidebar --- src/gui/GraphEditorWidget.cpp | 91 +++++++++++++++++++++++++++------- src/gui/GraphEditorWidget.h | 7 +++ src/gui/PipeWireGraphModel.cpp | 39 +++++++++++++++ src/gui/PipeWireGraphModel.h | 5 ++ 4 files changed, 125 insertions(+), 17 deletions(-) diff --git a/src/gui/GraphEditorWidget.cpp b/src/gui/GraphEditorWidget.cpp index ea0e9a2..fec1f70 100644 --- a/src/gui/GraphEditorWidget.cpp +++ b/src/gui/GraphEditorWidget.cpp @@ -10,8 +10,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -59,12 +61,14 @@ GraphEditorWidget::GraphEditorWidget(Potato::PipeWireController *controller, QWi m_view = new QtNodes::GraphicsView(m_scene); m_scene->setBackgroundBrush(QColor(28, 30, 34)); - auto *splitter = new QSplitter(this); - splitter->setOrientation(Qt::Horizontal); - splitter->addWidget(m_view); + m_splitter = new QSplitter(this); + m_splitter->setOrientation(Qt::Horizontal); + 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->setMinimumWidth(260); + meterPanel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); auto *meterLayout = new QVBoxLayout(meterPanel); meterLayout->setContentsMargins(20, 24, 20, 20); @@ -98,14 +102,13 @@ GraphEditorWidget::GraphEditorWidget(Potato::PipeWireController *controller, QWi meterLayout->addStretch(); - meterPanel->setFixedWidth(320); - splitter->addWidget(meterPanel); - splitter->setStretchFactor(0, 1); - splitter->setStretchFactor(1, 0); + m_splitter->addWidget(meterPanel); + m_splitter->setStretchFactor(0, 1); + m_splitter->setStretchFactor(1, 0); auto *layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); - layout->addWidget(splitter); + layout->addWidget(m_splitter); setLayout(layout); connect(m_model, &PipeWireGraphModel::connectionCreated, @@ -142,6 +145,7 @@ GraphEditorWidget::GraphEditorWidget(Potato::PipeWireController *controller, QWi connect(autoArrangeAction, &QAction::triggered, [this]() { m_model->autoArrange(); m_view->zoomFitAll(); + updateLayoutState(); m_model->saveLayout(); }); m_view->addAction(autoArrangeAction); @@ -154,6 +158,7 @@ GraphEditorWidget::GraphEditorWidget(Potato::PipeWireController *controller, QWi defaultPath, QString("Layout Files (*.json)")); if (!filePath.isEmpty()) { + updateLayoutState(); m_model->saveLayoutAs(filePath); } }); @@ -163,6 +168,7 @@ GraphEditorWidget::GraphEditorWidget(Potato::PipeWireController *controller, QWi connect(resetLayoutAction, &QAction::triggered, [this]() { m_model->resetLayout(); m_view->zoomFitAll(); + updateLayoutState(); }); m_view->addAction(resetLayoutAction); @@ -182,12 +188,18 @@ GraphEditorWidget::GraphEditorWidget(Potato::PipeWireController *controller, QWi QObject::connect(qApp, &QCoreApplication::aboutToQuit, [this]() { if (m_model) { - const QPointF center = m_view->mapToScene(m_view->viewport()->rect().center()); - m_model->setViewState(m_view->getScale(), center); + updateLayoutState(); m_model->saveLayout(); } }); + QList splitterSizes; + if (m_model->splitterSizes(splitterSizes)) { + m_splitter->setSizes(splitterSizes); + } else { + m_splitter->setSizes({1200, 320}); + } + m_meterTimer = new QTimer(this); m_meterTimer->setInterval(33); m_meterTimer->setTimerType(Qt::PreciseTimer); @@ -224,8 +236,7 @@ void GraphEditorWidget::syncGraph() void GraphEditorWidget::refreshGraph() { - const QPointF center = m_view->mapToScene(m_view->viewport()->rect().center()); - m_model->setViewState(m_view->getScale(), center); + updateLayoutState(); m_ignoreCreate.clear(); m_ignoreDelete.clear(); m_connectionToLinkId.clear(); @@ -234,6 +245,7 @@ void GraphEditorWidget::refreshGraph() m_model->reset(); syncGraph(); m_view->zoomFitAll(); + updateLayoutState(); m_model->saveLayout(); } @@ -266,6 +278,10 @@ void GraphEditorWidget::onNodeRemoved(uint32_t nodeId) if (m_nodeMeterRows.contains(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); 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) { if (m_nodeMeterRows.contains(nodeId)) { @@ -419,10 +448,9 @@ void GraphEditorWidget::refreshNodeMeter(uint32_t nodeId, const Potato::NodeInfo auto *label = new QLabel(title, row); label->setStyleSheet("color: #c7cfdd; font-size: 11px; border: none;"); label->setToolTip(title); - const int labelWidth = 250; - label->setFixedWidth(labelWidth); - QFontMetrics metrics(label->font()); - label->setText(metrics.elidedText(title, Qt::ElideRight, labelWidth)); + label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + label->setProperty("fullTitle", title); + label->installEventFilter(this); auto *meter = new AudioLevelMeter(row); meter->setMinimumHeight(70); @@ -436,4 +464,33 @@ void GraphEditorWidget::refreshNodeMeter(uint32_t nodeId, const Potato::NodeInfo m_nodeMeters.insert(nodeId, meter); 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(object)) { + if (event->type() == QEvent::Resize || event->type() == QEvent::Show) { + updateNodeMeterLabel(label); + } + } + + return QWidget::eventFilter(object, event); } diff --git a/src/gui/GraphEditorWidget.h b/src/gui/GraphEditorWidget.h index 7614575..cead265 100644 --- a/src/gui/GraphEditorWidget.h +++ b/src/gui/GraphEditorWidget.h @@ -12,9 +12,11 @@ #include class AudioLevelMeter; +class QLabel; class QTimer; class QScrollArea; class QVBoxLayout; +class QSplitter; class GraphEditorWidget : public QWidget { @@ -37,12 +39,16 @@ private slots: private: void syncGraph(); void refreshGraph(); + void updateLayoutState(); + void updateNodeMeterLabel(QLabel *label); QString connectionKey(const QtNodes::ConnectionId &connectionId) const; + bool eventFilter(QObject *object, QEvent *event) override; Potato::PipeWireController *m_controller = nullptr; PipeWireGraphModel *m_model = nullptr; QtNodes::BasicGraphicsScene *m_scene = nullptr; QtNodes::GraphicsView *m_view = nullptr; + QSplitter *m_splitter = nullptr; QSet m_ignoreCreate; QSet m_ignoreDelete; @@ -55,4 +61,5 @@ private: QVBoxLayout *m_meterListLayout = nullptr; QMap m_nodeMeters; QMap m_nodeMeterRows; + QMap m_nodeMeterLabels; }; diff --git a/src/gui/PipeWireGraphModel.cpp b/src/gui/PipeWireGraphModel.cpp index dac17ae..06c1455 100644 --- a/src/gui/PipeWireGraphModel.cpp +++ b/src/gui/PipeWireGraphModel.cpp @@ -549,6 +549,9 @@ void PipeWireGraphModel::autoArrange() void PipeWireGraphModel::loadLayout() { m_layoutByStableId.clear(); + m_hasViewState = false; + m_hasSplitterSizes = false; + m_splitterSizes.clear(); const QString path = layoutFilePath(); if (path.isEmpty()) { return; @@ -578,6 +581,19 @@ void PipeWireGraphModel::loadLayout() m_viewCenter = QPointF(x, y); m_hasViewState = true; } + + const QJsonArray splitter = root.value("splitter").toArray(); + if (!splitter.isEmpty()) { + QList 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_y"] = m_viewCenter.y(); 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); QDir().mkpath(QFileInfo(path).absolutePath()); @@ -826,4 +849,20 @@ bool PipeWireGraphModel::viewState(double &scale, QPointF ¢er) const center = m_viewCenter; return true; } + +void PipeWireGraphModel::setSplitterSizes(const QList &sizes) +{ + m_splitterSizes = sizes; + m_hasSplitterSizes = !sizes.isEmpty(); +} + +bool PipeWireGraphModel::splitterSizes(QList &sizes) const +{ + if (!m_hasSplitterSizes) { + return false; + } + + sizes = m_splitterSizes; + return !sizes.isEmpty(); +} #include diff --git a/src/gui/PipeWireGraphModel.h b/src/gui/PipeWireGraphModel.h index 01d3e02..891df31 100644 --- a/src/gui/PipeWireGraphModel.h +++ b/src/gui/PipeWireGraphModel.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -67,6 +68,8 @@ public: QString defaultLayoutPath() const; void setViewState(double scale, const QPointF ¢er); bool viewState(double &scale, QPointF ¢er) const; + void setSplitterSizes(const QList &sizes); + bool splitterSizes(QList &sizes) const; private: QtNodes::ConnectionId connectionFromPipeWire(const Potato::LinkInfo &link, bool *ok) const; @@ -90,4 +93,6 @@ private: QPointF m_viewCenter = QPointF(0, 0); double m_viewScale = 1.0; bool m_hasViewState = false; + QList m_splitterSizes; + bool m_hasSplitterSizes = false; };