Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
TextField.cpp 8.61 KiB
#include "TextField.h"

#include <QApplication>
#include <QEventTransition>
#include <QFontDatabase>
#include <QPaintEvent>
#include <QPainter>
#include <QPropertyAnimation>

TextField::TextField(QWidget *parent)
  : QLineEdit(parent)
{
        // Get rid of the focus border on macOS.
        setAttribute(Qt::WA_MacShowFocusRect, 0);

        state_machine_    = new TextFieldStateMachine(this);
        label_            = 0;
        label_font_size_  = 15;
        show_label_       = false;
        background_color_ = QColor("white");

        setFrame(false);
        setAttribute(Qt::WA_Hover);
        setMouseTracking(true);
        setTextMargins(0, 4, 0, 6);

        QFont font("Open Sans");
        font.setPixelSize(14);
        setFont(font);

        state_machine_->start();
        QCoreApplication::processEvents();
}

void
TextField::setBackgroundColor(const QColor &color)
{
        background_color_ = color;
}

QColor
TextField::backgroundColor() const
{
        return background_color_;
}

void
TextField::setShowLabel(bool value)
{
        if (show_label_ == value) {
                return;
        }

        show_label_ = value;

        if (!label_ && value) {
                label_ = new TextFieldLabel(this);
                state_machine_->setLabel(label_);
        }

        if (value) {
                setContentsMargins(0, 23, 0, 0);
        } else {
                setContentsMargins(0, 0, 0, 0);
        }
}

bool
TextField::hasLabel() const
{
        return show_label_;
}

void
TextField::setLabelFontSize(qreal size)
{
        label_font_size_ = size;

        if (label_) {
                QFont font(label_->font());
                font.setPixelSize(size);
                label_->setFont(font);
                label_->update();
        }
}

qreal
TextField::labelFontSize() const
{
        return label_font_size_;
}

void
TextField::setLabel(const QString &label)
{
        label_text_ = label;
        setShowLabel(true);
        label_->update();
}

QString
TextField::label() const
{
        return label_text_;
}

void
TextField::setTextColor(const QColor &color)
{
        text_color_ = color;
        setStyleSheet(QString("QLineEdit { color: %1; }").arg(color.name()));
}

QColor
TextField::textColor() const
{
        if (!text_color_.isValid()) {
                return QColor("black");
        }

        return text_color_;
}

void
TextField::setLabelColor(const QColor &color)
{
        label_color_ = color;
        update();
}

QColor
TextField::labelColor() const
{
        if (!label_color_.isValid()) {
                return QColor("#abb"); // TODO: Move this into Theme.h
        }

        return label_color_;
}
void
TextField::setInkColor(const QColor &color)
{
        ink_color_ = color;
        update();
}

QColor
TextField::inkColor() const
{
        if (!ink_color_.isValid()) {
                return QColor("black");
        }

        return ink_color_;
}

void
TextField::setUnderlineColor(const QColor &color)
{
        underline_color_ = color;
        update();
}

QColor
TextField::underlineColor() const
{
        if (!underline_color_.isValid()) {
                return QColor("black");
        }

        return underline_color_;
}

bool
TextField::event(QEvent *event)
{
        switch (event->type()) {
        case QEvent::Resize:
        case QEvent::Move: {
                if (label_)
                        label_->setGeometry(rect());
                break;
        }
        default:
                break;
        }

        return QLineEdit::event(event);
}

void
TextField::paintEvent(QPaintEvent *event)
{
        QLineEdit::paintEvent(event);

        QPainter painter(this);

        if (text().isEmpty()) {
                painter.setOpacity(1 - state_machine_->progress());
                painter.fillRect(rect(), backgroundColor());
        }

        const int y  = height() - 1;
        const int wd = width() - 5;

        QPen pen;
        pen.setWidth(1);
        pen.setColor(underlineColor());
        painter.setPen(pen);
        painter.setOpacity(1);
        painter.drawLine(2, y, wd, y);

        QBrush brush;
        brush.setStyle(Qt::SolidPattern);
        brush.setColor(inkColor());

        const qreal progress = state_machine_->progress();

        if (progress > 0) {
                painter.setPen(Qt::NoPen);
                painter.setBrush(brush);
                const int w = (1 - progress) * static_cast<qreal>(wd / 2);
                painter.drawRect(w + 2.5, height() - 2, wd - 2 * w, 2);
        }
}

TextFieldStateMachine::TextFieldStateMachine(TextField *parent)
  : QStateMachine(parent)
  , text_field_(parent)
{
        normal_state_  = new QState;
        focused_state_ = new QState;

        label_       = 0;
        offset_anim_ = 0;
        color_anim_  = 0;
        progress_    = 0.0;

        addState(normal_state_);
        addState(focused_state_);

        setInitialState(normal_state_);

        QEventTransition *transition;
        QPropertyAnimation *animation;

        transition = new QEventTransition(parent, QEvent::FocusIn);
        transition->setTargetState(focused_state_);
        normal_state_->addTransition(transition);

        animation = new QPropertyAnimation(this, "progress", this);
        animation->setEasingCurve(QEasingCurve::InCubic);
        animation->setDuration(310);
        transition->addAnimation(animation);

        transition = new QEventTransition(parent, QEvent::FocusOut);
        transition->setTargetState(normal_state_);
        focused_state_->addTransition(transition);

        animation = new QPropertyAnimation(this, "progress", this);
        animation->setEasingCurve(QEasingCurve::OutCubic);
        animation->setDuration(310);
        transition->addAnimation(animation);

        normal_state_->assignProperty(this, "progress", 0);
        focused_state_->assignProperty(this, "progress", 1);

        setupProperties();

        connect(text_field_, SIGNAL(textChanged(QString)), this, SLOT(setupProperties()));
}

void
TextFieldStateMachine::setLabel(TextFieldLabel *label)
{
        if (label_) {
                delete label_;
        }
        if (offset_anim_) {
                removeDefaultAnimation(offset_anim_);
                delete offset_anim_;
        }

        if (color_anim_) {
                removeDefaultAnimation(color_anim_);
                delete color_anim_;
        }

        label_ = label;

        if (label_) {
                offset_anim_ = new QPropertyAnimation(label_, "offset", this);
                offset_anim_->setDuration(210);
                offset_anim_->setEasingCurve(QEasingCurve::OutCubic);
                addDefaultAnimation(offset_anim_);

                color_anim_ = new QPropertyAnimation(label_, "color", this);
                color_anim_->setDuration(210);
                addDefaultAnimation(color_anim_);
        }

        setupProperties();
}

void
TextFieldStateMachine::setupProperties()
{
        if (label_) {
                const int m = text_field_->textMargins().top();

                if (text_field_->text().isEmpty()) {
                        normal_state_->assignProperty(label_, "offset", QPointF(0, 26));
                } else {
                        normal_state_->assignProperty(label_, "offset", QPointF(0, 0 - m));
                }

                focused_state_->assignProperty(label_, "offset", QPointF(0, 0 - m));
                focused_state_->assignProperty(label_, "color", text_field_->inkColor());
                normal_state_->assignProperty(label_, "color", text_field_->labelColor());

                if (0 != label_->offset().y() && !text_field_->text().isEmpty()) {
                        label_->setOffset(QPointF(0, 0 - m));
                } else if (!text_field_->hasFocus() && label_->offset().y() <= 0 &&
                           text_field_->text().isEmpty()) {
                        label_->setOffset(QPointF(0, 26));
                }
        }

        text_field_->update();
}

TextFieldLabel::TextFieldLabel(TextField *parent)
  : QWidget(parent)
  , text_field_(parent)
{
        x_     = 0;
        y_     = 26;
        scale_ = 1;
        color_ = parent->labelColor();

        QFont font("Open Sans SemiBold");
        font.setPixelSize(parent->labelFontSize());
        font.setLetterSpacing(QFont::PercentageSpacing, 102);
        setFont(font);
}

void
TextFieldLabel::paintEvent(QPaintEvent *)
{
        if (!text_field_->hasLabel())
                return;

        QPainter painter(this);
        painter.setRenderHint(QPainter::Antialiasing);
        painter.scale(scale_, scale_);
        painter.setPen(color_);
        painter.setOpacity(1);

        QPointF pos(2 + x_, height() - 36 + y_);
        painter.drawText(pos.x(), pos.y(), text_field_->label());
}