mirror-chatterino2/src/widgets/Scrollbar.cpp
2018-06-26 13:24:55 +02:00

387 lines
10 KiB
C++

#include "widgets/scrollbar.hpp"
#include "application.hpp"
#include "singletons/thememanager.hpp"
#include "widgets/helper/channelview.hpp"
#include <QDebug>
#include <QMouseEvent>
#include <QPainter>
#include <QTimer>
#include <cmath>
#define MIN_THUMB_HEIGHT 10
namespace chatterino {
namespace widgets {
Scrollbar::Scrollbar(ChannelView *parent)
: BaseWidget(parent)
, currentValueAnimation_(this, "currentValue_")
{
resize(int(16 * this->getScale()), 100);
this->currentValueAnimation_.setDuration(150);
this->currentValueAnimation_.setEasingCurve(QEasingCurve(QEasingCurve::OutCubic));
setMouseTracking(true);
}
void Scrollbar::addHighlight(ScrollbarHighlight highlight)
{
ScrollbarHighlight deleted;
this->highlights_.pushBack(highlight, deleted);
}
void Scrollbar::addHighlightsAtStart(const std::vector<ScrollbarHighlight> &_highlights)
{
this->highlights_.pushFront(_highlights);
}
void Scrollbar::replaceHighlight(size_t index, ScrollbarHighlight replacement)
{
this->highlights_.replaceItem(index, replacement);
}
void Scrollbar::pauseHighlights()
{
this->highlightsPaused_ = true;
}
void Scrollbar::unpauseHighlights()
{
this->highlightsPaused_ = false;
}
messages::LimitedQueueSnapshot<ScrollbarHighlight> Scrollbar::getHighlightSnapshot()
{
if (!this->highlightsPaused_) {
this->highlightSnapshot_ = this->highlights_.getSnapshot();
}
return this->highlightSnapshot_;
}
void Scrollbar::scrollToBottom(bool animate)
{
this->setDesiredValue(this->maximum_ - this->getLargeChange(), animate);
}
bool Scrollbar::isAtBottom() const
{
return this->atBottom_;
}
void Scrollbar::setMaximum(qreal value)
{
this->maximum_ = value;
updateScroll();
}
void Scrollbar::setMinimum(qreal value)
{
this->minimum_ = value;
updateScroll();
}
void Scrollbar::setLargeChange(qreal value)
{
this->largeChange_ = value;
updateScroll();
}
void Scrollbar::setSmallChange(qreal value)
{
this->smallChange_ = value;
updateScroll();
}
void Scrollbar::setDesiredValue(qreal value, bool animated)
{
auto app = getApp();
animated &= app->settings->enableSmoothScrolling.getValue();
value = std::max(this->minimum_, std::min(this->maximum_ - this->largeChange_, value));
if (std::abs(this->desiredValue_ + this->smoothScrollingOffset_ - value) > 0.0001) {
if (animated) {
this->currentValueAnimation_.stop();
this->currentValueAnimation_.setStartValue(this->currentValue_ +
this->smoothScrollingOffset_);
// if (((this->getMaximum() - this->getLargeChange()) - value) <= 0.01) {
// value += 1;
// }
this->currentValueAnimation_.setEndValue(value);
this->smoothScrollingOffset_ = 0;
this->atBottom_ = ((this->getMaximum() - this->getLargeChange()) - value) <= 0.0001;
this->currentValueAnimation_.start();
} else {
if (this->currentValueAnimation_.state() != QPropertyAnimation::Running) {
this->smoothScrollingOffset_ = 0;
this->desiredValue_ = value;
this->currentValueAnimation_.stop();
this->atBottom_ = ((this->getMaximum() - this->getLargeChange()) - value) <= 0.0001;
setCurrentValue(value);
}
}
}
this->smoothScrollingOffset_ = 0;
this->desiredValue_ = value;
this->desiredValueChanged_.invoke();
}
qreal Scrollbar::getMaximum() const
{
return this->maximum_;
}
qreal Scrollbar::getMinimum() const
{
return this->minimum_;
}
qreal Scrollbar::getLargeChange() const
{
return this->largeChange_;
}
qreal Scrollbar::getSmallChange() const
{
return this->smallChange_;
}
qreal Scrollbar::getDesiredValue() const
{
return this->desiredValue_ + this->smoothScrollingOffset_;
}
qreal Scrollbar::getCurrentValue() const
{
return this->currentValue_;
}
void Scrollbar::offset(qreal value)
{
if (this->currentValueAnimation_.state() == QPropertyAnimation::Running) {
this->smoothScrollingOffset_ += value;
} else {
this->setDesiredValue(this->getDesiredValue() + value);
}
}
pajlada::Signals::NoArgSignal &Scrollbar::getCurrentValueChanged()
{
return this->currentValueChanged_;
}
pajlada::Signals::NoArgSignal &Scrollbar::getDesiredValueChanged()
{
return this->desiredValueChanged_;
}
void Scrollbar::setCurrentValue(qreal value)
{
value = std::max(this->minimum_, std::min(this->maximum_ - this->largeChange_,
value + this->smoothScrollingOffset_));
if (std::abs(this->currentValue_ - value) > 0.0001) {
this->currentValue_ = value;
this->updateScroll();
this->currentValueChanged_.invoke();
this->update();
}
}
void Scrollbar::printCurrentState(const QString &prefix) const
{
qDebug() << prefix //
<< "Current value: " << this->getCurrentValue() //
<< ". Maximum: " << this->getMaximum() //
<< ". Minimum: " << this->getMinimum() //
<< ". Large change: " << this->getLargeChange(); //
}
void Scrollbar::paintEvent(QPaintEvent *)
{
auto *app = getApp();
bool mouseOver = this->mouseOverIndex_ != -1;
int xOffset = mouseOver ? 0 : width() - int(4 * this->getScale());
QPainter painter(this);
painter.fillRect(rect(), this->themeManager->scrollbars.background);
// painter.fillRect(QRect(xOffset, 0, width(), this->buttonHeight),
// this->themeManager->ScrollbarArrow);
// painter.fillRect(QRect(xOffset, height() - this->buttonHeight, width(),
// this->buttonHeight),
// this->themeManager->ScrollbarArrow);
this->thumbRect_.setX(xOffset);
// mouse over thumb
if (this->mouseDownIndex_ == 2) {
painter.fillRect(this->thumbRect_, this->themeManager->scrollbars.thumbSelected);
}
// mouse not over thumb
else {
painter.fillRect(this->thumbRect_, this->themeManager->scrollbars.thumb);
}
// draw highlights
auto snapshot = this->getHighlightSnapshot();
size_t snapshotLength = snapshot.getLength();
if (snapshotLength == 0) {
return;
}
int w = this->width();
float y = 0;
float dY = float(this->height()) / float(snapshotLength);
int highlightHeight = int(std::ceil(dY));
for (size_t i = 0; i < snapshotLength; i++) {
ScrollbarHighlight const &highlight = snapshot[i];
if (!highlight.isNull()) {
QColor color = [&] {
switch (highlight.getColor()) {
case ScrollbarHighlight::Highlight:
return app->themes->scrollbars.highlights.highlight;
case ScrollbarHighlight::Subscription:
return app->themes->scrollbars.highlights.subscription;
}
return QColor();
}();
switch (highlight.getStyle()) {
case ScrollbarHighlight::Default: {
painter.fillRect(w / 8 * 3, int(y), w / 4, highlightHeight, color);
} break;
case ScrollbarHighlight::Line: {
painter.fillRect(0, int(y), w, 1, color);
} break;
case ScrollbarHighlight::None:;
}
}
y += dY;
}
}
void Scrollbar::resizeEvent(QResizeEvent *)
{
this->resize(int(16 * this->getScale()), this->height());
}
void Scrollbar::mouseMoveEvent(QMouseEvent *event)
{
if (this->mouseDownIndex_ == -1) {
int y = event->pos().y();
auto oldIndex = this->mouseOverIndex_;
if (y < this->buttonHeight_) {
this->mouseOverIndex_ = 0;
} else if (y < this->thumbRect_.y()) {
this->mouseOverIndex_ = 1;
} else if (this->thumbRect_.contains(2, y)) {
this->mouseOverIndex_ = 2;
} else if (y < height() - this->buttonHeight_) {
this->mouseOverIndex_ = 3;
} else {
this->mouseOverIndex_ = 4;
}
if (oldIndex != this->mouseOverIndex_) {
update();
}
} else if (this->mouseDownIndex_ == 2) {
int delta = event->pos().y() - this->lastMousePosition_.y();
setDesiredValue(this->desiredValue_ + qreal(delta) / this->trackHeight_ * this->maximum_);
}
this->lastMousePosition_ = event->pos();
}
void Scrollbar::mousePressEvent(QMouseEvent *event)
{
int y = event->pos().y();
if (y < this->buttonHeight_) {
this->mouseDownIndex_ = 0;
} else if (y < this->thumbRect_.y()) {
this->mouseDownIndex_ = 1;
} else if (this->thumbRect_.contains(2, y)) {
this->mouseDownIndex_ = 2;
} else if (y < height() - this->buttonHeight_) {
this->mouseDownIndex_ = 3;
} else {
this->mouseDownIndex_ = 4;
}
}
void Scrollbar::mouseReleaseEvent(QMouseEvent *event)
{
int y = event->pos().y();
if (y < this->buttonHeight_) {
if (this->mouseDownIndex_ == 0) {
setDesiredValue(this->desiredValue_ - this->smallChange_, true);
}
} else if (y < this->thumbRect_.y()) {
if (this->mouseDownIndex_ == 1) {
setDesiredValue(this->desiredValue_ - this->smallChange_, true);
}
} else if (this->thumbRect_.contains(2, y)) {
// do nothing
} else if (y < height() - this->buttonHeight_) {
if (this->mouseDownIndex_ == 3) {
setDesiredValue(this->desiredValue_ + this->smallChange_, true);
}
} else {
if (this->mouseDownIndex_ == 4) {
setDesiredValue(this->desiredValue_ + this->smallChange_, true);
}
}
this->mouseDownIndex_ = -1;
update();
}
void Scrollbar::leaveEvent(QEvent *)
{
this->mouseOverIndex_ = -1;
update();
}
void Scrollbar::updateScroll()
{
this->trackHeight_ =
this->height() - this->buttonHeight_ - this->buttonHeight_ - MIN_THUMB_HEIGHT - 1;
this->thumbRect_ = QRect(
0, int(this->currentValue_ / this->maximum_ * this->trackHeight_) + 1 + this->buttonHeight_,
this->width(),
int(this->largeChange_ / this->maximum_ * this->trackHeight_) + MIN_THUMB_HEIGHT);
this->update();
}
} // namespace widgets
} // namespace chatterino