How to resize the icon of specific QTabBar icon?

Viewed 44

I'm trying to create an effect similar to this gif:

2022-08-23_23-08-40.gif

While you are hovering over the tab button it shows a text and also resizes the icon.

I was reading the docs about the property icon-size it says it supports QTabBar, but adding the property into the tab stylesheet, has no effect when the button is being hovered.

I'm doing something wrong or it's a 'bug'?

QTabBar::tab:hover {
     font-size: 12pt;
     icon-size: 64px 64px;
     color: rgb(255, 255, 255);
     background: transparent;
}

As I couldn't get it working with a stylesheet, I tried using an event filter:

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    ui->tabWidget->tabBar()->installEventFilter(this);
}

bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{

        if (event->type() == QEvent::HoverEnter)
        {
            QTabBar *tabbar = static_cast<QTabBar*>(obj);
            tabbar->setIconSize(QSize(40, 40));
        }
        else if (event->type() == QEvent::HoverLeave)
        {
            QTabBar *tabbar = static_cast<QTabBar*>(obj);
            tabbar->setIconSize(QSize(32,32));
        }
    
       // pass the event on to the parent class
       return QMainWindow::eventFilter(obj, event);
}

Result:

enter image description here

Whenever any tab button has hovered, it resizes all icons.

How I could resize just the tab button under the mouse instead of the entire "QTabBar"?

Tab stylesheet options from the gif above:


QTabWidget::pane {
    background: transparent;
}

QTabBar::tab {
    font-size: 12pt;
    color: rgba(255, 255, 255, 0);
    background: transparent;
}

QTabBar::tab:hover {
     font-size: 12pt;
     color: rgb(255, 255, 255);
     background: transparent;
}

QTabBar::tab:selected {
    background: transparent;
}

I don't understand what's going on with the text, the tab text is "Tab" but it's cropping the "T".

Why the tab pane background is not getting transparent?

1 Answers

The recommendation in my comment of yesterday:

The last resort might be to make a custom widget for the tab bar, and to use a QStackedWidget as container for the tab pages.

There are probably samples around. Instead of googling I didn't hesitate to add one on my own:

qIconBar.h:

#ifndef Q_ICON_BAR
#define Q_ICON_BAR

#include <vector>

#include <QIcon>
#include <QWidget>

class QIconBar: public QWidget {
  Q_OBJECT

  private:
    Qt::Orientation _ori;
    QSize _qSizeIcon = QSize(64, 64); // icon size
    int _spc = 4; // size between
    double _scl = 0.6; // scale factor for icons not under mouse
    struct Tab {
      QIcon qIcon;
      QString qLabel;
    };
    std::vector<Tab> _tabs;
    int _iTab = 0; // current tab

  signals:
    void currentChanged(int i);
    void tabClicked(int i);

  public:
    /// constructor.
    explicit QIconBar(
      Qt::Orientation ori = Qt::Horizontal, QWidget* pQParent = nullptr);

    /// destructor.
    virtual ~QIconBar() = default;

    // disabled:
    QIconBar(const QIconBar&) = delete;
    QIconBar& operator=(const QIconBar&) = delete;

  public:
    virtual QSize sizeHint() const;

    QSize iconSize() const { return _qSizeIcon; }
    void setIconSize(const QSize& qSizeIcon);

    int addTab(const QIcon& qIcon, const QString& qText)
    {
      return insertTab(-1, qIcon, qText);
    }
    int insertTab(int i, const QIcon& qIcon, const QString& qText);
    void removeTab(int i);

    int tabAt(const QPoint& qPos) const;
    QIcon tabIcon(int i) const
    {
      return (size_t)i < _tabs.size() ? _tabs[i].qIcon : QIcon();
    }
    void setTabIcon(int i, const QIcon& qIcon);
    QString tabText(int i) const
    {
      return (size_t)i < _tabs.size() ? _tabs[i].qLabel : QString();
    }
    void setTabText(int i, const QString& qLabel);

    int count() const { return (int)_tabs.size(); }
    int currentIndex() const { return _iTab; }
    void setCurrentIndex(int i);

  protected:
    
    virtual void paintEvent(QPaintEvent* pQEvent) override;

    virtual void mouseMoveEvent(QMouseEvent* pQEvent) override;

    virtual void mousePressEvent(QMouseEvent* pQEvent) override;

    virtual void leaveEvent(QEvent* pQEvent) override;
};

#endif // Q_ICON_BAR

Concerning the API, I (shamelessly) borrowed from QTabBar.

qIconBar.cc:

#include <QMouseEvent>
#include <QPainter>

#include "qIconBar.h"

QIconBar::QIconBar(Qt::Orientation ori, QWidget* pQParent):
  QWidget(pQParent),
  _ori(ori)
{
  setMouseTracking(true);
}

QSize QIconBar::sizeHint() const
{
  const int n = (int)_tabs.size();
  const int spc = std::max(n - 1, 0) * _spc;
  return _ori == Qt::Vertical
    ? QSize(_qSizeIcon.width(), _qSizeIcon.height() * n + spc)
    : QSize(_qSizeIcon.width() * n + spc, _qSizeIcon.height());
}

int QIconBar::insertTab(int i, const QIcon& qIcon, const QString& qText)
{
  if ((size_t)i > _tabs.size()) i = (int)_tabs.size();
  _tabs.insert(_tabs.begin() + i, { qIcon, qText });
  update();
  if (i <= _iTab && _iTab + 1 < (int)_tabs.size()) {
    setCurrentIndex(_iTab + 1);
  }
  return i;
}

void QIconBar::removeTab(int i)
{
  if ((size_t)i >= _tabs.size()) return;
  _tabs.erase(_tabs.begin() + i);
  update();
  if ((size_t)_iTab >= _tabs.size()) {
    setCurrentIndex(0);
  }
}

int QIconBar::tabAt(const QPoint& qPos) const
{
  if (!QRect(QPoint(), size()).contains(qPos)) return -1;
  return _ori == Qt::Vertical
    ? qPos.y() % (_qSizeIcon.height() + _spc) < _qSizeIcon.height()
      ? qPos.y() / (_qSizeIcon.height() + _spc)
      : -1
    : qPos.x() % (_qSizeIcon.width() + _spc) < _qSizeIcon.width()
      ? qPos.x() / (_qSizeIcon.width() + _spc)
      : -1;
}

void QIconBar::setCurrentIndex(int i)
{
  _iTab = i;
  emit currentChanged(_iTab);
  update();
}

void QIconBar::paintEvent(QPaintEvent* pQEvent)
{
  const QPoint qPosMouse = mapFromGlobal(QCursor::pos());
  const int iTabMouse = tabAt(qPosMouse);
  QPainter qPainter(this);
  for (int i = 0, n = (int)_tabs.size(); i < n; ++i) {
    const Tab& tab = _tabs[i];
    const QPoint qPos(
      (_ori == Qt::Horizontal) * i * (_qSizeIcon.width() + _spc)
        + _qSizeIcon.width() / 2,
      (_ori == Qt::Vertical) * i *(_qSizeIcon.height() + _spc)
        + _qSizeIcon.height() / 2);
    if (i == _iTab) {
      QRect qRect(QPoint(), _qSizeIcon);
      qRect.moveCenter(qPos);
      qPainter.fillRect(qRect, palette().color(QPalette::Base));
    }
    if (!tab.qIcon.isNull()) {
      const double scale = iTabMouse == i ? 1.0 : _scl;
      const QSize qSizeIcon = scale * _qSizeIcon;
      const QPixmap qPixmap = tab.qIcon.pixmap(qSizeIcon);
      QRect qRect(QPoint(), qSizeIcon);
      qRect.moveCenter(qPos);
      qPainter.drawPixmap(qRect, qPixmap);
    }
    if (i == iTabMouse) {
      QFont qFont = font();
      qFont.setBold(true);
      qPainter.setFont(qFont);
      QRect qRect(QPoint(), _qSizeIcon);
      qRect.moveCenter(qPos);
      qPainter.drawText(qRect, Qt::AlignHCenter | Qt::AlignBottom,
        tab.qLabel);
    }
  }
}

void QIconBar::mouseMoveEvent(QMouseEvent* pQEvent)
{
  update();
  QWidget::mouseMoveEvent(pQEvent);
}

void QIconBar::mousePressEvent(QMouseEvent* pQEvent)
{
  if (pQEvent->button() == Qt::LeftButton) {
    const int iTab = tabAt(pQEvent->pos());
    if ((size_t)iTab < _tabs.size()) {
      setCurrentIndex(iTab);
      emit tabClicked(iTab);
      return;
    }
  }
  QWidget::mousePressEvent(pQEvent);
}

void QIconBar::leaveEvent(QEvent* pQEvent)
{
  update();
  QWidget::leaveEvent(pQEvent);
}

A sample application testQIconBar.cc:

#include <QtWidgets>
#include "qIconBar.h"

const struct Item {
  const char* filePathIcon;
  const char* label;
} items[] = {
  { "icons/document-open.svg", "File" },
  { "icons/document-properties.svg", "Edit" },
  { "icons/edit-find.svg", "Find" },
  { "icons/help-help.svg", "Help" }
};
const size_t nItems = std::size(items);

int main(int argc, char** argv)
{
  QApplication app(argc, argv);
  // setup GUI
  QWidget qWinMain;
  qWinMain.setWindowTitle("QIconBar Test");
  qWinMain.resize(640, 480);
  QHBoxLayout qHBox;
  QIconBar qIconBar(Qt::Vertical);
  qHBox.addWidget(&qIconBar);
  QStackedLayout qStack;
  qHBox.addLayout(&qStack, 1);
  qWinMain.setLayout(&qHBox);
  qWinMain.show();
  // populate icon bar and stack
  QFont qFont = qWinMain.font();
  qFont.setPixelSize(30);
  for (const Item& item : items) {
    qIconBar.addTab(
      QIcon(item.filePathIcon), QString::fromUtf8(item.label));
    const QString qText
      = QString("This is the page for item %1.").arg(item.label);
    QLabel* pQLbl = new QLabel(qText);
    pQLbl->setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
    pQLbl->setFont(qFont);
    pQLbl->setAlignment(Qt::AlignCenter);
    qStack.addWidget(pQLbl);
  }
  qStack.setCurrentIndex(qIconBar.currentIndex());
  // install signal handlers
  QObject::connect(&qIconBar, &QIconBar::currentChanged,
    &qStack, &QStackedLayout::setCurrentIndex);
  // runtime loop
  return app.exec();
}

Additionally, I used the following CMakeLists.txt:

project(QIconBar)

cmake_minimum_required(VERSION 3.10.0)

set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

find_package(Qt5Widgets CONFIG REQUIRED)

include_directories("${CMAKE_SOURCE_DIR}")

qt5_wrap_cpp(moc_sources QIconBar.h
  TARGET testQIconBar)

add_executable(testQIconBar
  testQIconBar.cc qIconBar.cc qIconBar.h
  ${moc_sources})
target_link_libraries(testQIconBar Qt5::Widgets)

…and compiled this in Visual Studio 2019.

Output:

Demo of testQIconBar


According to feedback, I tested my MCVE concerning transparency of the widgets — i.e. with a background image in the main window:

Snapshot of testQIconBar with background image

The QIconBar doesn't clear it's background — the contents is just drawn over the background of the parent window. (It's the same with the QLabels I used in the QStackedLayout.)

Related