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 <QSplitter>
|
||||
#include <QTimer>
|
||||
#include <QEvent>
|
||||
#include <QVBoxLayout>
|
||||
#include <QScrollArea>
|
||||
#include <QSizePolicy>
|
||||
|
||||
#include <QtNodes/GraphicsViewStyle>
|
||||
#include <QtNodes/NodeStyle>
|
||||
|
|
@ -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<int> 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<QLabel*>(object)) {
|
||||
if (event->type() == QEvent::Resize || event->type() == QEvent::Show) {
|
||||
updateNodeMeterLabel(label);
|
||||
}
|
||||
}
|
||||
|
||||
return QWidget::eventFilter(object, event);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,9 +12,11 @@
|
|||
#include <cstdint>
|
||||
|
||||
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<QString> m_ignoreCreate;
|
||||
QSet<QString> m_ignoreDelete;
|
||||
|
|
@ -55,4 +61,5 @@ private:
|
|||
QVBoxLayout *m_meterListLayout = nullptr;
|
||||
QMap<uint32_t, AudioLevelMeter*> m_nodeMeters;
|
||||
QMap<uint32_t, QWidget*> m_nodeMeterRows;
|
||||
QMap<uint32_t, QLabel*> m_nodeMeterLabels;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<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_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<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>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QList>
|
||||
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
|
|
@ -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<int> &sizes);
|
||||
bool splitterSizes(QList<int> &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<int> m_splitterSizes;
|
||||
bool m_hasSplitterSizes = false;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue