scroll the controls panel's rows instead of clipping them
This commit is contained in:
@@ -5,6 +5,8 @@
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QMouseEvent>
|
||||
#include <QScrollArea>
|
||||
#include <QScrollBar>
|
||||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
@@ -15,10 +17,15 @@
|
||||
namespace
|
||||
{
|
||||
|
||||
const int kMarginPx = 8; // between the band's edge and the panel
|
||||
const int kMarginPx = 8; // between the view's edge and the panel
|
||||
const int kCardMarginPx = 8; // inside the panel, around its content
|
||||
const int kHeadingGapPx = 4; // between the heading and the rows
|
||||
const int kRefreshMs = 50; // see the class comment on why this polls
|
||||
|
||||
// The panel's border, from the stylesheet below. Spelled out because the stylesheet box
|
||||
// is what sets it and asking the style for it before the first show is unreliable.
|
||||
const int kBorderPx = 1;
|
||||
|
||||
// Separates the heading's name from its detail, e.g. "BUILD MODE * Assembler".
|
||||
const QChar kHeadingSeparator(0x00B7); // U+00B7 MIDDLE DOT
|
||||
|
||||
@@ -117,7 +124,7 @@ ControlsPanel::ControlsPanel(const GameWorldView* view, QWidget* parent)
|
||||
QVBoxLayout* outerLayout = new QVBoxLayout(this);
|
||||
outerLayout->setContentsMargins(kCardMarginPx, kCardMarginPx,
|
||||
kCardMarginPx, kCardMarginPx);
|
||||
outerLayout->setSpacing(4);
|
||||
outerLayout->setSpacing(kHeadingGapPx);
|
||||
|
||||
m_heading = new QLabel(this);
|
||||
m_heading->setObjectName(QStringLiteral("controlHeading"));
|
||||
@@ -125,11 +132,24 @@ ControlsPanel::ControlsPanel(const GameWorldView* view, QWidget* parent)
|
||||
m_heading->setFont(makeSpacedFont(font(), /*bold*/ true, /*pointSizeDelta*/ 0));
|
||||
outerLayout->addWidget(m_heading);
|
||||
|
||||
// Rows taller than the space available scroll rather than being cut off
|
||||
// (REQ-UI-CONTROLS-PANEL). The viewport is transparent so the panel's own rounded
|
||||
// chrome shows through, and horizontal scrolling is off because the width always
|
||||
// follows the content.
|
||||
m_rows = new QWidget(this);
|
||||
m_rowsLayout = new QVBoxLayout(m_rows);
|
||||
m_rowsLayout->setContentsMargins(0, 0, 0, 0);
|
||||
m_rowsLayout->setSpacing(0);
|
||||
outerLayout->addWidget(m_rows);
|
||||
m_rows->setAutoFillBackground(false);
|
||||
|
||||
m_scrollArea = new QScrollArea(this);
|
||||
m_scrollArea->setFrameShape(QFrame::NoFrame);
|
||||
m_scrollArea->setWidgetResizable(true);
|
||||
m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
m_scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
m_scrollArea->viewport()->setAutoFillBackground(false);
|
||||
m_scrollArea->setWidget(m_rows);
|
||||
outerLayout->addWidget(m_scrollArea);
|
||||
|
||||
// Polling rather than subscribing; see the class comment.
|
||||
m_refreshTimer = new QTimer(this);
|
||||
@@ -153,7 +173,7 @@ void ControlsPanel::mousePressEvent(QMouseEvent* event)
|
||||
if (m_heading->geometry().contains(event->pos()))
|
||||
{
|
||||
m_collapsed = !m_collapsed;
|
||||
m_rows->setVisible(!m_collapsed);
|
||||
m_scrollArea->setVisible(!m_collapsed);
|
||||
refit();
|
||||
}
|
||||
event->accept();
|
||||
@@ -220,7 +240,7 @@ void ControlsPanel::rebuild(const ControlContext& context)
|
||||
addAndShow(m_rowsLayout, makeRow(action, context, m_rows));
|
||||
}
|
||||
|
||||
m_rows->setVisible(!m_collapsed);
|
||||
m_scrollArea->setVisible(!m_collapsed);
|
||||
refit();
|
||||
}
|
||||
|
||||
@@ -270,21 +290,32 @@ void ControlsPanel::refit()
|
||||
m_rowsLayout->invalidate();
|
||||
m_rowsLayout->activate();
|
||||
|
||||
// Deliberately not activating the panel's own layout here. That lays the heading
|
||||
// and the rows out inside the geometry the panel still has from the previous
|
||||
// context, which is the wrong frame of reference for choosing the new one.
|
||||
// totalSizeHint answers "how big does this need to be" without reference to how big
|
||||
// it currently is; setGeometry below then re-runs the outer layout on its own.
|
||||
layout()->invalidate();
|
||||
const QSize needed = layout()->totalSizeHint();
|
||||
|
||||
const QRect band = m_worldRect.adjusted(kMarginPx, kMarginPx, -kMarginPx, -kMarginPx);
|
||||
if (band.width() <= 0 || band.height() <= 0) { return; }
|
||||
|
||||
// Measured from the heading and the rows directly rather than from the panel's own
|
||||
// layout: the rows now sit in a scroll area, whose size hint describes a viewport
|
||||
// and says nothing about how tall its contents are. Deliberately not activating the
|
||||
// panel's own layout either -- that lays its children out inside the geometry left
|
||||
// over from the previous context, which is the wrong frame of reference for
|
||||
// choosing the new one. setGeometry below re-runs it.
|
||||
const int chromePx = 2 * (kCardMarginPx + kBorderPx);
|
||||
const QSize headingHint = m_heading->sizeHint();
|
||||
const QSize rowsHint = m_rows->sizeHint();
|
||||
|
||||
int contentWidthPx = headingHint.width();
|
||||
int contentHeightPx = headingHint.height();
|
||||
if (!m_collapsed)
|
||||
{
|
||||
contentWidthPx = qMax(contentWidthPx, rowsHint.width());
|
||||
contentHeightPx += kHeadingGapPx + rowsHint.height();
|
||||
}
|
||||
const int wantedHeightPx = contentHeightPx + chromePx;
|
||||
|
||||
// The bottom-left corner of the view, growing upward as rows are added
|
||||
// (REQ-UI-CONTROLS-PANEL).
|
||||
const int widthPx = qMin(needed.width(), band.width());
|
||||
int heightPx = qMin(needed.height(), band.height());
|
||||
int widthPx = qMin(contentWidthPx + chromePx, band.width());
|
||||
int heightPx = qMin(wantedHeightPx, band.height());
|
||||
int topPx = band.bottom() - heightPx + 1;
|
||||
|
||||
// The build button bar is centered and sized to its buttons, so it usually leaves
|
||||
@@ -299,5 +330,13 @@ void ControlsPanel::refit()
|
||||
topPx = m_buildBarRect.top() - kMarginPx - heightPx;
|
||||
}
|
||||
|
||||
// Whatever the rows lost to either cap, they scroll for. The scrollbar needs its
|
||||
// own width, or it would appear over the labels.
|
||||
if (heightPx < wantedHeightPx)
|
||||
{
|
||||
widthPx = qMin(widthPx + m_scrollArea->verticalScrollBar()->sizeHint().width(),
|
||||
band.width());
|
||||
}
|
||||
|
||||
setGeometry(band.left(), topPx, widthPx, heightPx);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
class GameWorldView;
|
||||
class QLabel;
|
||||
class QScrollArea;
|
||||
class QTimer;
|
||||
class QVBoxLayout;
|
||||
|
||||
@@ -64,6 +65,9 @@ private:
|
||||
const GameWorldView* m_view;
|
||||
|
||||
QLabel* m_heading;
|
||||
// Scrolls the rows once they outgrow the space the panel has (REQ-UI-CONTROLS-PANEL).
|
||||
// The heading is deliberately outside it, so it stays put and stays clickable.
|
||||
QScrollArea* m_scrollArea;
|
||||
QWidget* m_rows;
|
||||
QVBoxLayout* m_rowsLayout;
|
||||
QTimer* m_refreshTimer;
|
||||
|
||||
Reference in New Issue
Block a user