Reputation: 23
I try to create an application with a transparent QToolBar in the titlebar. This works with some modifications to the window itself by using some Objective-C. Also with setUnifiedTitleAndToolBarOnMac()
it looks just like what I wanted. Now there is a problem. I want to add a QGridLayout later on. And just like in the new Photos app on iPadOS I want that the widgets go behind the toolbar. The transparent style would be probably achievable by styling the QToolBar (but this is a problem I can work on). My question is now, is there any possible way to overlap two widgets or send widgets behind any other widget? I could also work with a QVBoxLayout, but I don't know how to set some widgets behind any other widget (or layout).
What I try to achieve is the following:
My current approach is this:
I heard about stackUnder()
but this does not work.
I hope I got my question clear, its my first time posting here.
Thanks!
EDIT:
QToolBar *tabBar = new QToolBar(this);
tabBar->setMovable(false);
tabBar->setFloatable(false);
addToolBar(tabBar);
this->setUnifiedTitleAndToolBarOnMac(true);
QPushButton *tabBtn = new QPushButton("Test", this); // simulates our iPadOS tab control
QWidget *spaceLeft = new QWidget(this);
QWidget *spaceRight = new QWidget(this);
spaceLeft->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
spaceRight->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
tabBar->addWidget(spaceLeft);
tabBar->addWidget(tabBtn);
tabBar->addWidget(spaceRight);
ui->toggleMin->stackUnder(tabBar);
The three buttons are done using QtDesigner / .ui!
Upvotes: 2
Views: 1498
Reputation: 4869
Here is an example of a somewhat generic container QWidget
that manages an overlay QWidget
, which in this case is a toolbar-style widget (but it really could be anything). The code is a bit verbose for a simple example, but it tries to cover some different use cases to make the container more flexible. The overlay widget could be a QToolBar
, but doesn't have to be.
The main technique here is that the overlay widget is not positioned in a layout, but instead its geometry is managed "manually" by the parent widget (see positionToolbar()
in the code). This geometry needs to be re-adjusted whenever the sizes of the container or overlay change. The most convenient "hook" for this is the QWidget::resizeEvent()
method which we re-implement in the example. We also monitor the overlay widget for size changes, eg. when child items are added/removed or its styling is modified.
A different direction could be to write a custom QLayout
subclass which essentially does the same thing (in QLayoutItem::setGeometry()
override). It would be a bit more involved, but also more flexible since it could be used in any widget or as a sub-layout.
UPDATE: I have created such a layout manager, it is called OverlayStackLayout (docs). Also a simple but functional image viewer example app, inspired by this short one.
ToolbarOverlayWidget.h
#include <QEvent>
#include <QPointer>
#include <QToolBar>
#include <QWidget>
class ToolbarOverlayWidget : public QWidget
{
Q_OBJECT
public:
ToolbarOverlayWidget(QWidget *parent = nullptr) :
QWidget(parent)
{
// WA_LayoutOnEntireRect will ensure that any QLayout set on this widget will
// ignore QWidget::contentsMargins(), which allows us to use them for toolbar
// margins/positioning instead. This does not affect any layout()->contentsMargins()
// which can still be used to pad anything the main layout itself contains.
setAttribute(Qt::WA_LayoutOnEntireRect);
// create a default toolbar
setToolbar(new QToolBar(this));
}
~ToolbarOverlayWidget() override
{
// don't delete the toolbar widget if we don't own it
if (m_toolbar && !m_ownTbWidget)
m_toolbar->setParent(nullptr);
}
// Returns toolbar widget instance as a QToolBar.
// Returns nullptr if no toolbar widget is set, or widget does not inherit QToolBar.
QToolBar *toolbar() const { return qobject_cast<QToolBar*>(m_toolbar.data()); }
// Set a widget to be used as a toolbar. ToolbarOverlayWidget takes ownership of toolbar.
void setToolbar(QWidget *toolbar)
{
// dispose of old toolbar?
if (m_toolbar) {
m_toolbar->removeEventFilter(this);
m_toolbar->disconnect(this);
if (m_ownTbWidget)
m_toolbar->deleteLater();
else
m_toolbar->setParent(nullptr);
m_toolbar.clear();
}
if (!toolbar)
return;
m_toolbar = toolbar;
// toolbar's parent should be this widget, also keep track of if we owned it originally
m_ownTbWidget = (m_toolbar->parent() == this);
if (!m_ownTbWidget)
m_toolbar->setParent(this);
m_toolbar->setAutoFillBackground(true); // ensure a background if otherwise unstyled
m_toolbar->installEventFilter(this); // see eventFilter()
if (QToolBar *tb = qobject_cast<QToolBar*>(toolbar)) {
// reposition toolbar if icon size or button style change
connect(tb, &QToolBar::iconSizeChanged, this, [this](const QSize &) {
positionToolbar(); });
connect(tb, &QToolBar::toolButtonStyleChanged, this, [this](Qt::ToolButtonStyle) {
positionToolbar(); });
}
if (isVisible())
positionToolbar();
}
QSize sizeHint() const override
{
if (m_toolbar.isNull())
return QWidget::sizeHint();
// ensure a reasonable size hint if we have a toolbar which is larger than any contents
return QWidget::sizeHint().expandedTo(m_toolbar->sizeHint());
}
protected:
void resizeEvent(QResizeEvent *e) override
{
QWidget::resizeEvent(e);
// keep the toolbar properly positioned
positionToolbar();
}
// filter is installed on the toolbar widget
bool eventFilter(QObject *w, QEvent *e) override
{
if (!m_toolbar.isNull() && w == m_toolbar) {
switch (e->type()) {
// reposition the toolbar if its size hint (possibly) changed
case QEvent::ChildAdded:
case QEvent::ChildRemoved:
case QEvent::StyleChange:
case QEvent::FontChange:
if (isVisible())
positionToolbar();
break;
default:
break;
}
}
return QWidget::eventFilter(w, e);
}
private slots:
// Keep the toolbar properly positioned and sized
void positionToolbar() const
{
if (m_toolbar.isNull())
return;
const QRect rect = contentsRect(); // available geometry for toolbar
QRect tbRect(rect.topLeft(), m_toolbar->sizeHint()); // default TB position and size
// expand to full width?
if (m_toolbar->sizePolicy().expandingDirections() & Qt::Horizontal)
tbRect.setWidth(rect.width());
// constrain width if it is too wide to fit
else if (tbRect.width() > rect.width())
tbRect.setWidth(rect.width());
// otherwise center the toolbar if it is narrower than available width
else if (tbRect.width() < rect.width())
tbRect.moveLeft(rect.x() + (rect.width() - tbRect.width()) / 2);
// constrain height
if (tbRect.height() > rect.height())
tbRect.setHeight(rect.height());
// Set position and size of the toolbar.
m_toolbar->setGeometry(tbRect);
// Make sure the toolbar stacks on top
m_toolbar->raise();
}
private:
QPointer<QWidget> m_toolbar;
bool m_ownTbWidget = true;
};
Implementation example: This shows a couple styling options, both using CSS. In the first, the toolbar is minimum width and centered in the available area, and in the second the toolbar is styled as full width with centered buttons and a partially transparent background. The second version uses a plain QWidget instead of QToolBar because it's more flexible to style (QToolBar has some "quirks" to put it nicely).
main.cpp
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Use a stack widget as top-level for demo. This will have two pages.
QStackedWidget stack;
stack.resize(640, 480);
// common style for tool buttons
const QString commonCss(QStringLiteral(
"QToolButton {"
" font: bold normal 14px sans-serif;"
" color: #62777F;"
" background: transparent;"
" border-radius: 12px;"
" padding: 3px 6px 4px;"
"}"
"QToolButton:checked, QToolButton:hover {"
" color: #D5F2E5;"
" background-color: #62777F;"
"}"
"QToolButton:pressed { background-color: #72AF95; }"
));
// creates a new ToolbarOverlayWidget holding one scalable image label
auto imageWidget = [&stack](const QString &img) {
ToolbarOverlayWidget *w = new ToolbarOverlayWidget(&stack);
w->setLayout(new QVBoxLayout);
w->layout()->setContentsMargins(0,0,0,0);
QLabel *lbl = new QLabel(w);
lbl->setPixmap(QPixmap(img));
lbl->setScaledContents(true);
lbl->setMinimumSize(160, 120);
w->layout()->addWidget(lbl);
return w;
};
// Page 1: The first stack page uses a default QToolBar, which is simpler but less flexible.
{
ToolbarOverlayWidget *widget = imageWidget("../../images/image1.jpg");
// Set toolbar appearance
widget->setContentsMargins(0, 10, 0, 0); // 10px above toolbar, works better than CSS margin
widget->toolbar()->setStyleSheet(commonCss + QLatin1String(
"QToolBar {"
" background: #B5CAC1;"
" border-radius: 14px;"
" padding: 4px;" // can only set one padding for all sides of a qtoolbar
" spacing: 12px;" // between items
"}"
"QToolBar::separator { width: 1px; background-color: #72AF95; }"
));
// Add items to toolbar
QActionGroup *viewGrp = new QActionGroup(widget);
auto addViewAction = [viewGrp, widget](const QString &ttl, bool chk = false) {
QAction *act = widget->toolbar()->addAction(ttl);
act->setCheckable(true);
act->setChecked(chk);
viewGrp->addAction(act);
return act;
};
addViewAction("Years");
addViewAction("Months");
addViewAction("Days");
addViewAction("All Photos", true);
widget->toolbar()->addSeparator();
// page stack "push" action
QObject::connect(widget->toolbar()->addAction("view >"), &QAction::triggered, [&stack]() {
stack.setCurrentIndex(1);
});
stack.addWidget(widget);
}
// Page 2: This page uses a plain widget for a toolbar.
{
ToolbarOverlayWidget *widget = imageWidget("../../images/image1.jpg");
// Create a custom toolbar-style widget
QWidget *toolbar = new QWidget(widget);
toolbar->setLayout(new QHBoxLayout);
toolbar->layout()->setContentsMargins(3, 14, 3, 28);
toolbar->layout()->setSpacing(18);
toolbar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
toolbar->setObjectName("ToolbarWidget");
toolbar->setStyleSheet(commonCss + QLatin1String(
"#ToolbarWidget {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop: 0 black, stop: 1 transparent);"
"}"
"QToolButton {"
" color: #D5F2E5;"
" background-color: #62777F;"
"}"
"QToolButton:checked, QToolButton:hover:!pressed {"
" color: #62777F;"
" background-color: #D5F2E5;"
"}"
));
// Add items to toolbar
auto addButton = [toolbar](const QString &ttl, QLayout *lo, bool chk = false) {
QToolButton *tb = new QToolButton(toolbar);
tb->setText(ttl);
tb->setCheckable(chk);
lo->addWidget(tb);
return tb;
};
// left expander to keep buttons centered
toolbar->layout()->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored));
// page stack "pop" action
QObject::connect(addButton("< back", toolbar->layout()), &QToolButton::clicked, [&stack]() {
stack.setCurrentIndex(0);
});
addButton("Adjust", toolbar->layout());
addButton("Select", toolbar->layout(), true);
// zoom buttons, new sub-layout w/out spacing
QHBoxLayout *zoomBtnLayout = new QHBoxLayout;
zoomBtnLayout->setSpacing(0);
const QString zoomCss =
QStringLiteral("QToolButton { border-top-%1-radius: 0; border-bottom-%1-radius: 0; }");
addButton("-", zoomBtnLayout)->setStyleSheet(zoomCss.arg("right"));
addButton("+", zoomBtnLayout)->setStyleSheet(zoomCss.arg("left"));
toolbar->layout()->addItem(zoomBtnLayout);
// right expander to keep buttons centered
toolbar->layout()->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored));
// Use the custom widget as toolbar
widget->setToolbar(toolbar);
stack.addWidget(widget);
}
stack.show();
return a.exec();
}
#include "main.moc"
Related questions:
Upvotes: 3