Moved out MaskedInputField to separate module.
This commit is contained in:
parent
2c5923dc79
commit
faa67d73c5
9 changed files with 726 additions and 662 deletions
|
|
@ -202,6 +202,8 @@ PRIVATE
|
||||||
ui/widgets/input_fields.h
|
ui/widgets/input_fields.h
|
||||||
ui/widgets/labels.cpp
|
ui/widgets/labels.cpp
|
||||||
ui/widgets/labels.h
|
ui/widgets/labels.h
|
||||||
|
ui/widgets/fields/masked_input_field.cpp
|
||||||
|
ui/widgets/fields/masked_input_field.h
|
||||||
ui/widgets/fields/number_input.cpp
|
ui/widgets/fields/number_input.cpp
|
||||||
ui/widgets/fields/number_input.h
|
ui/widgets/fields/number_input.h
|
||||||
ui/widgets/fields/password_input.cpp
|
ui/widgets/fields/password_input.cpp
|
||||||
|
|
|
||||||
546
ui/widgets/fields/masked_input_field.cpp
Normal file
546
ui/widgets/fields/masked_input_field.cpp
Normal file
|
|
@ -0,0 +1,546 @@
|
||||||
|
// This file is part of Desktop App Toolkit,
|
||||||
|
// a set of libraries for developing nice desktop applications.
|
||||||
|
//
|
||||||
|
// For license and copyright information please follow this link:
|
||||||
|
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||||
|
//
|
||||||
|
#include "ui/widgets/fields/masked_input_field.h"
|
||||||
|
|
||||||
|
#include "base/qt/qt_common_adapters.h"
|
||||||
|
#include "ui/painter.h"
|
||||||
|
#include "ui/widgets/popup_menu.h"
|
||||||
|
#include "styles/palette.h"
|
||||||
|
#include "styles/style_widgets.h"
|
||||||
|
|
||||||
|
#include <QtWidgets/QCommonStyle>
|
||||||
|
#include <QtWidgets/QApplication>
|
||||||
|
#include <QtGui/QClipboard>
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class InputStyle final : public QCommonStyle {
|
||||||
|
public:
|
||||||
|
InputStyle() {
|
||||||
|
setParent(QCoreApplication::instance());
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawPrimitive(
|
||||||
|
PrimitiveElement element,
|
||||||
|
const QStyleOption *option,
|
||||||
|
QPainter *painter,
|
||||||
|
const QWidget *widget = nullptr) const override {
|
||||||
|
}
|
||||||
|
|
||||||
|
static InputStyle *instance() {
|
||||||
|
if (!_instance) {
|
||||||
|
if (!QGuiApplication::instance()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
_instance = new InputStyle();
|
||||||
|
}
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
~InputStyle() {
|
||||||
|
_instance = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static InputStyle *_instance;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
InputStyle *InputStyle::_instance = nullptr;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
MaskedInputField::MaskedInputField(
|
||||||
|
QWidget *parent,
|
||||||
|
const style::InputField &st,
|
||||||
|
rpl::producer<QString> placeholder,
|
||||||
|
const QString &val)
|
||||||
|
: Parent(val, parent)
|
||||||
|
, _st(st)
|
||||||
|
, _oldtext(val)
|
||||||
|
, _placeholderFull(std::move(placeholder)) {
|
||||||
|
resize(_st.width, _st.heightMin);
|
||||||
|
|
||||||
|
setFont(_st.font);
|
||||||
|
setAlignment(_st.textAlign);
|
||||||
|
|
||||||
|
_placeholderFull.value(
|
||||||
|
) | rpl::start_with_next([=](const QString &text) {
|
||||||
|
refreshPlaceholder(text);
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
style::PaletteChanged(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
updatePalette();
|
||||||
|
}, lifetime());
|
||||||
|
updatePalette();
|
||||||
|
|
||||||
|
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||||
|
|
||||||
|
connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(onTextChange(const QString&)));
|
||||||
|
connect(this, SIGNAL(cursorPositionChanged(int,int)), this, SLOT(onCursorPositionChanged(int,int)));
|
||||||
|
|
||||||
|
connect(this, SIGNAL(textEdited(const QString&)), this, SLOT(onTextEdited()));
|
||||||
|
connect(this, &MaskedInputField::selectionChanged, [] {
|
||||||
|
Integration::Instance().textActionsUpdated();
|
||||||
|
});
|
||||||
|
|
||||||
|
setStyle(InputStyle::instance());
|
||||||
|
QLineEdit::setTextMargins(0, 0, 0, 0);
|
||||||
|
setContentsMargins(_textMargins + QMargins(-2, -1, -2, -1));
|
||||||
|
setFrame(false);
|
||||||
|
|
||||||
|
setAttribute(Qt::WA_AcceptTouchEvents);
|
||||||
|
_touchTimer.setSingleShot(true);
|
||||||
|
connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer()));
|
||||||
|
|
||||||
|
setTextMargins(_st.textMargins);
|
||||||
|
|
||||||
|
startPlaceholderAnimation();
|
||||||
|
startBorderAnimation();
|
||||||
|
finishAnimating();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::updatePalette() {
|
||||||
|
auto p = palette();
|
||||||
|
p.setColor(QPalette::Text, _st.textFg->c);
|
||||||
|
p.setColor(QPalette::Highlight, st::msgInBgSelected->c);
|
||||||
|
p.setColor(QPalette::HighlightedText, st::historyTextInFgSelected->c);
|
||||||
|
setPalette(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::setCorrectedText(QString &now, int &nowCursor, const QString &newText, int newPos) {
|
||||||
|
if (newPos < 0 || newPos > newText.size()) {
|
||||||
|
newPos = newText.size();
|
||||||
|
}
|
||||||
|
auto updateText = (newText != now);
|
||||||
|
if (updateText) {
|
||||||
|
now = newText;
|
||||||
|
setText(now);
|
||||||
|
startPlaceholderAnimation();
|
||||||
|
}
|
||||||
|
auto updateCursorPosition = (newPos != nowCursor) || updateText;
|
||||||
|
if (updateCursorPosition) {
|
||||||
|
nowCursor = newPos;
|
||||||
|
setCursorPosition(nowCursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::customUpDown(bool custom) {
|
||||||
|
_customUpDown = custom;
|
||||||
|
}
|
||||||
|
|
||||||
|
int MaskedInputField::borderAnimationStart() const {
|
||||||
|
return _borderAnimationStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::setTextMargins(const QMargins &mrg) {
|
||||||
|
_textMargins = mrg;
|
||||||
|
setContentsMargins(_textMargins + QMargins(-2, -1, -2, -1));
|
||||||
|
refreshPlaceholder(_placeholderFull.current());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::onTouchTimer() {
|
||||||
|
_touchRightButton = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MaskedInputField::eventHook(QEvent *e) {
|
||||||
|
auto type = e->type();
|
||||||
|
if (type == QEvent::TouchBegin
|
||||||
|
|| type == QEvent::TouchUpdate
|
||||||
|
|| type == QEvent::TouchEnd
|
||||||
|
|| type == QEvent::TouchCancel) {
|
||||||
|
auto event = static_cast<QTouchEvent*>(e);
|
||||||
|
if (event->device()->type() == base::TouchDevice::TouchScreen) {
|
||||||
|
touchEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Parent::eventHook(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::touchEvent(QTouchEvent *e) {
|
||||||
|
switch (e->type()) {
|
||||||
|
case QEvent::TouchBegin: {
|
||||||
|
if (_touchPress || e->touchPoints().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_touchTimer.start(QApplication::startDragTime());
|
||||||
|
_touchPress = true;
|
||||||
|
_touchMove = _touchRightButton = _mousePressedInTouch = false;
|
||||||
|
_touchStart = e->touchPoints().cbegin()->screenPos().toPoint();
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case QEvent::TouchUpdate: {
|
||||||
|
if (!e->touchPoints().isEmpty()) {
|
||||||
|
touchUpdate(e->touchPoints().cbegin()->screenPos().toPoint());
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case QEvent::TouchEnd: {
|
||||||
|
touchFinish();
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case QEvent::TouchCancel: {
|
||||||
|
_touchPress = false;
|
||||||
|
_touchTimer.stop();
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::touchUpdate(QPoint globalPosition) {
|
||||||
|
if (_touchPress
|
||||||
|
&& !_touchMove
|
||||||
|
&& ((globalPosition - _touchStart).manhattanLength()
|
||||||
|
>= QApplication::startDragDistance())) {
|
||||||
|
_touchMove = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::touchFinish() {
|
||||||
|
if (!_touchPress) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto weak = MakeWeak(this);
|
||||||
|
if (!_touchMove && window()) {
|
||||||
|
QPoint mapped(mapFromGlobal(_touchStart));
|
||||||
|
|
||||||
|
if (_touchRightButton) {
|
||||||
|
QContextMenuEvent contextEvent(
|
||||||
|
QContextMenuEvent::Mouse,
|
||||||
|
mapped,
|
||||||
|
_touchStart);
|
||||||
|
contextMenuEvent(&contextEvent);
|
||||||
|
} else {
|
||||||
|
QGuiApplication::inputMethod()->show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (weak) {
|
||||||
|
_touchTimer.stop();
|
||||||
|
_touchPress
|
||||||
|
= _touchMove
|
||||||
|
= _touchRightButton
|
||||||
|
= _mousePressedInTouch = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::paintEvent(QPaintEvent *e) {
|
||||||
|
auto p = QPainter(this);
|
||||||
|
|
||||||
|
auto r = rect().intersected(e->rect());
|
||||||
|
p.fillRect(r, _st.textBg);
|
||||||
|
if (_st.border) {
|
||||||
|
p.fillRect(0, height() - _st.border, width(), _st.border, _st.borderFg->b);
|
||||||
|
}
|
||||||
|
auto errorDegree = _a_error.value(_error ? 1. : 0.);
|
||||||
|
auto focusedDegree = _a_focused.value(_focused ? 1. : 0.);
|
||||||
|
auto borderShownDegree = _a_borderShown.value(1.);
|
||||||
|
auto borderOpacity = _a_borderOpacity.value(_borderVisible ? 1. : 0.);
|
||||||
|
if (_st.borderActive && (borderOpacity > 0.)) {
|
||||||
|
auto borderStart = std::clamp(_borderAnimationStart, 0, width());
|
||||||
|
auto borderFrom = qRound(borderStart * (1. - borderShownDegree));
|
||||||
|
auto borderTo = borderStart + qRound((width() - borderStart) * borderShownDegree);
|
||||||
|
if (borderTo > borderFrom) {
|
||||||
|
auto borderFg = anim::brush(_st.borderFgActive, _st.borderFgError, errorDegree);
|
||||||
|
p.setOpacity(borderOpacity);
|
||||||
|
p.fillRect(borderFrom, height() - _st.borderActive, borderTo - borderFrom, _st.borderActive, borderFg);
|
||||||
|
p.setOpacity(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.setClipRect(r);
|
||||||
|
if (_st.placeholderScale > 0. && !_placeholderPath.isEmpty()) {
|
||||||
|
auto placeholderShiftDegree = _a_placeholderShifted.value(_placeholderShifted ? 1. : 0.);
|
||||||
|
p.save();
|
||||||
|
p.setClipRect(r);
|
||||||
|
|
||||||
|
auto placeholderTop = anim::interpolate(0, _st.placeholderShift, placeholderShiftDegree);
|
||||||
|
|
||||||
|
QRect r(rect().marginsRemoved(_textMargins + _st.placeholderMargins));
|
||||||
|
r.moveTop(r.top() + placeholderTop);
|
||||||
|
if (style::RightToLeft()) r.moveLeft(width() - r.left() - r.width());
|
||||||
|
|
||||||
|
auto placeholderScale = 1. - (1. - _st.placeholderScale) * placeholderShiftDegree;
|
||||||
|
auto placeholderFg = anim::color(_st.placeholderFg, _st.placeholderFgActive, focusedDegree);
|
||||||
|
placeholderFg = anim::color(placeholderFg, _st.placeholderFgError, errorDegree);
|
||||||
|
|
||||||
|
PainterHighQualityEnabler hq(p);
|
||||||
|
p.setPen(Qt::NoPen);
|
||||||
|
p.setBrush(placeholderFg);
|
||||||
|
p.translate(r.topLeft());
|
||||||
|
p.scale(placeholderScale, placeholderScale);
|
||||||
|
p.drawPath(_placeholderPath);
|
||||||
|
|
||||||
|
p.restore();
|
||||||
|
} else if (!_placeholder.isEmpty()) {
|
||||||
|
auto placeholderHiddenDegree = _a_placeholderShifted.value(_placeholderShifted ? 1. : 0.);
|
||||||
|
if (placeholderHiddenDegree < 1.) {
|
||||||
|
p.setOpacity(1. - placeholderHiddenDegree);
|
||||||
|
p.save();
|
||||||
|
p.setClipRect(r);
|
||||||
|
|
||||||
|
auto placeholderLeft = anim::interpolate(0, -_st.placeholderShift, placeholderHiddenDegree);
|
||||||
|
|
||||||
|
QRect r(rect().marginsRemoved(_textMargins + _st.placeholderMargins));
|
||||||
|
r.moveLeft(r.left() + placeholderLeft);
|
||||||
|
if (style::RightToLeft()) r.moveLeft(width() - r.left() - r.width());
|
||||||
|
|
||||||
|
p.setFont(_st.placeholderFont);
|
||||||
|
p.setPen(anim::pen(_st.placeholderFg, _st.placeholderFgActive, focusedDegree));
|
||||||
|
p.drawText(r, _placeholder, _st.placeholderAlign);
|
||||||
|
|
||||||
|
p.restore();
|
||||||
|
p.setOpacity(1.);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
paintAdditionalPlaceholder(p);
|
||||||
|
QLineEdit::paintEvent(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::mousePressEvent(QMouseEvent *e) {
|
||||||
|
if (_touchPress && e->button() == Qt::LeftButton) {
|
||||||
|
_mousePressedInTouch = true;
|
||||||
|
_touchStart = e->globalPos();
|
||||||
|
}
|
||||||
|
return QLineEdit::mousePressEvent(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::mouseReleaseEvent(QMouseEvent *e) {
|
||||||
|
if (_mousePressedInTouch) {
|
||||||
|
touchFinish();
|
||||||
|
}
|
||||||
|
return QLineEdit::mouseReleaseEvent(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::mouseMoveEvent(QMouseEvent *e) {
|
||||||
|
if (_mousePressedInTouch) {
|
||||||
|
touchUpdate(e->globalPos());
|
||||||
|
}
|
||||||
|
return QLineEdit::mouseMoveEvent(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString MaskedInputField::getDisplayedText() const {
|
||||||
|
auto result = getLastText();
|
||||||
|
if (!_lastPreEditText.isEmpty()) {
|
||||||
|
result = result.mid(0, _oldcursor)
|
||||||
|
+ _lastPreEditText
|
||||||
|
+ result.mid(_oldcursor);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::startBorderAnimation() {
|
||||||
|
auto borderVisible = (_error || _focused);
|
||||||
|
if (_borderVisible != borderVisible) {
|
||||||
|
_borderVisible = borderVisible;
|
||||||
|
if (_borderVisible) {
|
||||||
|
if (_a_borderOpacity.animating()) {
|
||||||
|
_a_borderOpacity.start([this] { update(); }, 0., 1., _st.duration);
|
||||||
|
} else {
|
||||||
|
_a_borderShown.start([this] { update(); }, 0., 1., _st.duration);
|
||||||
|
}
|
||||||
|
} else if (qFuzzyCompare(_a_borderShown.value(1.), 0.)) {
|
||||||
|
_a_borderShown.stop();
|
||||||
|
_a_borderOpacity.stop();
|
||||||
|
} else {
|
||||||
|
_a_borderOpacity.start([this] { update(); }, 1., 0., _st.duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::focusInEvent(QFocusEvent *e) {
|
||||||
|
_borderAnimationStart = (e->reason() == Qt::MouseFocusReason) ? mapFromGlobal(QCursor::pos()).x() : (width() / 2);
|
||||||
|
setFocused(true);
|
||||||
|
QLineEdit::focusInEvent(e);
|
||||||
|
focused();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::focusOutEvent(QFocusEvent *e) {
|
||||||
|
setFocused(false);
|
||||||
|
QLineEdit::focusOutEvent(e);
|
||||||
|
blurred();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::setFocused(bool focused) {
|
||||||
|
if (_focused != focused) {
|
||||||
|
_focused = focused;
|
||||||
|
_a_focused.start([this] { update(); }, _focused ? 0. : 1., _focused ? 1. : 0., _st.duration);
|
||||||
|
startPlaceholderAnimation();
|
||||||
|
startBorderAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::resizeEvent(QResizeEvent *e) {
|
||||||
|
refreshPlaceholder(_placeholderFull.current());
|
||||||
|
_borderAnimationStart = width() / 2;
|
||||||
|
QLineEdit::resizeEvent(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::refreshPlaceholder(const QString &text) {
|
||||||
|
const auto availableWidth = width() - _textMargins.left() - _textMargins.right() - _st.placeholderMargins.left() - _st.placeholderMargins.right() - 1;
|
||||||
|
if (_st.placeholderScale > 0.) {
|
||||||
|
auto placeholderFont = _st.placeholderFont->f;
|
||||||
|
placeholderFont.setStyleStrategy(QFont::PreferMatch);
|
||||||
|
const auto metrics = QFontMetrics(placeholderFont);
|
||||||
|
_placeholder = metrics.elidedText(text, Qt::ElideRight, availableWidth);
|
||||||
|
_placeholderPath = QPainterPath();
|
||||||
|
if (!_placeholder.isEmpty()) {
|
||||||
|
_placeholderPath.addText(0, QFontMetrics(placeholderFont).ascent(), placeholderFont, _placeholder);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_placeholder = _st.placeholderFont->elided(text, availableWidth);
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::setPlaceholder(rpl::producer<QString> placeholder) {
|
||||||
|
_placeholderFull = std::move(placeholder);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::contextMenuEvent(QContextMenuEvent *e) {
|
||||||
|
if (const auto menu = createStandardContextMenu()) {
|
||||||
|
_contextMenu = base::make_unique_q<PopupMenu>(this, menu);
|
||||||
|
_contextMenu->popup(e->globalPos());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::inputMethodEvent(QInputMethodEvent *e) {
|
||||||
|
QLineEdit::inputMethodEvent(e);
|
||||||
|
_lastPreEditText = e->preeditString();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::showError() {
|
||||||
|
showErrorNoFocus();
|
||||||
|
if (!hasFocus()) {
|
||||||
|
setFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::showErrorNoFocus() {
|
||||||
|
setErrorShown(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::hideError() {
|
||||||
|
setErrorShown(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::setErrorShown(bool error) {
|
||||||
|
if (_error != error) {
|
||||||
|
_error = error;
|
||||||
|
_a_error.start([this] { update(); }, _error ? 0. : 1., _error ? 1. : 0., _st.duration);
|
||||||
|
startBorderAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize MaskedInputField::sizeHint() const {
|
||||||
|
return geometry().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize MaskedInputField::minimumSizeHint() const {
|
||||||
|
return geometry().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::setDisplayFocused(bool focused) {
|
||||||
|
setFocused(focused);
|
||||||
|
finishAnimating();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::finishAnimating() {
|
||||||
|
_a_focused.stop();
|
||||||
|
_a_error.stop();
|
||||||
|
_a_placeholderShifted.stop();
|
||||||
|
_a_borderShown.stop();
|
||||||
|
_a_borderOpacity.stop();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::setPlaceholderHidden(bool forcePlaceholderHidden) {
|
||||||
|
_forcePlaceholderHidden = forcePlaceholderHidden;
|
||||||
|
startPlaceholderAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::startPlaceholderAnimation() {
|
||||||
|
auto placeholderShifted = _forcePlaceholderHidden || (_focused && _st.placeholderScale > 0.) || !getLastText().isEmpty();
|
||||||
|
if (_placeholderShifted != placeholderShifted) {
|
||||||
|
_placeholderShifted = placeholderShifted;
|
||||||
|
_a_placeholderShifted.start([this] { update(); }, _placeholderShifted ? 0. : 1., _placeholderShifted ? 1. : 0., _st.duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QRect MaskedInputField::placeholderRect() const {
|
||||||
|
return rect().marginsRemoved(_textMargins + _st.placeholderMargins);
|
||||||
|
}
|
||||||
|
|
||||||
|
style::font MaskedInputField::phFont() {
|
||||||
|
return _st.font;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::placeholderAdditionalPrepare(QPainter &p) {
|
||||||
|
p.setFont(_st.font);
|
||||||
|
p.setPen(_st.placeholderFg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::keyPressEvent(QKeyEvent *e) {
|
||||||
|
QString wasText(_oldtext);
|
||||||
|
int32 wasCursor(_oldcursor);
|
||||||
|
|
||||||
|
if (_customUpDown && (e->key() == Qt::Key_Up || e->key() == Qt::Key_Down || e->key() == Qt::Key_PageUp || e->key() == Qt::Key_PageDown)) {
|
||||||
|
e->ignore();
|
||||||
|
} else {
|
||||||
|
QLineEdit::keyPressEvent(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto newText = text();
|
||||||
|
auto newCursor = cursorPosition();
|
||||||
|
if (wasText == newText && wasCursor == newCursor) { // call correct manually
|
||||||
|
correctValue(wasText, wasCursor, newText, newCursor);
|
||||||
|
_oldtext = newText;
|
||||||
|
_oldcursor = newCursor;
|
||||||
|
if (wasText != _oldtext) changed();
|
||||||
|
startPlaceholderAnimation();
|
||||||
|
}
|
||||||
|
if (e->key() == Qt::Key_Escape) {
|
||||||
|
e->ignore();
|
||||||
|
cancelled();
|
||||||
|
} else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) {
|
||||||
|
submitted(e->modifiers());
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
} else if (e->key() == Qt::Key_E && e->modifiers().testFlag(Qt::ControlModifier)) {
|
||||||
|
auto selected = selectedText();
|
||||||
|
if (!selected.isEmpty() && echoMode() == QLineEdit::Normal) {
|
||||||
|
QGuiApplication::clipboard()->setText(selected, QClipboard::FindBuffer);
|
||||||
|
}
|
||||||
|
#endif // Q_OS_MAC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::onTextEdited() {
|
||||||
|
QString wasText(_oldtext), newText(text());
|
||||||
|
int32 wasCursor(_oldcursor), newCursor(cursorPosition());
|
||||||
|
|
||||||
|
correctValue(wasText, wasCursor, newText, newCursor);
|
||||||
|
_oldtext = newText;
|
||||||
|
_oldcursor = newCursor;
|
||||||
|
if (wasText != _oldtext) changed();
|
||||||
|
startPlaceholderAnimation();
|
||||||
|
|
||||||
|
Integration::Instance().textActionsUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::onTextChange(const QString &text) {
|
||||||
|
_oldtext = QLineEdit::text();
|
||||||
|
setErrorShown(false);
|
||||||
|
Integration::Instance().textActionsUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskedInputField::onCursorPositionChanged(int oldPosition, int position) {
|
||||||
|
_oldcursor = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Ui
|
||||||
174
ui/widgets/fields/masked_input_field.h
Normal file
174
ui/widgets/fields/masked_input_field.h
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
// This file is part of Desktop App Toolkit,
|
||||||
|
// a set of libraries for developing nice desktop applications.
|
||||||
|
//
|
||||||
|
// For license and copyright information please follow this link:
|
||||||
|
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/rp_widget.h"
|
||||||
|
#include "ui/effects/animations.h"
|
||||||
|
|
||||||
|
#include <QtWidgets/QLineEdit>
|
||||||
|
#include <QtCore/QTimer>
|
||||||
|
|
||||||
|
namespace style {
|
||||||
|
struct InputField;
|
||||||
|
} // namespace style
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
|
||||||
|
class PopupMenu;
|
||||||
|
|
||||||
|
class MaskedInputField : public RpWidgetBase<QLineEdit> {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
using Parent = RpWidgetBase<QLineEdit>;
|
||||||
|
public:
|
||||||
|
MaskedInputField(
|
||||||
|
QWidget *parent,
|
||||||
|
const style::InputField &st,
|
||||||
|
rpl::producer<QString> placeholder = nullptr,
|
||||||
|
const QString &val = QString());
|
||||||
|
|
||||||
|
void showError();
|
||||||
|
void showErrorNoFocus();
|
||||||
|
void hideError();
|
||||||
|
|
||||||
|
QSize sizeHint() const override;
|
||||||
|
QSize minimumSizeHint() const override;
|
||||||
|
|
||||||
|
void customUpDown(bool isCustom);
|
||||||
|
int borderAnimationStart() const;
|
||||||
|
|
||||||
|
const QString &getLastText() const {
|
||||||
|
return _oldtext;
|
||||||
|
}
|
||||||
|
void setPlaceholder(rpl::producer<QString> placeholder);
|
||||||
|
void setPlaceholderHidden(bool forcePlaceholderHidden);
|
||||||
|
void setDisplayFocused(bool focused);
|
||||||
|
void finishAnimating();
|
||||||
|
void setFocusFast() {
|
||||||
|
setDisplayFocused(true);
|
||||||
|
setFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setText(const QString &text) {
|
||||||
|
QLineEdit::setText(text);
|
||||||
|
startPlaceholderAnimation();
|
||||||
|
}
|
||||||
|
void clear() {
|
||||||
|
QLineEdit::clear();
|
||||||
|
startPlaceholderAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
void onTextChange(const QString &text);
|
||||||
|
void onCursorPositionChanged(int oldPosition, int position);
|
||||||
|
|
||||||
|
void onTextEdited();
|
||||||
|
|
||||||
|
void onTouchTimer();
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void changed();
|
||||||
|
void cancelled();
|
||||||
|
void submitted(Qt::KeyboardModifiers);
|
||||||
|
void focused();
|
||||||
|
void blurred();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QString getDisplayedText() const;
|
||||||
|
void startBorderAnimation();
|
||||||
|
void startPlaceholderAnimation();
|
||||||
|
|
||||||
|
bool eventHook(QEvent *e) override;
|
||||||
|
void touchEvent(QTouchEvent *e);
|
||||||
|
void paintEvent(QPaintEvent *e) override;
|
||||||
|
void focusInEvent(QFocusEvent *e) override;
|
||||||
|
void focusOutEvent(QFocusEvent *e) override;
|
||||||
|
void keyPressEvent(QKeyEvent *e) override;
|
||||||
|
void resizeEvent(QResizeEvent *e) override;
|
||||||
|
void contextMenuEvent(QContextMenuEvent *e) override;
|
||||||
|
void inputMethodEvent(QInputMethodEvent *e) override;
|
||||||
|
|
||||||
|
void mousePressEvent(QMouseEvent *e) override;
|
||||||
|
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||||
|
void mouseMoveEvent(QMouseEvent *e) override;
|
||||||
|
|
||||||
|
virtual void correctValue(
|
||||||
|
const QString &was,
|
||||||
|
int wasCursor,
|
||||||
|
QString &now,
|
||||||
|
int &nowCursor) {
|
||||||
|
}
|
||||||
|
void setCorrectedText(
|
||||||
|
QString &now,
|
||||||
|
int &nowCursor,
|
||||||
|
const QString &newText,
|
||||||
|
int newPos);
|
||||||
|
|
||||||
|
virtual void paintAdditionalPlaceholder(QPainter &p) {
|
||||||
|
}
|
||||||
|
|
||||||
|
style::font phFont();
|
||||||
|
|
||||||
|
void placeholderAdditionalPrepare(QPainter &p);
|
||||||
|
QRect placeholderRect() const;
|
||||||
|
|
||||||
|
void setTextMargins(const QMargins &mrg);
|
||||||
|
const style::InputField &_st;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updatePalette();
|
||||||
|
void refreshPlaceholder(const QString &text);
|
||||||
|
void setErrorShown(bool error);
|
||||||
|
|
||||||
|
void touchUpdate(QPoint globalPosition);
|
||||||
|
void touchFinish();
|
||||||
|
|
||||||
|
void setFocused(bool focused);
|
||||||
|
|
||||||
|
int _maxLength = -1;
|
||||||
|
bool _forcePlaceholderHidden = false;
|
||||||
|
|
||||||
|
QString _oldtext;
|
||||||
|
int _oldcursor = 0;
|
||||||
|
QString _lastPreEditText;
|
||||||
|
|
||||||
|
bool _undoAvailable = false;
|
||||||
|
bool _redoAvailable = false;
|
||||||
|
|
||||||
|
bool _customUpDown = false;
|
||||||
|
|
||||||
|
rpl::variable<QString> _placeholderFull;
|
||||||
|
QString _placeholder;
|
||||||
|
Animations::Simple _a_placeholderShifted;
|
||||||
|
bool _placeholderShifted = false;
|
||||||
|
QPainterPath _placeholderPath;
|
||||||
|
|
||||||
|
Animations::Simple _a_borderShown;
|
||||||
|
int _borderAnimationStart = 0;
|
||||||
|
Animations::Simple _a_borderOpacity;
|
||||||
|
bool _borderVisible = false;
|
||||||
|
|
||||||
|
Animations::Simple _a_focused;
|
||||||
|
Animations::Simple _a_error;
|
||||||
|
|
||||||
|
bool _focused = false;
|
||||||
|
bool _error = false;
|
||||||
|
|
||||||
|
style::margins _textMargins;
|
||||||
|
|
||||||
|
QTimer _touchTimer;
|
||||||
|
bool _touchPress = false;
|
||||||
|
bool _touchRightButton = false;
|
||||||
|
bool _touchMove = false;
|
||||||
|
bool _mousePressedInTouch = false;
|
||||||
|
QPoint _touchStart;
|
||||||
|
|
||||||
|
base::unique_qptr<PopupMenu> _contextMenu;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Ui
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
//
|
//
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ui/widgets/input_fields.h"
|
#include "ui/widgets/fields/masked_input_field.h"
|
||||||
|
|
||||||
namespace style {
|
namespace style {
|
||||||
struct InputField;
|
struct InputField;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
//
|
//
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ui/widgets/input_fields.h"
|
#include "ui/widgets/fields/masked_input_field.h"
|
||||||
|
|
||||||
namespace style {
|
namespace style {
|
||||||
struct InputField;
|
struct InputField;
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
//
|
//
|
||||||
#include "ui/widgets/fields/time_part_input.h"
|
#include "ui/widgets/fields/time_part_input.h"
|
||||||
|
|
||||||
|
#include "base/qt/qt_string_view.h"
|
||||||
#include "ui/ui_utility.h" // WheelDirection
|
#include "ui/ui_utility.h" // WheelDirection
|
||||||
|
|
||||||
#include <QtCore/QRegularExpression>
|
#include <QtCore/QRegularExpression>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
//
|
//
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ui/widgets/input_fields.h"
|
#include "ui/widgets/fields/masked_input_field.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -659,36 +659,6 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class InputStyle : public QCommonStyle {
|
|
||||||
public:
|
|
||||||
InputStyle() {
|
|
||||||
setParent(QCoreApplication::instance());
|
|
||||||
}
|
|
||||||
|
|
||||||
void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = nullptr) const override {
|
|
||||||
}
|
|
||||||
|
|
||||||
static InputStyle *instance() {
|
|
||||||
if (!_instance) {
|
|
||||||
if (!QGuiApplication::instance()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
_instance = new InputStyle();
|
|
||||||
}
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
~InputStyle() {
|
|
||||||
_instance = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
static InputStyle *_instance;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
InputStyle *InputStyle::_instance = nullptr;
|
|
||||||
|
|
||||||
template <typename Iterator>
|
template <typename Iterator>
|
||||||
QString AccumulateText(Iterator begin, Iterator end) {
|
QString AccumulateText(Iterator begin, Iterator end) {
|
||||||
auto result = QString();
|
auto result = QString();
|
||||||
|
|
@ -3875,478 +3845,4 @@ void InputField::setErrorShown(bool error) {
|
||||||
|
|
||||||
InputField::~InputField() = default;
|
InputField::~InputField() = default;
|
||||||
|
|
||||||
MaskedInputField::MaskedInputField(
|
|
||||||
QWidget *parent,
|
|
||||||
const style::InputField &st,
|
|
||||||
rpl::producer<QString> placeholder,
|
|
||||||
const QString &val)
|
|
||||||
: Parent(val, parent)
|
|
||||||
, _st(st)
|
|
||||||
, _oldtext(val)
|
|
||||||
, _placeholderFull(std::move(placeholder)) {
|
|
||||||
resize(_st.width, _st.heightMin);
|
|
||||||
|
|
||||||
setFont(_st.font);
|
|
||||||
setAlignment(_st.textAlign);
|
|
||||||
|
|
||||||
_placeholderFull.value(
|
|
||||||
) | rpl::start_with_next([=](const QString &text) {
|
|
||||||
refreshPlaceholder(text);
|
|
||||||
}, lifetime());
|
|
||||||
|
|
||||||
style::PaletteChanged(
|
|
||||||
) | rpl::start_with_next([=] {
|
|
||||||
updatePalette();
|
|
||||||
}, lifetime());
|
|
||||||
updatePalette();
|
|
||||||
|
|
||||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
|
||||||
|
|
||||||
connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(onTextChange(const QString&)));
|
|
||||||
connect(this, SIGNAL(cursorPositionChanged(int,int)), this, SLOT(onCursorPositionChanged(int,int)));
|
|
||||||
|
|
||||||
connect(this, SIGNAL(textEdited(const QString&)), this, SLOT(onTextEdited()));
|
|
||||||
connect(this, &MaskedInputField::selectionChanged, [] {
|
|
||||||
Integration::Instance().textActionsUpdated();
|
|
||||||
});
|
|
||||||
|
|
||||||
setStyle(InputStyle::instance());
|
|
||||||
QLineEdit::setTextMargins(0, 0, 0, 0);
|
|
||||||
setContentsMargins(_textMargins + QMargins(-2, -1, -2, -1));
|
|
||||||
setFrame(false);
|
|
||||||
|
|
||||||
setAttribute(Qt::WA_AcceptTouchEvents);
|
|
||||||
_touchTimer.setSingleShot(true);
|
|
||||||
connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer()));
|
|
||||||
|
|
||||||
setTextMargins(_st.textMargins);
|
|
||||||
|
|
||||||
startPlaceholderAnimation();
|
|
||||||
startBorderAnimation();
|
|
||||||
finishAnimating();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::updatePalette() {
|
|
||||||
auto p = palette();
|
|
||||||
p.setColor(QPalette::Text, _st.textFg->c);
|
|
||||||
p.setColor(QPalette::Highlight, st::msgInBgSelected->c);
|
|
||||||
p.setColor(QPalette::HighlightedText, st::historyTextInFgSelected->c);
|
|
||||||
setPalette(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::setCorrectedText(QString &now, int &nowCursor, const QString &newText, int newPos) {
|
|
||||||
if (newPos < 0 || newPos > newText.size()) {
|
|
||||||
newPos = newText.size();
|
|
||||||
}
|
|
||||||
auto updateText = (newText != now);
|
|
||||||
if (updateText) {
|
|
||||||
now = newText;
|
|
||||||
setText(now);
|
|
||||||
startPlaceholderAnimation();
|
|
||||||
}
|
|
||||||
auto updateCursorPosition = (newPos != nowCursor) || updateText;
|
|
||||||
if (updateCursorPosition) {
|
|
||||||
nowCursor = newPos;
|
|
||||||
setCursorPosition(nowCursor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::customUpDown(bool custom) {
|
|
||||||
_customUpDown = custom;
|
|
||||||
}
|
|
||||||
|
|
||||||
int MaskedInputField::borderAnimationStart() const {
|
|
||||||
return _borderAnimationStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::setTextMargins(const QMargins &mrg) {
|
|
||||||
_textMargins = mrg;
|
|
||||||
setContentsMargins(_textMargins + QMargins(-2, -1, -2, -1));
|
|
||||||
refreshPlaceholder(_placeholderFull.current());
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::onTouchTimer() {
|
|
||||||
_touchRightButton = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MaskedInputField::eventHook(QEvent *e) {
|
|
||||||
auto type = e->type();
|
|
||||||
if (type == QEvent::TouchBegin
|
|
||||||
|| type == QEvent::TouchUpdate
|
|
||||||
|| type == QEvent::TouchEnd
|
|
||||||
|| type == QEvent::TouchCancel) {
|
|
||||||
auto event = static_cast<QTouchEvent*>(e);
|
|
||||||
if (event->device()->type() == base::TouchDevice::TouchScreen) {
|
|
||||||
touchEvent(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Parent::eventHook(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::touchEvent(QTouchEvent *e) {
|
|
||||||
switch (e->type()) {
|
|
||||||
case QEvent::TouchBegin: {
|
|
||||||
if (_touchPress || e->touchPoints().isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_touchTimer.start(QApplication::startDragTime());
|
|
||||||
_touchPress = true;
|
|
||||||
_touchMove = _touchRightButton = _mousePressedInTouch = false;
|
|
||||||
_touchStart = e->touchPoints().cbegin()->screenPos().toPoint();
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case QEvent::TouchUpdate: {
|
|
||||||
if (!e->touchPoints().isEmpty()) {
|
|
||||||
touchUpdate(e->touchPoints().cbegin()->screenPos().toPoint());
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case QEvent::TouchEnd: {
|
|
||||||
touchFinish();
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case QEvent::TouchCancel: {
|
|
||||||
_touchPress = false;
|
|
||||||
_touchTimer.stop();
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::touchUpdate(QPoint globalPosition) {
|
|
||||||
if (_touchPress
|
|
||||||
&& !_touchMove
|
|
||||||
&& ((globalPosition - _touchStart).manhattanLength()
|
|
||||||
>= QApplication::startDragDistance())) {
|
|
||||||
_touchMove = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::touchFinish() {
|
|
||||||
if (!_touchPress) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto weak = MakeWeak(this);
|
|
||||||
if (!_touchMove && window()) {
|
|
||||||
QPoint mapped(mapFromGlobal(_touchStart));
|
|
||||||
|
|
||||||
if (_touchRightButton) {
|
|
||||||
QContextMenuEvent contextEvent(
|
|
||||||
QContextMenuEvent::Mouse,
|
|
||||||
mapped,
|
|
||||||
_touchStart);
|
|
||||||
contextMenuEvent(&contextEvent);
|
|
||||||
} else {
|
|
||||||
QGuiApplication::inputMethod()->show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (weak) {
|
|
||||||
_touchTimer.stop();
|
|
||||||
_touchPress
|
|
||||||
= _touchMove
|
|
||||||
= _touchRightButton
|
|
||||||
= _mousePressedInTouch = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::paintEvent(QPaintEvent *e) {
|
|
||||||
auto p = QPainter(this);
|
|
||||||
|
|
||||||
auto r = rect().intersected(e->rect());
|
|
||||||
p.fillRect(r, _st.textBg);
|
|
||||||
if (_st.border) {
|
|
||||||
p.fillRect(0, height() - _st.border, width(), _st.border, _st.borderFg->b);
|
|
||||||
}
|
|
||||||
auto errorDegree = _a_error.value(_error ? 1. : 0.);
|
|
||||||
auto focusedDegree = _a_focused.value(_focused ? 1. : 0.);
|
|
||||||
auto borderShownDegree = _a_borderShown.value(1.);
|
|
||||||
auto borderOpacity = _a_borderOpacity.value(_borderVisible ? 1. : 0.);
|
|
||||||
if (_st.borderActive && (borderOpacity > 0.)) {
|
|
||||||
auto borderStart = std::clamp(_borderAnimationStart, 0, width());
|
|
||||||
auto borderFrom = qRound(borderStart * (1. - borderShownDegree));
|
|
||||||
auto borderTo = borderStart + qRound((width() - borderStart) * borderShownDegree);
|
|
||||||
if (borderTo > borderFrom) {
|
|
||||||
auto borderFg = anim::brush(_st.borderFgActive, _st.borderFgError, errorDegree);
|
|
||||||
p.setOpacity(borderOpacity);
|
|
||||||
p.fillRect(borderFrom, height() - _st.borderActive, borderTo - borderFrom, _st.borderActive, borderFg);
|
|
||||||
p.setOpacity(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p.setClipRect(r);
|
|
||||||
if (_st.placeholderScale > 0. && !_placeholderPath.isEmpty()) {
|
|
||||||
auto placeholderShiftDegree = _a_placeholderShifted.value(_placeholderShifted ? 1. : 0.);
|
|
||||||
p.save();
|
|
||||||
p.setClipRect(r);
|
|
||||||
|
|
||||||
auto placeholderTop = anim::interpolate(0, _st.placeholderShift, placeholderShiftDegree);
|
|
||||||
|
|
||||||
QRect r(rect().marginsRemoved(_textMargins + _st.placeholderMargins));
|
|
||||||
r.moveTop(r.top() + placeholderTop);
|
|
||||||
if (style::RightToLeft()) r.moveLeft(width() - r.left() - r.width());
|
|
||||||
|
|
||||||
auto placeholderScale = 1. - (1. - _st.placeholderScale) * placeholderShiftDegree;
|
|
||||||
auto placeholderFg = anim::color(_st.placeholderFg, _st.placeholderFgActive, focusedDegree);
|
|
||||||
placeholderFg = anim::color(placeholderFg, _st.placeholderFgError, errorDegree);
|
|
||||||
|
|
||||||
PainterHighQualityEnabler hq(p);
|
|
||||||
p.setPen(Qt::NoPen);
|
|
||||||
p.setBrush(placeholderFg);
|
|
||||||
p.translate(r.topLeft());
|
|
||||||
p.scale(placeholderScale, placeholderScale);
|
|
||||||
p.drawPath(_placeholderPath);
|
|
||||||
|
|
||||||
p.restore();
|
|
||||||
} else if (!_placeholder.isEmpty()) {
|
|
||||||
auto placeholderHiddenDegree = _a_placeholderShifted.value(_placeholderShifted ? 1. : 0.);
|
|
||||||
if (placeholderHiddenDegree < 1.) {
|
|
||||||
p.setOpacity(1. - placeholderHiddenDegree);
|
|
||||||
p.save();
|
|
||||||
p.setClipRect(r);
|
|
||||||
|
|
||||||
auto placeholderLeft = anim::interpolate(0, -_st.placeholderShift, placeholderHiddenDegree);
|
|
||||||
|
|
||||||
QRect r(rect().marginsRemoved(_textMargins + _st.placeholderMargins));
|
|
||||||
r.moveLeft(r.left() + placeholderLeft);
|
|
||||||
if (style::RightToLeft()) r.moveLeft(width() - r.left() - r.width());
|
|
||||||
|
|
||||||
p.setFont(_st.placeholderFont);
|
|
||||||
p.setPen(anim::pen(_st.placeholderFg, _st.placeholderFgActive, focusedDegree));
|
|
||||||
p.drawText(r, _placeholder, _st.placeholderAlign);
|
|
||||||
|
|
||||||
p.restore();
|
|
||||||
p.setOpacity(1.);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
paintAdditionalPlaceholder(p);
|
|
||||||
QLineEdit::paintEvent(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::mousePressEvent(QMouseEvent *e) {
|
|
||||||
if (_touchPress && e->button() == Qt::LeftButton) {
|
|
||||||
_mousePressedInTouch = true;
|
|
||||||
_touchStart = e->globalPos();
|
|
||||||
}
|
|
||||||
return QLineEdit::mousePressEvent(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::mouseReleaseEvent(QMouseEvent *e) {
|
|
||||||
if (_mousePressedInTouch) {
|
|
||||||
touchFinish();
|
|
||||||
}
|
|
||||||
return QLineEdit::mouseReleaseEvent(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::mouseMoveEvent(QMouseEvent *e) {
|
|
||||||
if (_mousePressedInTouch) {
|
|
||||||
touchUpdate(e->globalPos());
|
|
||||||
}
|
|
||||||
return QLineEdit::mouseMoveEvent(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::startBorderAnimation() {
|
|
||||||
auto borderVisible = (_error || _focused);
|
|
||||||
if (_borderVisible != borderVisible) {
|
|
||||||
_borderVisible = borderVisible;
|
|
||||||
if (_borderVisible) {
|
|
||||||
if (_a_borderOpacity.animating()) {
|
|
||||||
_a_borderOpacity.start([this] { update(); }, 0., 1., _st.duration);
|
|
||||||
} else {
|
|
||||||
_a_borderShown.start([this] { update(); }, 0., 1., _st.duration);
|
|
||||||
}
|
|
||||||
} else if (qFuzzyCompare(_a_borderShown.value(1.), 0.)) {
|
|
||||||
_a_borderShown.stop();
|
|
||||||
_a_borderOpacity.stop();
|
|
||||||
} else {
|
|
||||||
_a_borderOpacity.start([this] { update(); }, 1., 0., _st.duration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::focusInEvent(QFocusEvent *e) {
|
|
||||||
_borderAnimationStart = (e->reason() == Qt::MouseFocusReason) ? mapFromGlobal(QCursor::pos()).x() : (width() / 2);
|
|
||||||
setFocused(true);
|
|
||||||
QLineEdit::focusInEvent(e);
|
|
||||||
focused();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::focusOutEvent(QFocusEvent *e) {
|
|
||||||
setFocused(false);
|
|
||||||
QLineEdit::focusOutEvent(e);
|
|
||||||
blurred();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::setFocused(bool focused) {
|
|
||||||
if (_focused != focused) {
|
|
||||||
_focused = focused;
|
|
||||||
_a_focused.start([this] { update(); }, _focused ? 0. : 1., _focused ? 1. : 0., _st.duration);
|
|
||||||
startPlaceholderAnimation();
|
|
||||||
startBorderAnimation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::resizeEvent(QResizeEvent *e) {
|
|
||||||
refreshPlaceholder(_placeholderFull.current());
|
|
||||||
_borderAnimationStart = width() / 2;
|
|
||||||
QLineEdit::resizeEvent(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::refreshPlaceholder(const QString &text) {
|
|
||||||
const auto availableWidth = width() - _textMargins.left() - _textMargins.right() - _st.placeholderMargins.left() - _st.placeholderMargins.right() - 1;
|
|
||||||
if (_st.placeholderScale > 0.) {
|
|
||||||
auto placeholderFont = _st.placeholderFont->f;
|
|
||||||
placeholderFont.setStyleStrategy(QFont::PreferMatch);
|
|
||||||
const auto metrics = QFontMetrics(placeholderFont);
|
|
||||||
_placeholder = metrics.elidedText(text, Qt::ElideRight, availableWidth);
|
|
||||||
_placeholderPath = QPainterPath();
|
|
||||||
if (!_placeholder.isEmpty()) {
|
|
||||||
_placeholderPath.addText(0, QFontMetrics(placeholderFont).ascent(), placeholderFont, _placeholder);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_placeholder = _st.placeholderFont->elided(text, availableWidth);
|
|
||||||
}
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::setPlaceholder(rpl::producer<QString> placeholder) {
|
|
||||||
_placeholderFull = std::move(placeholder);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::contextMenuEvent(QContextMenuEvent *e) {
|
|
||||||
if (const auto menu = createStandardContextMenu()) {
|
|
||||||
_contextMenu = base::make_unique_q<PopupMenu>(this, menu);
|
|
||||||
_contextMenu->popup(e->globalPos());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::inputMethodEvent(QInputMethodEvent *e) {
|
|
||||||
QLineEdit::inputMethodEvent(e);
|
|
||||||
_lastPreEditText = e->preeditString();
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::showError() {
|
|
||||||
showErrorNoFocus();
|
|
||||||
if (!hasFocus()) {
|
|
||||||
setFocus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::showErrorNoFocus() {
|
|
||||||
setErrorShown(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::hideError() {
|
|
||||||
setErrorShown(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::setErrorShown(bool error) {
|
|
||||||
if (_error != error) {
|
|
||||||
_error = error;
|
|
||||||
_a_error.start([this] { update(); }, _error ? 0. : 1., _error ? 1. : 0., _st.duration);
|
|
||||||
startBorderAnimation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QSize MaskedInputField::sizeHint() const {
|
|
||||||
return geometry().size();
|
|
||||||
}
|
|
||||||
|
|
||||||
QSize MaskedInputField::minimumSizeHint() const {
|
|
||||||
return geometry().size();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::setDisplayFocused(bool focused) {
|
|
||||||
setFocused(focused);
|
|
||||||
finishAnimating();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::finishAnimating() {
|
|
||||||
_a_focused.stop();
|
|
||||||
_a_error.stop();
|
|
||||||
_a_placeholderShifted.stop();
|
|
||||||
_a_borderShown.stop();
|
|
||||||
_a_borderOpacity.stop();
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::setPlaceholderHidden(bool forcePlaceholderHidden) {
|
|
||||||
_forcePlaceholderHidden = forcePlaceholderHidden;
|
|
||||||
startPlaceholderAnimation();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::startPlaceholderAnimation() {
|
|
||||||
auto placeholderShifted = _forcePlaceholderHidden || (_focused && _st.placeholderScale > 0.) || !getLastText().isEmpty();
|
|
||||||
if (_placeholderShifted != placeholderShifted) {
|
|
||||||
_placeholderShifted = placeholderShifted;
|
|
||||||
_a_placeholderShifted.start([this] { update(); }, _placeholderShifted ? 0. : 1., _placeholderShifted ? 1. : 0., _st.duration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QRect MaskedInputField::placeholderRect() const {
|
|
||||||
return rect().marginsRemoved(_textMargins + _st.placeholderMargins);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::placeholderAdditionalPrepare(QPainter &p) {
|
|
||||||
p.setFont(_st.font);
|
|
||||||
p.setPen(_st.placeholderFg);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::keyPressEvent(QKeyEvent *e) {
|
|
||||||
QString wasText(_oldtext);
|
|
||||||
int32 wasCursor(_oldcursor);
|
|
||||||
|
|
||||||
if (_customUpDown && (e->key() == Qt::Key_Up || e->key() == Qt::Key_Down || e->key() == Qt::Key_PageUp || e->key() == Qt::Key_PageDown)) {
|
|
||||||
e->ignore();
|
|
||||||
} else {
|
|
||||||
QLineEdit::keyPressEvent(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto newText = text();
|
|
||||||
auto newCursor = cursorPosition();
|
|
||||||
if (wasText == newText && wasCursor == newCursor) { // call correct manually
|
|
||||||
correctValue(wasText, wasCursor, newText, newCursor);
|
|
||||||
_oldtext = newText;
|
|
||||||
_oldcursor = newCursor;
|
|
||||||
if (wasText != _oldtext) changed();
|
|
||||||
startPlaceholderAnimation();
|
|
||||||
}
|
|
||||||
if (e->key() == Qt::Key_Escape) {
|
|
||||||
e->ignore();
|
|
||||||
cancelled();
|
|
||||||
} else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) {
|
|
||||||
submitted(e->modifiers());
|
|
||||||
#ifdef Q_OS_MAC
|
|
||||||
} else if (e->key() == Qt::Key_E && e->modifiers().testFlag(Qt::ControlModifier)) {
|
|
||||||
auto selected = selectedText();
|
|
||||||
if (!selected.isEmpty() && echoMode() == QLineEdit::Normal) {
|
|
||||||
QGuiApplication::clipboard()->setText(selected, QClipboard::FindBuffer);
|
|
||||||
}
|
|
||||||
#endif // Q_OS_MAC
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::onTextEdited() {
|
|
||||||
QString wasText(_oldtext), newText(text());
|
|
||||||
int32 wasCursor(_oldcursor), newCursor(cursorPosition());
|
|
||||||
|
|
||||||
correctValue(wasText, wasCursor, newText, newCursor);
|
|
||||||
_oldtext = newText;
|
|
||||||
_oldcursor = newCursor;
|
|
||||||
if (wasText != _oldtext) changed();
|
|
||||||
startPlaceholderAnimation();
|
|
||||||
|
|
||||||
Integration::Instance().textActionsUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::onTextChange(const QString &text) {
|
|
||||||
_oldtext = QLineEdit::text();
|
|
||||||
setErrorShown(false);
|
|
||||||
Integration::Instance().textActionsUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskedInputField::onCursorPositionChanged(int oldPosition, int position) {
|
|
||||||
_oldcursor = position;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
||||||
|
|
@ -563,159 +563,4 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class MaskedInputField : public RpWidgetBase<QLineEdit> {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
using Parent = RpWidgetBase<QLineEdit>;
|
|
||||||
public:
|
|
||||||
MaskedInputField(
|
|
||||||
QWidget *parent,
|
|
||||||
const style::InputField &st,
|
|
||||||
rpl::producer<QString> placeholder = nullptr,
|
|
||||||
const QString &val = QString());
|
|
||||||
|
|
||||||
void showError();
|
|
||||||
void showErrorNoFocus();
|
|
||||||
void hideError();
|
|
||||||
|
|
||||||
QSize sizeHint() const override;
|
|
||||||
QSize minimumSizeHint() const override;
|
|
||||||
|
|
||||||
void customUpDown(bool isCustom);
|
|
||||||
int borderAnimationStart() const;
|
|
||||||
|
|
||||||
const QString &getLastText() const {
|
|
||||||
return _oldtext;
|
|
||||||
}
|
|
||||||
void setPlaceholder(rpl::producer<QString> placeholder);
|
|
||||||
void setPlaceholderHidden(bool forcePlaceholderHidden);
|
|
||||||
void setDisplayFocused(bool focused);
|
|
||||||
void finishAnimating();
|
|
||||||
void setFocusFast() {
|
|
||||||
setDisplayFocused(true);
|
|
||||||
setFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setText(const QString &text) {
|
|
||||||
QLineEdit::setText(text);
|
|
||||||
startPlaceholderAnimation();
|
|
||||||
}
|
|
||||||
void clear() {
|
|
||||||
QLineEdit::clear();
|
|
||||||
startPlaceholderAnimation();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Q_SLOTS:
|
|
||||||
void onTextChange(const QString &text);
|
|
||||||
void onCursorPositionChanged(int oldPosition, int position);
|
|
||||||
|
|
||||||
void onTextEdited();
|
|
||||||
|
|
||||||
void onTouchTimer();
|
|
||||||
|
|
||||||
Q_SIGNALS:
|
|
||||||
void changed();
|
|
||||||
void cancelled();
|
|
||||||
void submitted(Qt::KeyboardModifiers);
|
|
||||||
void focused();
|
|
||||||
void blurred();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
QString getDisplayedText() const {
|
|
||||||
auto result = getLastText();
|
|
||||||
if (!_lastPreEditText.isEmpty()) {
|
|
||||||
result = result.mid(0, _oldcursor) + _lastPreEditText + result.mid(_oldcursor);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
void startBorderAnimation();
|
|
||||||
void startPlaceholderAnimation();
|
|
||||||
|
|
||||||
bool eventHook(QEvent *e) override;
|
|
||||||
void touchEvent(QTouchEvent *e);
|
|
||||||
void paintEvent(QPaintEvent *e) override;
|
|
||||||
void focusInEvent(QFocusEvent *e) override;
|
|
||||||
void focusOutEvent(QFocusEvent *e) override;
|
|
||||||
void keyPressEvent(QKeyEvent *e) override;
|
|
||||||
void resizeEvent(QResizeEvent *e) override;
|
|
||||||
void contextMenuEvent(QContextMenuEvent *e) override;
|
|
||||||
void inputMethodEvent(QInputMethodEvent *e) override;
|
|
||||||
|
|
||||||
void mousePressEvent(QMouseEvent *e) override;
|
|
||||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
|
||||||
void mouseMoveEvent(QMouseEvent *e) override;
|
|
||||||
|
|
||||||
virtual void correctValue(
|
|
||||||
const QString &was,
|
|
||||||
int wasCursor,
|
|
||||||
QString &now,
|
|
||||||
int &nowCursor) {
|
|
||||||
}
|
|
||||||
void setCorrectedText(QString &now, int &nowCursor, const QString &newText, int newPos);
|
|
||||||
|
|
||||||
virtual void paintAdditionalPlaceholder(QPainter &p) {
|
|
||||||
}
|
|
||||||
|
|
||||||
style::font phFont() {
|
|
||||||
return _st.font;
|
|
||||||
}
|
|
||||||
|
|
||||||
void placeholderAdditionalPrepare(QPainter &p);
|
|
||||||
QRect placeholderRect() const;
|
|
||||||
|
|
||||||
void setTextMargins(const QMargins &mrg);
|
|
||||||
const style::InputField &_st;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void updatePalette();
|
|
||||||
void refreshPlaceholder(const QString &text);
|
|
||||||
void setErrorShown(bool error);
|
|
||||||
|
|
||||||
void touchUpdate(QPoint globalPosition);
|
|
||||||
void touchFinish();
|
|
||||||
|
|
||||||
void setFocused(bool focused);
|
|
||||||
|
|
||||||
int _maxLength = -1;
|
|
||||||
bool _forcePlaceholderHidden = false;
|
|
||||||
|
|
||||||
QString _oldtext;
|
|
||||||
int _oldcursor = 0;
|
|
||||||
QString _lastPreEditText;
|
|
||||||
|
|
||||||
bool _undoAvailable = false;
|
|
||||||
bool _redoAvailable = false;
|
|
||||||
|
|
||||||
bool _customUpDown = false;
|
|
||||||
|
|
||||||
rpl::variable<QString> _placeholderFull;
|
|
||||||
QString _placeholder;
|
|
||||||
Animations::Simple _a_placeholderShifted;
|
|
||||||
bool _placeholderShifted = false;
|
|
||||||
QPainterPath _placeholderPath;
|
|
||||||
|
|
||||||
Animations::Simple _a_borderShown;
|
|
||||||
int _borderAnimationStart = 0;
|
|
||||||
Animations::Simple _a_borderOpacity;
|
|
||||||
bool _borderVisible = false;
|
|
||||||
|
|
||||||
Animations::Simple _a_focused;
|
|
||||||
Animations::Simple _a_error;
|
|
||||||
|
|
||||||
bool _focused = false;
|
|
||||||
bool _error = false;
|
|
||||||
|
|
||||||
style::margins _textMargins;
|
|
||||||
|
|
||||||
QTimer _touchTimer;
|
|
||||||
bool _touchPress = false;
|
|
||||||
bool _touchRightButton = false;
|
|
||||||
bool _touchMove = false;
|
|
||||||
bool _mousePressedInTouch = false;
|
|
||||||
QPoint _touchStart;
|
|
||||||
|
|
||||||
base::unique_qptr<PopupMenu> _contextMenu;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue