Don't use a modal to edit room name and topic

This commit is contained in:
Nicolas Werner 2022-03-30 06:45:31 +02:00
parent 82cdb483a9
commit 656fcac91c
No known key found for this signature in database
GPG key ID: C8D75E610773F2D9
8 changed files with 187 additions and 868 deletions

View file

@ -338,7 +338,6 @@ set(SRC_FILES
src/ui/NhekoDropArea.cpp
src/ui/NhekoGlobalObject.cpp
src/ui/RoomSettings.cpp
src/ui/TextField.cpp
src/ui/Theme.cpp
src/ui/ThemeManager.cpp
src/ui/UIA.cpp
@ -532,7 +531,6 @@ qt5_wrap_cpp(MOC_HEADERS
src/ui/NhekoDropArea.h
src/ui/NhekoGlobalObject.h
src/ui/RoomSettings.h
src/ui/TextField.h
src/ui/Theme.h
src/ui/ThemeManager.h
src/ui/UIA.h

View file

@ -107,17 +107,58 @@ ApplicationWindow {
hideErrorAnimation.restart();
}
}
Label {
text: roomSettings.roomName
Layout.alignment: Qt.AlignHCenter
font.pixelSize: fontMetrics.font.pixelSize * 2
Layout.fillWidth: true
horizontalAlignment: TextEdit.AlignHCenter
color: Nheko.colors.text
wrapMode: Text.Wrap
textFormat: Text.RichText
TextEdit {
id: roomName
property bool isNameEditingAllowed: false
readOnly: !isNameEditingAllowed
textFormat: isNameEditingAllowed ? TextEdit.PlainText : TextEdit.RichText
text: isNameEditingAllowed ? roomSettings.plainRoomName : roomSettings.roomName
font.pixelSize: fontMetrics.font.pixelSize * 2
color: Nheko.colors.text
Layout.alignment: Qt.AlignHCenter
Layout.maximumWidth: parent.width - (Nheko.paddingSmall * 2) - nameChangeButton.anchors.leftMargin - (nameChangeButton.width * 2)
horizontalAlignment: TextEdit.AlignHCenter
wrapMode: TextEdit.Wrap
selectByMouse: true
Keys.onShortcutOverride: event.key === Qt.Key_Enter
Keys.onPressed: {
if (event.matches(StandardKey.InsertLineSeparator) || event.matches(StandardKey.InsertParagraphSeparator)) {
roomSettings.changeName(roomName.text);
roomName.isNameEditingAllowed = false;
event.accepted = true;
}
}
ImageButton {
id: nameChangeButton
visible: roomSettings.canChangeName
anchors.leftMargin: Nheko.paddingSmall
anchors.left: roomName.right
anchors.verticalCenter: roomName.verticalCenter
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Change name of this room")
ToolTip.delay: Nheko.tooltipDelay
image: roomName.isNameEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg"
onClicked: {
if (roomName.isNameEditingAllowed) {
roomSettings.changeName(roomName.text);
roomName.isNameEditingAllowed = false;
} else {
roomName.isNameEditingAllowed = true;
roomName.focus = true;
roomName.selectAll();
}
}
}
}
Label {
text: qsTr("%n member(s)", "", roomSettings.memberCount)
Layout.alignment: Qt.AlignHCenter
@ -134,17 +175,10 @@ ApplicationWindow {
}
ImageButton {
Layout.alignment: Qt.AlignHCenter
image: ":/icons/icons/ui/edit.svg"
visible: roomSettings.canChangeNameAndTopic
onClicked: roomSettings.openEditModal()
}
TextArea {
id: roomTopic
property bool cut: implicitHeight > 100
property bool showMore
property bool showMore: false
clip: true
Layout.maximumHeight: showMore? Number.POSITIVE_INFINITY : 100
Layout.preferredHeight: implicitHeight
@ -153,10 +187,12 @@ ApplicationWindow {
Layout.leftMargin: Nheko.paddingLarge
Layout.rightMargin: Nheko.paddingLarge
text: TimelineManager.escapeEmoji(roomSettings.roomTopic)
property bool isTopicEditingAllowed: false
readOnly: !isTopicEditingAllowed
textFormat: isTopicEditingAllowed ? TextEdit.PlainText : TextEdit.RichText
text: isTopicEditingAllowed ? roomSettings.plainRoomTopic : roomSettings.roomTopic
wrapMode: TextEdit.WordWrap
textFormat: TextEdit.RichText
readOnly: true
background: null
selectByMouse: !Settings.mobileMode
color: Nheko.colors.text
@ -169,6 +205,29 @@ ApplicationWindow {
}
}
ImageButton {
id: topicChangeButton
Layout.alignment: Qt.AlignHCenter
visible: roomSettings.canChangeTopic
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Change topic of this room")
ToolTip.delay: Nheko.tooltipDelay
image: roomTopic.isTopicEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg"
onClicked: {
if (roomTopic.isTopicEditingAllowed) {
roomSettings.changeTopic(roomTopic.text);
roomTopic.isTopicEditingAllowed = false;
} else {
roomTopic.isTopicEditingAllowed = true;
roomTopic.showMore = true;
roomTopic.focus = true;
//roomTopic.selectAll();
}
}
}
Item {
Layout.alignment: Qt.AlignHCenter
id: showMorePlaceholder

View file

@ -5,13 +5,10 @@
#include "RoomSettings.h"
#include <QApplication>
#include <QFileDialog>
#include <QHBoxLayout>
#include <QImageReader>
#include <QMimeDatabase>
#include <QStandardPaths>
#include <QVBoxLayout>
#include <mtx/events/event_type.hpp>
#include <mtx/responses/common.hpp>
#include <mtx/responses/media.hpp>
@ -23,153 +20,9 @@
#include "Logging.h"
#include "MatrixClient.h"
#include "Utils.h"
#include "ui/TextField.h"
using namespace mtx::events;
EditModal::EditModal(const QString &roomId, QWidget *parent)
: QWidget(parent)
, roomId_{roomId}
{
setAutoFillBackground(true);
setAttribute(Qt::WA_DeleteOnClose, true);
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
setWindowModality(Qt::WindowModal);
QFont largeFont;
largeFont.setPointSizeF(largeFont.pointSizeF() * 1.4);
setMinimumWidth(conf::window::minModalWidth);
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
auto layout = new QVBoxLayout(this);
applyBtn_ = new QPushButton(tr("Apply"), this);
cancelBtn_ = new QPushButton(tr("Cancel"), this);
cancelBtn_->setDefault(true);
auto btnLayout = new QHBoxLayout;
btnLayout->addStretch(1);
btnLayout->setSpacing(15);
btnLayout->addWidget(cancelBtn_);
btnLayout->addWidget(applyBtn_);
nameInput_ = new TextField(this);
nameInput_->setLabel(tr("Name").toUpper());
topicInput_ = new TextField(this);
topicInput_->setLabel(tr("Topic").toUpper());
errorField_ = new QLabel(this);
errorField_->setWordWrap(true);
errorField_->hide();
layout->addWidget(nameInput_);
layout->addWidget(topicInput_);
layout->addLayout(btnLayout, 1);
auto labelLayout = new QHBoxLayout;
labelLayout->setAlignment(Qt::AlignHCenter);
labelLayout->addWidget(errorField_);
layout->addLayout(labelLayout);
connect(applyBtn_, &QPushButton::clicked, this, &EditModal::applyClicked);
connect(cancelBtn_, &QPushButton::clicked, this, &EditModal::close);
auto window = QApplication::activeWindow();
if (window != nullptr) {
auto center = window->frameGeometry().center();
move(center.x() - (width() * 0.5), center.y() - (height() * 0.5));
}
}
void
EditModal::topicEventSent(const QString &topic)
{
errorField_->hide();
emit topicChanged(topic);
close();
}
void
EditModal::nameEventSent(const QString &name)
{
errorField_->hide();
emit nameChanged(name);
close();
}
void
EditModal::error(const QString &msg)
{
errorField_->setText(msg);
errorField_->show();
}
void
EditModal::applyClicked()
{
// Check if the values are changed from the originals.
auto newName = nameInput_->text().trimmed();
auto newTopic = topicInput_->text().trimmed();
errorField_->hide();
if (newName == initialName_ && newTopic == initialTopic_) {
close();
return;
}
using namespace mtx::events;
auto proxy = std::make_shared<ThreadProxy>();
connect(proxy.get(), &ThreadProxy::topicEventSent, this, &EditModal::topicEventSent);
connect(proxy.get(), &ThreadProxy::nameEventSent, this, &EditModal::nameEventSent);
connect(proxy.get(), &ThreadProxy::error, this, &EditModal::error);
if (newName != initialName_ && !newName.isEmpty()) {
state::Name body;
body.name = newName.toStdString();
http::client()->send_state_event(
roomId_.toStdString(),
body,
[proxy, newName](const mtx::responses::EventId &, mtx::http::RequestErr err) {
if (err) {
emit proxy->error(QString::fromStdString(err->matrix_error.error));
return;
}
emit proxy->nameEventSent(newName);
});
}
if (newTopic != initialTopic_ && !newTopic.isEmpty()) {
state::Topic body;
body.topic = newTopic.toStdString();
http::client()->send_state_event(
roomId_.toStdString(),
body,
[proxy, newTopic](const mtx::responses::EventId &, mtx::http::RequestErr err) {
if (err) {
emit proxy->error(QString::fromStdString(err->matrix_error.error));
return;
}
emit proxy->topicEventSent(newTopic);
});
}
}
void
EditModal::setFields(const QString &roomName, const QString &roomTopic)
{
initialName_ = roomName;
initialTopic_ = roomTopic;
nameInput_->setText(roomName);
topicInput_->setText(roomTopic);
}
RoomSettings::RoomSettings(QString roomid, QObject *parent)
: QObject(parent)
, roomid_{std::move(roomid)}
@ -244,6 +97,18 @@ RoomSettings::roomTopic() const
.replace(QLatin1String("\n"), QLatin1String("<br>"))));
}
QString
RoomSettings::plainRoomName() const
{
return QString::fromStdString(info_.name);
}
QString
RoomSettings::plainRoomTopic() const
{
return QString::fromStdString(info_.topic);
}
QString
RoomSettings::roomId() const
{
@ -340,12 +205,24 @@ RoomSettings::canChangeJoinRules() const
}
bool
RoomSettings::canChangeNameAndTopic() const
RoomSettings::canChangeName() const
{
try {
return cache::hasEnoughPowerLevel({EventType::RoomName, EventType::RoomTopic},
roomid_.toStdString(),
utils::localUser().toStdString());
return cache::hasEnoughPowerLevel(
{EventType::RoomName}, roomid_.toStdString(), utils::localUser().toStdString());
} catch (const lmdb::error &e) {
nhlog::db()->warn("lmdb error: {}", e.what());
}
return false;
}
bool
RoomSettings::canChangeTopic() const
{
try {
return cache::hasEnoughPowerLevel(
{EventType::RoomTopic}, roomid_.toStdString(), utils::localUser().toStdString());
} catch (const lmdb::error &e) {
nhlog::db()->warn("lmdb error: {}", e.what());
}
@ -387,26 +264,6 @@ RoomSettings::supportsRestricted() const
info_.version != "6" && info_.version != "7";
}
void
RoomSettings::openEditModal()
{
retrieveRoomInfo();
auto modal = new EditModal(roomid_);
modal->setFields(QString::fromStdString(info_.name), QString::fromStdString(info_.topic));
modal->raise();
modal->show();
connect(modal, &EditModal::nameChanged, this, [this](const QString &newName) {
info_.name = newName.toStdString();
emit roomNameChanged();
});
connect(modal, &EditModal::topicChanged, this, [this](const QString &newTopic) {
info_.topic = newTopic.toStdString();
emit roomTopicChanged();
});
}
void
RoomSettings::changeNotifications(int currentIndex)
{
@ -502,6 +359,74 @@ RoomSettings::changeAccessRules(int index)
updateAccessRules(roomid_.toStdString(), join_rule, guest_access);
}
void
RoomSettings::changeName(QString name)
{
// Check if the values are changed from the originals.
auto newName = name.trimmed().toStdString();
if (newName == info_.name) {
return;
}
using namespace mtx::events;
auto proxy = std::make_shared<ThreadProxy>();
connect(proxy.get(), &ThreadProxy::nameEventSent, this, [this](QString newRoomName) {
this->info_.name = newRoomName.toStdString();
emit roomNameChanged();
});
connect(proxy.get(), &ThreadProxy::error, this, &RoomSettings::displayError);
state::Name body;
body.name = newName;
http::client()->send_state_event(
roomid_.toStdString(),
body,
[proxy, newName](const mtx::responses::EventId &, mtx::http::RequestErr err) {
if (err) {
emit proxy->error(QString::fromStdString(err->matrix_error.error));
return;
}
emit proxy->nameEventSent(QString::fromStdString(newName));
});
}
void
RoomSettings::changeTopic(QString topic)
{
// Check if the values are changed from the originals.
auto newTopic = topic.trimmed().toStdString();
if (newTopic == info_.topic) {
return;
}
using namespace mtx::events;
auto proxy = std::make_shared<ThreadProxy>();
connect(proxy.get(), &ThreadProxy::topicEventSent, this, [this](QString newRoomTopic) {
this->info_.topic = newRoomTopic.toStdString();
emit roomTopicChanged();
});
connect(proxy.get(), &ThreadProxy::error, this, &RoomSettings::displayError);
state::Topic body;
body.topic = newTopic;
http::client()->send_state_event(
roomid_.toStdString(),
body,
[proxy, newTopic](const mtx::responses::EventId &, mtx::http::RequestErr err) {
if (err) {
emit proxy->error(QString::fromStdString(err->matrix_error.error));
return;
}
emit proxy->topicEventSent(QString::fromStdString(newTopic));
});
}
void
RoomSettings::updateAccessRules(const std::string &room_id,
const mtx::events::state::JoinRules &join_rule,

View file

@ -5,9 +5,7 @@
#pragma once
#include <QLabel>
#include <QObject>
#include <QPushButton>
#include <QSet>
#include <QString>
@ -16,8 +14,6 @@
#include "CacheStructs.h"
class TextField;
/// Convenience class which connects events emmited from threads
/// outside of main with the UI code.
class ThreadProxy : public QObject
@ -31,40 +27,6 @@ signals:
void stopLoading();
};
class EditModal : public QWidget
{
Q_OBJECT
public:
EditModal(const QString &roomId, QWidget *parent = nullptr);
void setFields(const QString &roomName, const QString &roomTopic);
signals:
void nameChanged(const QString &roomName);
void topicChanged(const QString &topic);
private slots:
void topicEventSent(const QString &topic);
void nameEventSent(const QString &name);
void error(const QString &msg);
void applyClicked();
private:
QString roomId_;
QString initialName_;
QString initialTopic_;
QLabel *errorField_;
TextField *nameInput_;
TextField *topicInput_;
QPushButton *applyBtn_;
QPushButton *cancelBtn_;
};
class RoomSettings : public QObject
{
Q_OBJECT
@ -72,6 +34,8 @@ class RoomSettings : public QObject
Q_PROPERTY(QString roomVersion READ roomVersion CONSTANT)
Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged)
Q_PROPERTY(QString roomTopic READ roomTopic NOTIFY roomTopicChanged)
Q_PROPERTY(QString plainRoomName READ plainRoomName NOTIFY roomNameChanged)
Q_PROPERTY(QString plainRoomTopic READ plainRoomTopic NOTIFY roomTopicChanged)
Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl NOTIFY avatarUrlChanged)
Q_PROPERTY(int memberCount READ memberCount CONSTANT)
Q_PROPERTY(int notifications READ notifications NOTIFY notificationsChanged)
@ -79,7 +43,8 @@ class RoomSettings : public QObject
Q_PROPERTY(bool isLoading READ isLoading NOTIFY loadingChanged)
Q_PROPERTY(bool canChangeAvatar READ canChangeAvatar CONSTANT)
Q_PROPERTY(bool canChangeJoinRules READ canChangeJoinRules CONSTANT)
Q_PROPERTY(bool canChangeNameAndTopic READ canChangeNameAndTopic CONSTANT)
Q_PROPERTY(bool canChangeName READ canChangeName CONSTANT)
Q_PROPERTY(bool canChangeTopic READ canChangeTopic CONSTANT)
Q_PROPERTY(bool isEncryptionEnabled READ isEncryptionEnabled NOTIFY encryptionChanged)
Q_PROPERTY(bool supportsKnocking READ supportsKnocking CONSTANT)
Q_PROPERTY(bool supportsRestricted READ supportsRestricted CONSTANT)
@ -90,6 +55,8 @@ public:
QString roomId() const;
QString roomName() const;
QString roomTopic() const;
QString plainRoomName() const;
QString plainRoomTopic() const;
QString roomVersion() const;
QString roomAvatarUrl();
int memberCount() const;
@ -98,8 +65,10 @@ public:
bool isLoading() const;
//! Whether the user has enough power level to send m.room.join_rules events.
bool canChangeJoinRules() const;
//! Whether the user has enough power level to send m.room.name & m.room.topic events.
bool canChangeNameAndTopic() const;
//! Whether the user has enough power level to send m.room.name.
bool canChangeName() const;
//! Whether the user has enough power level to send m.room.topic events.
bool canChangeTopic() const;
//! Whether the user has enough power level to send m.room.avatar event.
bool canChangeAvatar() const;
bool isEncryptionEnabled() const;
@ -108,9 +77,10 @@ public:
Q_INVOKABLE void enableEncryption();
Q_INVOKABLE void updateAvatar();
Q_INVOKABLE void openEditModal();
Q_INVOKABLE void changeAccessRules(int index);
Q_INVOKABLE void changeNotifications(int currentIndex);
Q_INVOKABLE void changeTopic(QString topic);
Q_INVOKABLE void changeName(QString name);
signals:
void loadingChanged();

View file

@ -1,382 +0,0 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
#include "TextField.h"
#include <QCoreApplication>
#include <QEventTransition>
#include <QFontDatabase>
#include <QPaintEvent>
#include <QPainter>
#include <QPropertyAnimation>
#include <QRegularExpressionValidator>
TextField::TextField(QWidget *parent)
: QLineEdit(parent)
{
// Get rid of the focus border on macOS.
setAttribute(Qt::WA_MacShowFocusRect, 0);
QPalette pal;
state_machine_ = new TextFieldStateMachine(this);
label_ = nullptr;
label_font_size_ = 15;
show_label_ = false;
background_color_ = pal.color(QPalette::Window);
is_valid_ = true;
setFrame(false);
setAttribute(Qt::WA_Hover);
setMouseTracking(true);
setTextMargins(0, 4, 0, 6);
state_machine_->start();
QCoreApplication::processEvents();
}
void
TextField::setBackgroundColor(const QColor &color)
{
background_color_ = color;
emit backgroundColorChanged();
}
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::setValid(bool valid)
{
is_valid_ = valid;
}
bool
TextField::isValid() const
{
QString s = text();
int pos = 0;
if (regexp_.pattern().isEmpty()) {
return is_valid_;
}
QRegularExpressionValidator v(QRegularExpression(regexp_), 0);
return v.validate(s, pos) == QValidator::Acceptable;
}
void
TextField::setLabelFontSize(qreal size)
{
label_font_size_ = size;
if (label_) {
QFont font(label_->font());
font.setPointSizeF(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::setLabelColor(const QColor &color)
{
label_color_ = color;
emit labelColorChanged();
update();
}
QColor
TextField::labelColor() const
{
if (!label_color_.isValid()) {
return QPalette().color(QPalette::Text);
}
return label_color_;
}
void
TextField::setInkColor(const QColor &color)
{
ink_color_ = color;
emit inkColorChanged();
update();
}
QColor
TextField::inkColor() const
{
if (!ink_color_.isValid()) {
return QPalette().color(QPalette::Text);
}
return ink_color_;
}
void
TextField::setUnderlineColor(const QColor &color)
{
underline_color_ = color;
emit underlineColorChanged();
update();
}
void
TextField::setRegexp(const QRegularExpression &regexp)
{
regexp_ = regexp;
}
QColor
TextField::underlineColor() const
{
if (!underline_color_.isValid()) {
if ((hasAcceptableInput() && isValid()) || !isModified())
return QPalette().color(QPalette::Highlight);
else
return Qt::red;
}
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_ = nullptr;
offset_anim_ = nullptr;
color_anim_ = nullptr;
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;
font.setWeight(60);
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());
}

View file

@ -1,201 +0,0 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <QColor>
#include <QLineEdit>
#include <QPaintEvent>
#include <QPropertyAnimation>
#include <QRegularExpression>
#include <QStateMachine>
#include <QtGlobal>
class TextField;
class TextFieldLabel;
class TextFieldStateMachine;
class TextField : public QLineEdit
{
Q_OBJECT
Q_PROPERTY(QColor inkColor WRITE setInkColor READ inkColor NOTIFY inkColorChanged)
Q_PROPERTY(QColor labelColor WRITE setLabelColor READ labelColor NOTIFY labelColorChanged)
Q_PROPERTY(QColor underlineColor WRITE setUnderlineColor READ underlineColor NOTIFY
underlineColorChanged)
Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor NOTIFY
backgroundColorChanged)
public:
explicit TextField(QWidget *parent = nullptr);
void setInkColor(const QColor &color);
void setBackgroundColor(const QColor &color);
void setLabel(const QString &label);
void setLabelColor(const QColor &color);
void setLabelFontSize(qreal size);
void setShowLabel(bool value);
void setUnderlineColor(const QColor &color);
void setRegexp(const QRegularExpression &regexp);
void setValid(bool valid);
QColor inkColor() const;
QColor labelColor() const;
QColor underlineColor() const;
QColor backgroundColor() const;
QString label() const;
bool hasLabel() const;
bool isValid() const;
qreal labelFontSize() const;
protected:
bool event(QEvent *event) override;
void paintEvent(QPaintEvent *event) override;
signals:
void inkColorChanged();
void labelColorChanged();
void underlineColorChanged();
void backgroundColorChanged();
private:
void init();
QColor ink_color_;
QColor background_color_;
QColor label_color_;
QColor underline_color_;
QString label_text_;
TextFieldLabel *label_;
TextFieldStateMachine *state_machine_;
bool show_label_;
QRegularExpression regexp_;
bool is_valid_;
qreal label_font_size_;
};
class TextFieldLabel : public QWidget
{
Q_OBJECT
Q_PROPERTY(qreal scale WRITE setScale READ scale NOTIFY scaleChanged)
Q_PROPERTY(QPointF offset WRITE setOffset READ offset NOTIFY offsetChanged)
Q_PROPERTY(QColor color WRITE setColor READ color NOTIFY colorChanged)
public:
TextFieldLabel(TextField *parent);
inline void setColor(const QColor &color);
inline void setOffset(QPointF pos);
inline void setScale(qreal scale);
inline QColor color() const;
inline QPointF offset() const;
inline qreal scale() const;
protected:
void paintEvent(QPaintEvent *event) override;
signals:
void scaleChanged();
void offsetChanged();
void colorChanged();
private:
TextField *const text_field_;
QColor color_;
qreal scale_;
qreal x_;
qreal y_;
};
inline void
TextFieldLabel::setColor(const QColor &color)
{
color_ = color;
emit colorChanged();
update();
}
inline void
TextFieldLabel::setOffset(QPointF pos)
{
x_ = pos.x();
y_ = pos.y();
emit offsetChanged();
update();
}
inline void
TextFieldLabel::setScale(qreal scale)
{
scale_ = scale;
emit scaleChanged();
update();
}
inline QPointF
TextFieldLabel::offset() const
{
return QPointF(x_, y_);
}
inline qreal
TextFieldLabel::scale() const
{
return scale_;
}
inline QColor
TextFieldLabel::color() const
{
return color_;
}
class TextFieldStateMachine : public QStateMachine
{
Q_OBJECT
Q_PROPERTY(qreal progress WRITE setProgress READ progress NOTIFY progressChanged)
public:
TextFieldStateMachine(TextField *parent);
inline void setProgress(qreal progress);
void setLabel(TextFieldLabel *label);
inline qreal progress() const;
public slots:
void setupProperties();
signals:
void progressChanged();
private:
QPropertyAnimation *color_anim_;
QPropertyAnimation *offset_anim_;
QState *focused_state_;
QState *normal_state_;
TextField *text_field_;
TextFieldLabel *label_;
qreal progress_;
};
inline void
TextFieldStateMachine::setProgress(qreal progress)
{
progress_ = progress;
emit progressChanged();
text_field_->update();
}
inline qreal
TextFieldStateMachine::progress() const
{
return progress_;
}

View file

@ -8,54 +8,6 @@
#include <QColor>
#include <QPalette>
namespace ui {
// Default font size.
const int FontSize = 16;
// Default avatar size. Width and height.
const int AvatarSize = 40;
enum class ButtonPreset
{
FlatPreset,
CheckablePreset
};
enum class RippleStyle
{
CenteredRipple,
PositionedRipple,
NoRipple
};
enum class OverlayStyle
{
NoOverlay,
TintedOverlay,
GrayOverlay
};
enum class Role
{
Default,
Primary,
Secondary
};
enum class ButtonIconPlacement
{
LeftIcon,
RightIcon
};
enum class ProgressType
{
DeterminateProgress,
IndeterminateProgress
};
} // namespace ui
class Theme : public QPalette
{
Q_GADGET

View file

@ -3,8 +3,6 @@
//
// SPDX-License-Identifier: GPL-3.0-or-later
#include <QFontDatabase>
#include "ThemeManager.h"
QColor