You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
291 lines
8.5 KiB
291 lines
8.5 KiB
/*****************************************************************************
|
|
* Copyright (C) 2021 VLC authors and VideoLAN
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* ( at your option ) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
|
|
*****************************************************************************/
|
|
#include "flickable_scroll_handler.hpp"
|
|
|
|
#include <QApplication>
|
|
#include <QStyleHints>
|
|
#include <QtQml>
|
|
#include <QQmlProperty>
|
|
|
|
#define OVERRIDE_SCROLLBAR_STEPSIZE true
|
|
#define PAGE_SCROLL_SHIFT_OR_CTRL true
|
|
|
|
FlickableScrollHandler::FlickableScrollHandler(QObject *parent)
|
|
: QObject(parent)
|
|
{
|
|
connect(this, &FlickableScrollHandler::scaleFactorChanged, this, [this]() {
|
|
m_effectiveScaleFactor = QApplication::styleHints()->wheelScrollLines() * 20 * m_scaleFactor;
|
|
emit effectiveScaleFactorChanged();
|
|
});
|
|
|
|
emit scaleFactorChanged();
|
|
}
|
|
|
|
FlickableScrollHandler::~FlickableScrollHandler()
|
|
{
|
|
detach();
|
|
}
|
|
|
|
void FlickableScrollHandler::classBegin()
|
|
{
|
|
}
|
|
|
|
void FlickableScrollHandler::componentComplete()
|
|
{
|
|
assert(parent());
|
|
|
|
m_target = qobject_cast<QQuickItem*>(parent());
|
|
if (!m_target || !m_target->inherits("QQuickFlickable"))
|
|
{
|
|
qmlWarning(this) << "Parent is not QQuickFlickable!";
|
|
return;
|
|
}
|
|
|
|
const auto qCtx = qmlContext(m_target);
|
|
assert(qCtx);
|
|
|
|
m_propertyContentX = QQmlProperty(m_target, "contentX", qCtx);
|
|
m_propertyContentY = QQmlProperty(m_target, "contentY", qCtx);
|
|
m_propertyContentHeight = QQmlProperty(m_target, "contentHeight", qCtx);
|
|
m_propertyContentWidth = QQmlProperty(m_target, "contentWidth", qCtx);
|
|
m_propertyHeight = QQmlProperty(m_target, "height", qCtx);
|
|
m_propertyWidth = QQmlProperty(m_target, "width", qCtx);
|
|
|
|
m_scrollBarV.scrollBar = QQmlProperty(m_target, "ScrollBar.vertical", qCtx);
|
|
m_scrollBarH.scrollBar = QQmlProperty(m_target, "ScrollBar.horizontal", qCtx);
|
|
|
|
adjustScrollBarV();
|
|
adjustScrollBarH();
|
|
|
|
m_scrollBarV.scrollBar.connectNotifySignal(this, SLOT(adjustScrollBarV()));
|
|
m_scrollBarH.scrollBar.connectNotifySignal(this, SLOT(adjustScrollBarH()));
|
|
|
|
if (enabled())
|
|
attach();
|
|
|
|
emit initialized();
|
|
}
|
|
|
|
bool FlickableScrollHandler::eventFilter(QObject *watched, QEvent *event)
|
|
{
|
|
assert (event);
|
|
assert (watched == m_target);
|
|
|
|
if (event->type() != QEvent::Wheel)
|
|
return QObject::eventFilter(watched, event);
|
|
|
|
const auto wheel = static_cast<QWheelEvent *>(event);
|
|
|
|
struct {
|
|
QPoint delta;
|
|
enum class Type {
|
|
Pixel,
|
|
Degree
|
|
} type;
|
|
} ev;
|
|
|
|
using Type = decltype(ev)::Type;
|
|
|
|
if (!wheel->pixelDelta().isNull() && (wheel->pixelDelta().manhattanLength() % 120))
|
|
{
|
|
ev.delta = wheel->pixelDelta();
|
|
ev.type = Type::Pixel;
|
|
}
|
|
else if (!m_handleOnlyPixelDelta && !wheel->angleDelta().isNull())
|
|
{
|
|
ev.delta = wheel->angleDelta() / 8 / 15;
|
|
ev.type = Type::Degree;
|
|
}
|
|
else
|
|
return false;
|
|
|
|
if (wheel->inverted())
|
|
{
|
|
ev.delta = -ev.delta;
|
|
}
|
|
|
|
const auto handler = [this, wheel, ev](Qt::Orientation orientation, bool fallback = false) {
|
|
const auto vertical = (orientation == Qt::Vertical);
|
|
|
|
const auto handler = [this, vertical](qreal delta, Type type) {
|
|
const auto contentSize = vertical ? m_propertyContentHeight.read().toReal()
|
|
: m_propertyContentWidth.read().toReal();
|
|
const auto pos = vertical ? m_propertyContentY.read().toReal()
|
|
: m_propertyContentX.read().toReal();
|
|
const auto size = vertical ? m_propertyHeight.read().toReal()
|
|
: m_propertyWidth.read().toReal();
|
|
|
|
if (contentSize < size || pos >= contentSize)
|
|
return false;
|
|
|
|
const auto& scrollBar = vertical ? m_scrollBarV : m_scrollBarH;
|
|
if (scrollBar.valid())
|
|
{
|
|
// Attached ScrollBar is available, use it to scroll
|
|
#if !OVERRIDE_SCROLLBAR_STEPSIZE
|
|
const auto _stepSize = scrollBar.stepSize.read().toReal();
|
|
#endif
|
|
qreal newStepSize = delta;
|
|
if (type == Type::Degree)
|
|
newStepSize *= (m_effectiveScaleFactor / contentSize);
|
|
else
|
|
newStepSize *= (1.0 / contentSize);
|
|
|
|
scrollBar.stepSize.write(qBound<qreal>(0, qAbs(newStepSize), 1));
|
|
|
|
if (newStepSize > 0)
|
|
scrollBar.decreaseMethod.invoke(scrollBar.item);
|
|
else
|
|
scrollBar.increaseMethod.invoke(scrollBar.item);
|
|
|
|
#if !OVERRIDE_SCROLLBAR_STEPSIZE
|
|
scrollBar.stepSize.write(_stepSize);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
qreal newPos = pos;
|
|
if (type == Type::Degree)
|
|
newPos -= (m_effectiveScaleFactor * delta);
|
|
else
|
|
newPos -= delta;
|
|
|
|
newPos = qBound<qreal>(0, newPos, contentSize - size);
|
|
|
|
if (vertical)
|
|
m_propertyContentY.write(newPos);
|
|
else
|
|
m_propertyContentX.write(newPos);
|
|
}
|
|
|
|
return (vertical ? (!qFuzzyCompare(m_propertyContentY.read().toReal(), pos))
|
|
: (!qFuzzyCompare(m_propertyContentX.read().toReal(), pos)));
|
|
};
|
|
|
|
const bool _vertical = (fallback ? !vertical : vertical);
|
|
qreal _delta = _vertical ? ev.delta.y() : ev.delta.x();
|
|
auto _type = ev.type;
|
|
|
|
#if PAGE_SCROLL_SHIFT_OR_CTRL
|
|
if (_delta != 0 && (wheel->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier)))
|
|
{
|
|
_type = Type::Pixel;
|
|
_delta = (_vertical ? m_propertyHeight.read().toReal()
|
|
: m_propertyWidth.read().toReal()) * (_delta > 0 ? 1 : -1);
|
|
}
|
|
#endif
|
|
|
|
if (_delta != 0 && handler(_delta, _type))
|
|
{
|
|
wheel->accept();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
bool rV = handler(Qt::Vertical);
|
|
bool rH = handler(Qt::Horizontal);
|
|
|
|
if (m_fallbackScroll && (rV == false && rH == false))
|
|
{
|
|
if (ev.delta.y() != 0)
|
|
rH = handler(Qt::Horizontal, true);
|
|
else if (ev.delta.x() != 0)
|
|
rV = handler(Qt::Vertical, true);
|
|
}
|
|
|
|
return (rV || rH) || QObject::eventFilter(watched, event);
|
|
}
|
|
|
|
void FlickableScrollHandler::attach()
|
|
{
|
|
if (m_target)
|
|
{
|
|
m_target->installEventFilter(this);
|
|
}
|
|
}
|
|
|
|
void FlickableScrollHandler::detach()
|
|
{
|
|
if (m_target)
|
|
{
|
|
m_target->removeEventFilter(this);
|
|
}
|
|
}
|
|
|
|
void FlickableScrollHandler::adjustScrollBar(ScrollBar& scrollBar)
|
|
{
|
|
const auto item = scrollBar.scrollBar.read().value<QQuickItem *>();
|
|
|
|
scrollBar.item = item;
|
|
|
|
if (item)
|
|
{
|
|
scrollBar.stepSize = QQmlProperty(item, "stepSize", qmlContext(item));
|
|
scrollBar.decreaseMethod = item->metaObject()->method(item->metaObject()->indexOfMethod("decrease()"));
|
|
scrollBar.increaseMethod = item->metaObject()->method(item->metaObject()->indexOfMethod("increase()"));
|
|
}
|
|
}
|
|
|
|
qreal FlickableScrollHandler::scaleFactor() const
|
|
{
|
|
return m_scaleFactor;
|
|
}
|
|
|
|
void FlickableScrollHandler::setScaleFactor(qreal newScaleFactor)
|
|
{
|
|
if (qFuzzyCompare(m_scaleFactor, newScaleFactor))
|
|
return;
|
|
m_scaleFactor = newScaleFactor;
|
|
emit scaleFactorChanged();
|
|
}
|
|
|
|
bool FlickableScrollHandler::enabled() const
|
|
{
|
|
return m_enabled;
|
|
}
|
|
|
|
void FlickableScrollHandler::setEnabled(bool newEnabled)
|
|
{
|
|
if (m_enabled == newEnabled)
|
|
return;
|
|
|
|
if (newEnabled)
|
|
attach();
|
|
else
|
|
detach();
|
|
|
|
m_enabled = newEnabled;
|
|
emit enabledChanged();
|
|
}
|
|
|
|
void FlickableScrollHandler::adjustScrollBarV()
|
|
{
|
|
adjustScrollBar(m_scrollBarV);
|
|
}
|
|
|
|
void FlickableScrollHandler::adjustScrollBarH()
|
|
{
|
|
adjustScrollBar(m_scrollBarH);
|
|
}
|
|
|
|
qreal FlickableScrollHandler::effectiveScaleFactor() const
|
|
{
|
|
return m_effectiveScaleFactor;
|
|
}
|
|
|