//////////////////////////////////////////////////////////////////////////////
// breezetransitionwidget.cpp
// stores event filters and maps widgets to transitions for transitions
// -------------------
//
// SPDX-FileCopyrightText: 2009 Hugo Pereira Da Costa <hugo.pereira@free.fr>
//
// SPDX-License-Identifier: MIT
//////////////////////////////////////////////////////////////////////////////

#include "breezetransitionwidget.h"

#include <QPaintEvent>
#include <QPainter>
#include <QStyleOption>
#include <QTextStream>

namespace Breeze
{
//________________________________________________
bool TransitionWidget::_paintEnabled = true;
bool TransitionWidget::paintEnabled()
{
    return _paintEnabled;
}

int TransitionWidget::_steps = 0;

//________________________________________________
TransitionWidget::TransitionWidget(QWidget *parent, int duration)
    : QWidget(parent)
    , _animation(new Animation(duration, this))
{
    // background flags
    setAttribute(Qt::WA_NoSystemBackground);
    setAutoFillBackground(false);

    // setup animation
    _animation.data()->setStartValue(0);
    _animation.data()->setEndValue(1.0);
    _animation.data()->setTargetObject(this);
    _animation.data()->setPropertyName("opacity");

    // hide when animation is finished
    connect(_animation.data(), &QAbstractAnimation::finished, this, &QWidget::hide);
}

//________________________________________________
QPixmap TransitionWidget::grab(QWidget *widget, QRect rect)
{
    // change rect
    if (!rect.isValid()) {
        rect = widget->rect();
    }
    if (!rect.isValid()) {
        return QPixmap();
    }

    // initialize pixmap
    QPixmap out(rect.size());
    out.fill(Qt::transparent);
    _paintEnabled = false;

    if (testFlag(GrabFromWindow)) {
        rect = rect.translated(widget->mapTo(widget->window(), widget->rect().topLeft()));
        widget = widget->window();
        out = widget->grab(rect);

    } else {
        if (!testFlag(Transparent)) {
            grabBackground(out, widget, rect);
        }
        grabWidget(out, widget, rect);
    }

    _paintEnabled = true;

    return out;
}

//________________________________________________
bool TransitionWidget::event(QEvent *event)
{
    switch (event->type()) {
    case QEvent::MouseButtonPress:
    case QEvent::MouseButtonRelease:
    case QEvent::KeyPress:
    case QEvent::KeyRelease:
        endAnimation();
        hide();
        event->ignore();
        return false;

    default:
        return QWidget::event(event);
    }
}

//________________________________________________
void TransitionWidget::paintEvent(QPaintEvent *event)
{
    // fully transparent case
    if (opacity() >= 1.0 && endPixmap().isNull()) {
        return;
    }
    if (!_paintEnabled) {
        return;
    }

    // get rect
    QRect rect = event->rect();
    if (!rect.isValid()) {
        rect = this->rect();
    }

    // local pixmap
    const bool paintOnWidget(testFlag(PaintOnWidget) && !testFlag(Transparent));
    if (!paintOnWidget) {
        if (_currentPixmap.isNull() || _currentPixmap.size() != size()) {
            _currentPixmap = QPixmap(size());
        }
    }

    // fill
    _currentPixmap.fill(Qt::transparent);

    // copy local pixmap to current
    {
        QPainter p;

        // draw end pixmap first, provided that opacity is small enough
        if (opacity() >= 0.004 && !_endPixmap.isNull()) {
            // faded endPixmap if parent target is transparent and opacity is
            if (opacity() <= 0.996 && testFlag(Transparent)) {
                fade(_endPixmap, _currentPixmap, opacity(), rect);
                p.begin(&_currentPixmap);
                p.setClipRect(event->rect());

            } else {
                if (paintOnWidget) {
                    p.begin(this);
                } else {
                    p.begin(&_currentPixmap);
                }
                p.setClipRect(event->rect());
                p.drawPixmap(QPoint(), _endPixmap);
            }

        } else {
            if (paintOnWidget) {
                p.begin(this);
            } else {
                p.begin(&_currentPixmap);
            }
            p.setClipRect(event->rect());
        }

        // draw fading start pixmap
        if (opacity() <= 0.996 && !_startPixmap.isNull()) {
            if (opacity() >= 0.004) {
                fade(_startPixmap, _localStartPixmap, 1.0 - opacity(), rect);
                p.drawPixmap(QPoint(), _localStartPixmap);

            } else {
                p.drawPixmap(QPoint(), _startPixmap);
            }
        }

        p.end();
    }

    // copy current pixmap on widget
    if (!paintOnWidget) {
        QPainter p(this);
        p.setClipRect(event->rect());
        p.drawPixmap(QPoint(0, 0), _currentPixmap);
        p.end();
    }
}

//________________________________________________
void TransitionWidget::grabBackground(QPixmap &pixmap, QWidget *widget, QRect &rect) const
{
    if (!widget) {
        return;
    }

    QWidgetList widgets;
    if (widget->autoFillBackground()) {
        widgets.append(widget);
    }

    QWidget *parent(nullptr);

    // get highest level parent
    for (parent = widget->parentWidget(); parent; parent = parent->parentWidget()) {
        if (!(parent->isVisible() && parent->rect().isValid())) {
            continue;
        }

        // store in list
        widgets.append(parent);

        // stop at topLevel
        if (parent->isWindow() || parent->autoFillBackground()) {
            break;
        }
    }

    if (!parent) {
        parent = widget;
    }

    // painting
    QPainter p(&pixmap);
    p.setClipRect(rect);
    const QBrush backgroundBrush = parent->palette().brush(parent->backgroundRole());
    if (backgroundBrush.style() == Qt::TexturePattern) {
        p.drawTiledPixmap(rect, backgroundBrush.texture(), widget->mapTo(parent, rect.topLeft()));

    } else {
        p.fillRect(pixmap.rect(), backgroundBrush);
    }

    if (parent->isWindow() && parent->testAttribute(Qt::WA_StyledBackground)) {
        QStyleOption option;
        option.initFrom(parent);
        option.rect = rect;
        option.rect.translate(widget->mapTo(parent, rect.topLeft()));
        p.translate(-option.rect.topLeft());
        parent->style()->drawPrimitive(QStyle::PE_Widget, &option, &p, parent);
        p.translate(option.rect.topLeft());
    }

    // draw all widgets in parent list
    // backward
    QPaintEvent event(rect);
    for (int i = widgets.size() - 1; i >= 0; i--) {
        QWidget *w = widgets.at(i);
        w->render(&p, -widget->mapTo(w, rect.topLeft()), rect, {});
    }

    // end
    p.end();
}

//________________________________________________
void TransitionWidget::grabWidget(QPixmap &pixmap, QWidget *widget, QRect &rect) const
{
    widget->render(&pixmap, pixmap.rect().topLeft(), rect, QWidget::DrawChildren);
}

//________________________________________________
void TransitionWidget::fade(const QPixmap &source, QPixmap &target, qreal opacity, const QRect &rect) const
{
    if (target.isNull() || target.size() != size()) {
        target = QPixmap(size());
    }

    // erase target
    target.fill(Qt::transparent);

    // check opacity
    if (opacity * 255 < 1) {
        return;
    }

    QPainter p(&target);
    p.setClipRect(rect);

    // draw pixmap
    p.drawPixmap(QPoint(0, 0), source);

    // opacity mask (0.996 corresponds to 254/255)
    if (opacity <= 0.996) {
        p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
        QColor color(Qt::black);
        color.setAlphaF(opacity);
        p.fillRect(rect, color);
    }

    p.end();
}

}
