spring
spring

Reputation: 18537

Sliding OSX Dock type "sidebar" rather embedded QDockWidget type?

I want to make a sliding "sidebar" similar to the functionality of the OSX "Dock" (e.g. mouse passes edge of screen and Dock slides out). I've been playing around with QDockWidget but since that is embedded in the window layout, it causes everything to shift when it becomes visible.

Can someone suggest a way to implement this?

I'm new to Qt and so don't want to over-think this. Is this just a matter of a custom widget or should I be looking at a borderless window? The custom widget approach seems right but I don't know how to specify that it overlay other window content and also scale if the window scales.

Upvotes: 2

Views: 1507

Answers (1)

QDockWidget has nothing to do with what you want - behaviorally. Just because it's called a Dock widget doesn't mean it's the same "Dock" concept as in OS X. It merely means that it docks somewhere. QDockWidget's documentation quite explicitly explains what is meant by the docking behavior.

The code below implements the behavior you seem to want. Whether it's good design or not is arguable. The reason the code is "convoluted" seems to hint that nobody is expected to come up with such a UI design. What's wrong with actually clicking a button somewhere to display the slider window?

The code works under both Qt 4.8 and 5.1.

Note: This begs to be implemented in Qt Quick 2. That's what it was designed for :) Of course Qt 4.6+ improved the behavior of the QWidget-moving animations, and Qt 5 does further tweaks, but really this code smells bad and there's a good reason it does: QWidget API, while powerful, ultimately encapsulates a set of APIs that date to 1984 when the original Macintosh was released. There's only so much you can do when you have to composite results from a bunch of stacked painters. In Qt Quick, the rendering is done by the GPU. The animation amounts to passing a couple of new floats to the GPU to update a single transformation matrix. That's it.

#include <QApplication>
#include <QWidget>
#include <QGridLayout>
#include <QLabel>
#include <QPainter>
#include <QGradient>
#include <QMouseEvent>
#include <QPropertyAnimation>

class Slider : public QWidget {
    void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE {
        QPainter p(this);
        QLinearGradient g(QPointF(0,0), QPointF(rect().bottomRight()));
        g.setColorAt(0, Qt::blue);
        g.setColorAt(1, Qt::gray);
        p.setBackground(g);
        p.eraseRect(rect());
        p.setPen(Qt::yellow);
        p.setFont(QFont("Helvetica", 48));
        p.drawText(rect(), "Click Me To Hide");
    }
    void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE {
        hide();
    }
public:
    explicit Slider(QWidget *parent = 0) : QWidget(parent) {
        setAttribute(Qt::WA_OpaquePaintEvent);
    }
};

class Window : public QWidget {
    QGridLayout m_layout;
    Slider m_slider;
    QLabel m_label;
    QPropertyAnimation m_animation;
public:
    explicit Window(QWidget *parent = 0, Qt::WindowFlags f = 0) :
        QWidget(parent, f),
        m_layout(this),
        m_slider(this),
        m_animation(&m_slider, "pos")
   {
        setMouseTracking(true);
        m_layout.addWidget(&m_label);
        m_slider.hide();
        m_slider.setMouseTracking(false);
        m_animation.setStartValue(QPoint(-width(), 0));
        m_animation.setEndValue(QPoint(0, 0));
        m_animation.setDuration(500);
        m_animation.setEasingCurve(QEasingCurve::InCubic);
    }
    void leaveEvent(QEvent *) {
        if (window() && QCursor::pos().x() <= window()->geometry().topLeft().x()) {
            showSlider();
        }
    }
    void childEvent(QChildEvent * ev) {
        if (ev->added() && ev->child()->isWidgetType()) {
            ev->child()->installEventFilter(this);
            static_cast<QWidget*>(ev->child())->setMouseTracking(true);
        }
    }
    bool event(QEvent * ev) {
        eventFilter(this, ev);
        return QWidget::event(ev);
    }
    bool eventFilter(QObject *, QEvent * ev) {
        if (ev->type() == QEvent::MouseMove) {
            auto pos = QCursor::pos();
            if (window() && window()->isFullScreen()) {
                if (pos.x() <= window()->geometry().topLeft().x()) {
                    showSlider();
                }
            }
            m_label.setText(QString("%1, %2").arg(pos.x()).arg(pos.y()));
        }
        return false;
    }
    void resizeEvent(QResizeEvent *) {
        m_slider.resize(size());
        m_animation.setStartValue(QPoint(-width(), 0));
    }
    Q_SLOT void showSlider() {
        if (m_slider.isVisible() || (window() && qApp->activeWindow() != window())) return;
        m_slider.raise();
        m_slider.show();
        m_animation.start();
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Window w;
    w.show();
    return a.exec();
}

Upvotes: 2

Related Questions