Skip to content

Instantly share code, notes, and snippets.

@pavel-perina
Last active June 15, 2023 12:33
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pavel-perina/1324ff064aedede0e01311aab315f83d to your computer and use it in GitHub Desktop.
Save pavel-perina/1324ff064aedede0e01311aab315f83d to your computer and use it in GitHub Desktop.
QLayout that keeps fixed aspect ratio of it's only widget.
/*
Copyright (c) 2017 Pavel Perina
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/// \file fixed_aspect_ratio_qlayout.h
/// \brief Header only implementation of AspectRatioSingleItemLayout
#ifndef ASPECTRATIO_SINGLEITEM_LAYOUT_H
#define ASPECTRATIO_SINGLEITEM_LAYOUT_H
#include <QtWidgets/QLayout>
/// \brief Layout that keeps single item and maintains given aspect ratio
class AspectRatioSingleItemLayout : public QLayout {
public:
/// \brief Constructor
AspectRatioSingleItemLayout(QWidget *parent = nullptr, double aspectRatio = 16.0/9.0)
: QLayout(parent)
, m_aspectRatio(aspectRatio)
, m_item(nullptr)
{
}
/// \brief Destructor
/// \warning Let's hope we're responsible for deleting item
~AspectRatioSingleItemLayout()
{
delete m_item;
}
/// \brief Set aspect ratio to keep
/// \todo Invalidate geometry
void setAspectRatio(double aspectRatio)
{
m_aspectRatio = aspectRatio;
}
/// \brief Return number of items
int count() const override
{
return m_item != nullptr ? 1 : 0;
}
/// \brief Return item at given index, nullptr if index is out of range
QLayoutItem *itemAt(int i) const override
{
return i == 0 ? m_item : nullptr;
}
/// \brief Take item at index. Now caller is responsible for deletion, we no longer own it
QLayoutItem *takeAt(int) override
{
QLayoutItem *retval = m_item;
m_item = nullptr;
return retval;
}
/// \brief Returns directions where we want to expand beyond sizeHint()
Qt::Orientations expandingDirections() const override
{
// we'd like grow beyond sizeHint()
return Qt::Horizontal | Qt::Vertical;
}
/// \brief We want to limit height based on width
bool hasHeightForWidth() const override
{
return false;
}
/// \brief We want that much height for given \a width
int heightForWidth(int width) const override
{
int height = (width - 2 * margin()) / m_aspectRatio + 2 * margin();
return height;
}
/// \brief Set geometry of our only widget
void setGeometry(const QRect &rect) override
{
QLayout::setGeometry(rect);
if (m_item) {
QWidget *wdg = m_item->widget();
int availW = rect.width() - 2 * margin();
int availH = rect.height() - 2 * margin();
int w, h;
h = availH;
w = h * m_aspectRatio;
if (w > availW) {
// fill width
w = availW;
h = w / m_aspectRatio;
int y;
if (m_item->alignment() & Qt::AlignTop)
y = margin();
else if (m_item->alignment() & Qt::AlignBottom)
y = rect.height() - margin() - h;
else
y = margin() + (availH - h) / 2;
wdg->setGeometry(rect.x() + margin(), rect.y() + y, w, h);
}
else {
int x;
if (m_item->alignment() & Qt::AlignLeft)
x = margin();
else if (m_item->alignment() & Qt::AlignRight)
x = rect.width() - margin() - w;
else
x = margin() + (availW - w) / 2;
wdg->setGeometry(rect.x() + x, rect.y() + margin(), w, h);
}
}
}
QSize sizeHint() const override
{
const int margins = 2 * margin();
return m_item ? m_item->sizeHint() + QSize(margins, margins) : QSize(margins, margins);
}
QSize minimumSize() const override
{
const int margins = 2 * margin();
return m_item ? m_item->minimumSize() + QSize(margins, margins) : QSize(margins,margins);
}
/// \brief Add item, this removes existing from container
void addItem(QLayoutItem *item) override
{
delete m_item;
m_item = item;
item->setAlignment(0);
}
private:
QLayoutItem *m_item; // at most one :-)
double m_aspectRatio;
};
#endif // ASPECTRATIO_SINGLEITEM_LAYOUT_H
@pavel-perina
Copy link
Author

pavel-perina commented May 31, 2017

QLayout that keeps fixed aspect ratio of it's only widget.

This may be useful when you need layouts keeping widgets at fixed aspect ratio frequently. Like images or videos.
I guess it's easier to handle resize event and place widgets manually for a few cases.

It's far from perfect, as of 2017-05-31 it can't handle margins properly for example.

Sample usage:

	assert(m_rendererWidget == nullptr);
	AspectRatioSingleItemLayout *dummyLayout = new AspectRatioSingleItemLayout(m_ui->wdg3d); // create layout as parent of wdg3d, which is used as place holder
	dummyLayout->setContentsMargins(0, 0, 0, 0);
	m_ui->wdg3d->setLayout(dummyLayout); // set layout of wdg3d to newly created
	m_rendererWidget = renderer->createRendererWidget(m_ui->wdg3d); // create renderer widget as parent of wdg3d
	dummyLayout->addWidget(m_rendererWidget); // add widget into layout, it's aspect ratio will be kept
	dummyLayout->setAlignment(m_rendererWidget, Qt::AlignLeft | Qt::AlignTop);

singleitemlayout

@jowr
Copy link

jowr commented Jun 22, 2017

Hi, could you please add a license? I would like to use this layout in a closed-source, non-commercial project and I wonder whether I am allowed to do so. Thanks.

@pavel-perina
Copy link
Author

Ok, I've added MIT license.

@sjdv1982
Copy link

sjdv1982 commented Sep 7, 2017

@jaehayoo
Copy link

jaehayoo commented Oct 12, 2017

Hi,

I am using the QWebEngineView for html5 + hybrid application.
I need to apply to keep aspect ratio 9:4 for the QWebEngineView when resize the window.


int main(int argc, char *argv[]) {
....
QWebEngineView *view = new QWebEngineView();
view->setUrl(QUrl(QStringLiteral("http://localhost/qttest/index.html")));
view->resize(990, 701);
....
}

How to apply your sample for QWebEngineView??
I tried as like below. But, it dose not applied when I run application and resize window.

AspectRatioSingleItemLayout *dummyLayout = new AspectRatioSingleItemLayout(view);
dummyLayout->setContentsMargins(0, 0, 0, 0);
view->setLayout(dummyLayout);
dummyLayout->addWidget(view);

Please help me...
Thanks a lot !

@pavel-perina
Copy link
Author

pavel-perina commented Mar 26, 2018

Sorry for belated reply.
You can't have view, add dummyLayout into it and then add view into dummyLayout. You need something like placeholderWidget->aspectRatioLayout->view
placeHolder widget is empty resizeable widget, e.g. from Qt Designer.
view is your widget that will have fixed aspect ratio, e.g. image, live camera view, ...

I've added image for clarification.

PS: don't forget this: setAspectRatio ((double) width / height).

@eadanfahey
Copy link

eadanfahey commented Jul 7, 2020

Thank you @pavel-perina! I spent so much time trying to fix the aspect ratio of a video and finally this is something that works.

@Alvazz
Copy link

Alvazz commented Jun 15, 2023

@pavel-perina Can it be used in QGridLayout? Look at the effect in this video I recorded. It should be customized Layout
https://www.youtube.com/watch?v=PcZdCChcwPQ

@Alvazz
Copy link

Alvazz commented Jun 15, 2023

It looks like proportional scaling, in a Layout

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment