Mircea
Mircea

Reputation: 1999

QScrollArea - Resize content widgets by keeping the aspect ratio

I have a layout that looks like this.

Where:

Blue: rectangle it's a ScrollArea

Orange: rectangles are the widgets from that ScrollArea

enter image description here


My code:

#include <QtWidgets>
///////////////////////////////////////////////////////////////////////////////////////

class RoundedPolygon : public QPolygon {
public:
    RoundedPolygon() { SetRadius(10); }

    void SetRadius(unsigned int iRadius) { m_iRadius = iRadius; }

    const QPainterPath &GetPath() {
        m_path = QPainterPath();

        if (count() < 3) {
            qDebug() << "!! Polygon should have at least 3 points !!";
            return m_path;
        }

        QPointF pt1;
        QPointF pt2;
        for (int i = 0; i < count(); i++) {
            pt1 = GetLineStart(i);

            if (i == 0)
                m_path.moveTo(pt1);
            else
                m_path.quadTo(at(i), pt1);

            pt2 = GetLineEnd(i);
            m_path.lineTo(pt2);
        }

        // close the last corner
        pt1 = GetLineStart(0);
        m_path.quadTo(at(0), pt1);

        return m_path;
    }

private:
    QPointF GetLineStart(int i) const {
        QPointF pt;
        QPoint pt1 = at(i);
        QPoint pt2 = at((i + 1) % count());
        float fRat = m_iRadius / GetDistance(pt1, pt2);
        if (fRat > 0.5f)
            fRat = 0.5f;

        pt.setX((1.0f - fRat) * pt1.x() + fRat * pt2.x());
        pt.setY((1.0f - fRat) * pt1.y() + fRat * pt2.y());
        return pt;
    }

    QPointF GetLineEnd(int i) const {
        QPointF pt;
        QPoint pt1 = at(i);
        QPoint pt2 = at((i + 1) % count());
        float fRat = m_iRadius / GetDistance(pt1, pt2);
        if (fRat > 0.5f)
            fRat = 0.5f;
        pt.setX(fRat * pt1.x() + (1.0f - fRat) * pt2.x());
        pt.setY(fRat * pt1.y() + (1.0f - fRat) * pt2.y());
        return pt;
    }

    float GetDistance(QPoint pt1, QPoint pt2) const {
        int fD = (pt1.x() - pt2.x()) * (pt1.x() - pt2.x()) + (pt1.y() - pt2.y()) * (pt1.y() - pt2.y());
        return sqrtf(fD);
    }

private:
    QPainterPath m_path;
    unsigned int m_iRadius{};
};

class PolygonButtonWidget : public QWidget {
Q_OBJECT
public:
    explicit PolygonButtonWidget(QWidget *parent = nullptr) : QWidget(parent) {}

    ~PolygonButtonWidget() override = default;

protected:
    void resizeEvent(QResizeEvent *event) override {
        float ratioW = 8;
        float ratioH = 3;
//        ui->scrollAreaWidgetContents->setFixedSize(5000, h);
        float thisAspectRatio = (float) event->size().width() / event->size().height();
        if (thisAspectRatio < ratioW / ratioH) {
            float w = event->size().height() * ratioW / ratioH;
            float h = event->size().height();
            qDebug() << hasHeightForWidth() << " " << w << " " << h;
            this->resize(w, h);
            if (m_nrButtons != 0) {
                this->move((w + 20) * m_nrButtons, this->y());
            }
        }

        QWidget::resizeEvent(event);
    }

    int m_nrButtons{};
public:
    void setMNrButtons(int mNrButtons) {
        m_nrButtons = mNrButtons;
    }

protected:
    void paintEvent(QPaintEvent *event) override {
        int offset = 50;

        m_polygon.clear();
        m_polygon.emplace_back(0, height()); //DOWN-LEFT
        m_polygon.emplace_back(width() - offset, height()); //DOWN-RIGHT
        m_polygon.emplace_back(width(), 0); //TOP-RIGHT
        m_polygon.emplace_back(0 + offset, 0);


        QPainter painter(this);
        painter.setRenderHint(QPainter::Antialiasing);
        RoundedPolygon poly;
        poly.SetRadius(15);
        for (QPoint point: m_polygon) {
            poly << point;
        }

        QBrush fillBrush;
        fillBrush.setColor(Qt::darkBlue);
        fillBrush.setStyle(Qt::SolidPattern);

        QPainterPath path;
        path.addPath(poly.GetPath());

        painter.fillPath(path, fillBrush);
    }

    void mousePressEvent(QMouseEvent *event) override {
        auto cursorPos = mapFromGlobal(QCursor::pos());
        qDebug() << "X: " << cursorPos.x() << " Y: " << cursorPos.y();

        inside(cursorPos, m_polygon);
        qDebug() << "Pressed";
    }

private:
    std::vector<QPoint> m_polygon;

    bool inside(QPoint point, std::vector<QPoint> polygon) {
        auto x = point.x();
        auto y = point.y();

        auto inside = false;
        auto i = 0;
        auto j = polygon.size() - 1;
        while (i < polygon.size()) {
            auto xi = polygon[i].x();
            auto yi = polygon[i].y();
            auto xj = polygon[j].x();
            auto yj = polygon[j].y();

            auto intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
            if (intersect) inside = !inside;

            j = i++;
        }
        qDebug() << inside;

        return inside;
    }
};

///////////////////////////////////////////////////////////////////////////////////////

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    QWidget root;
    QHBoxLayout layout{&root};

    for (int i = 0; i < 10; ++i) {
        auto p = new PolygonButtonWidget();
        p->setMinimumSize(100, 100);
        p->setMNrButtons(i);
        layout.addWidget(p);
    }
    root.setStyleSheet("background-color: rgb(19,19,19);");
    QScrollArea view;
    view.setWidget(&root);
    view.show();
    app.exec();
}

#include "main.moc"

The problem arises when I'm trying to resize the window. In the moment of resizing, I want my widgets to keep their aspect ratio. But that's not going to happen.

I have scroll list of widgets which is looking like this (if it's expended on X way too much) enter image description here

If I will scale it on Y-axis it's going to look like this.

enter image description here

After I've changed the resizeEvent now it's going to look something like this

enter image description here

or like this

enter image description here

How can I fix this? For some reason, some of my widgets are going to disappear, what should be my approach in order to fix this issue?

Upvotes: 1

Views: 539

Answers (1)

The problem is caused by the assumption that there's any mechanism that will automatically resize the widgets for you. There isn't. A QScrollArea acts as a layout barrier and any layouts inside of it are isolated from its size, and thus from any resize events.

You must resize the container widget (the one with blue outline on your diagram) yourself anytime the scroll area changes size, and you need first to prepare a test case for the widgets such that their size changes are properly managed when placed in the layout of your choice, and said layout is resized.

Finally, the pet peeve of mine: It's unlikely that you actually need the QMainWindow for anything. It's just a silly Qt Creator template. But unless you want an MDI interface and docking, you shouldn't be using the QMainWindow - and especially not when making a self-contained example. All you need here is QScrollArea as a top-level widget. That's literally all. Any QWidget can be a top-level window!

For future submissions, please provide all the code needed in a single main.cpp file that begins with #include <QtWidgets> and ends with #include "main.moc". You won't need any other includes for Qt classes, and you can write class definitions Java-style, with all the methods defined within the class declaration itself. This provides for short code - after all, a SO question isn't an Enterprise project. It's supposed to be minimal, and that really means that anything not necessary must be removed. No need for header files, multiple includes, nor other fluff - i.e. use Qt containers instead of C++ STL so that you don't need more includes etc.

Your example should look roughly as follows:

#include <QtWidgets>

class PolygonButtonWidget : public QAbstractButton {
  Q_OBJECT
  /* without seeing the code here, your question is unanswerable */
};

int main(int argc, char* argv[]) {
  QApplication app(argc, argv);
  QWidget root;
  QHBoxLayout layout{&root};
  PolygonButtonWidget buttons[10];
  for (auto &button : buttons)
    layout.addWidget(&button);
  QScrollArea view;
  view.setWidget(&root);
  view.show();
  app.exec();
  view.takeWidget();
}

#include "main.moc"

Without such an example, your question is hard to answer, since:

  1. How can we debug it? Debugging means using a debugger. If your code cannot be immediately compiled, then it's quite unlikely that someone will bother debugging it, and debugging by inspection is often error-prone.

  2. How can we provide a tested answer if we'd have to first write the entire "test case" for it?

  3. How can we know what's inside your button widget? The behavior of that widget does affect the ultimate solution.

It'd also help if you described a few use cases that you'd expect to work. That is, mock up (with a drawing) the state of the widgets before and after the view is resized, so that we can easily see what it is that you expect to happen. A lot of it is very easy to miss when explaining your needs in words. Use cases are a lingua franca of software specifications. If you don't use them, it's highly likely that you yourself don't know what behavior you expect in all cases.

Upvotes: 1

Related Questions