mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Move SendButton/EmojiButton to td_ui.
This commit is contained in:
parent
af1854e877
commit
39cf51c066
23 changed files with 624 additions and 506 deletions
|
@ -47,8 +47,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/widgets/checkbox.h"
|
#include "ui/widgets/checkbox.h"
|
||||||
#include "ui/widgets/checkbox.h"
|
#include "ui/widgets/checkbox.h"
|
||||||
#include "ui/text/format_values.h"
|
#include "ui/text/format_values.h"
|
||||||
#include "ui/special_buttons.h"
|
|
||||||
#include "ui/text/text_options.h"
|
#include "ui/text/text_options.h"
|
||||||
|
#include "ui/controls/emoji_button.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
#include "confirm_box.h"
|
#include "confirm_box.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
|
|
@ -34,7 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/text/format_values.h"
|
#include "ui/text/format_values.h"
|
||||||
#include "ui/grouped_layout.h"
|
#include "ui/grouped_layout.h"
|
||||||
#include "ui/text/text_options.h"
|
#include "ui/text/text_options.h"
|
||||||
#include "ui/special_buttons.h"
|
#include "ui/controls/emoji_button.h"
|
||||||
#include "lottie/lottie_single_player.h"
|
#include "lottie/lottie_single_player.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "media/clip/media_clip_reader.h"
|
#include "media/clip/media_clip_reader.h"
|
||||||
|
|
|
@ -133,25 +133,3 @@ inline const QRegularExpression &cRussianLetters() {
|
||||||
static QRegularExpression regexp(QString::fromUtf8("[а-яА-ЯёЁ]"));
|
static QRegularExpression regexp(QString::fromUtf8("[а-яА-ЯёЁ]"));
|
||||||
return regexp;
|
return regexp;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline const QStringList &cImgExtensions() {
|
|
||||||
static QStringList result;
|
|
||||||
if (result.isEmpty()) {
|
|
||||||
result.reserve(4);
|
|
||||||
result.push_back(qsl(".jpg"));
|
|
||||||
result.push_back(qsl(".jpeg"));
|
|
||||||
result.push_back(qsl(".png"));
|
|
||||||
result.push_back(qsl(".gif"));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline const QStringList &cExtensionsForCompress() {
|
|
||||||
static QStringList result;
|
|
||||||
if (result.isEmpty()) {
|
|
||||||
result.push_back(qsl(".jpg"));
|
|
||||||
result.push_back(qsl(".jpeg"));
|
|
||||||
result.push_back(qsl(".png"));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "base/unixtime.h"
|
#include "base/unixtime.h"
|
||||||
#include "ui/delayed_activation.h"
|
#include "ui/delayed_activation.h"
|
||||||
|
#include "ui/chat/attach/attach_extensions.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
|
|
||||||
|
@ -316,8 +317,20 @@ QString AllFilesFilter() {
|
||||||
#endif // Q_OS_WIN
|
#endif // Q_OS_WIN
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString ImagesFilter() {
|
||||||
|
return u"Image files (*"_q + Ui::ImageExtensions().join(u" *"_q) + u")"_q;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString AllOrImagesFilter() {
|
||||||
|
return AllFilesFilter() + u";;"_q + ImagesFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ImagesOrAllFilter() {
|
||||||
|
return ImagesFilter() + u";;"_q + AllFilesFilter();
|
||||||
|
}
|
||||||
|
|
||||||
QString AlbumFilesFilter() {
|
QString AlbumFilesFilter() {
|
||||||
return qsl("Image and Video Files (*.png *.jpg *.jpeg *.mp4 *.mov)");
|
return u"Image and Video Files (*.png *.jpg *.jpeg *.mp4 *.mov)"_q;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
|
@ -89,8 +89,11 @@ void GetFolder(
|
||||||
Fn<void(QString &&result)> callback,
|
Fn<void(QString &&result)> callback,
|
||||||
Fn<void()> failed = Fn<void()>());
|
Fn<void()> failed = Fn<void()>());
|
||||||
|
|
||||||
QString AllFilesFilter();
|
[[nodiscard]] QString AllFilesFilter();
|
||||||
QString AlbumFilesFilter();
|
[[nodiscard]] QString ImagesFilter();
|
||||||
|
[[nodiscard]] QString AllOrImagesFilter();
|
||||||
|
[[nodiscard]] QString ImagesOrAllFilter();
|
||||||
|
[[nodiscard]] QString AlbumFilesFilter();
|
||||||
|
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/chat/message_bar.h"
|
#include "ui/chat/message_bar.h"
|
||||||
#include "ui/image/image.h"
|
#include "ui/image/image.h"
|
||||||
#include "ui/special_buttons.h"
|
#include "ui/special_buttons.h"
|
||||||
|
#include "ui/controls/emoji_button.h"
|
||||||
|
#include "ui/controls/send_button.h"
|
||||||
#include "inline_bots/inline_bot_result.h"
|
#include "inline_bots/inline_bot_result.h"
|
||||||
#include "base/event_filter.h"
|
#include "base/event_filter.h"
|
||||||
#include "base/unixtime.h"
|
#include "base/unixtime.h"
|
||||||
|
@ -3276,10 +3278,7 @@ void HistoryWidget::chooseAttach() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto filter = FileDialog::AllFilesFilter()
|
const auto filter = FileDialog::AllOrImagesFilter();
|
||||||
+ qsl(";;Image files (*")
|
|
||||||
+ cImgExtensions().join(qsl(" *"))
|
|
||||||
+ qsl(")");
|
|
||||||
|
|
||||||
FileDialog::GetOpenPaths(this, tr::lng_choose_files(tr::now), filter, crl::guard(this, [=](
|
FileDialog::GetOpenPaths(this, tr::lng_choose_files(tr::now), filter, crl::guard(this, [=](
|
||||||
FileDialog::OpenResult &&result) {
|
FileDialog::OpenResult &&result) {
|
||||||
|
|
|
@ -32,11 +32,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "media/audio/media_audio_capture.h"
|
#include "media/audio/media_audio_capture.h"
|
||||||
#include "media/audio/media_audio.h"
|
#include "media/audio/media_audio.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
#include "ui/special_buttons.h"
|
|
||||||
#include "ui/text/text_options.h"
|
#include "ui/text/text_options.h"
|
||||||
#include "ui/ui_utility.h"
|
#include "ui/ui_utility.h"
|
||||||
#include "ui/widgets/input_fields.h"
|
#include "ui/widgets/input_fields.h"
|
||||||
#include "ui/text/format_values.h"
|
#include "ui/text/format_values.h"
|
||||||
|
#include "ui/controls/emoji_button.h"
|
||||||
|
#include "ui/controls/send_button.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
|
|
||||||
|
|
|
@ -526,11 +526,7 @@ void RepliesWidget::chooseAttach() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto filter = FileDialog::AllFilesFilter()
|
const auto filter = FileDialog::AllOrImagesFilter();
|
||||||
+ qsl(";;Image files (*")
|
|
||||||
+ cImgExtensions().join(qsl(" *"))
|
|
||||||
+ qsl(")");
|
|
||||||
|
|
||||||
FileDialog::GetOpenPaths(this, tr::lng_choose_files(tr::now), filter, crl::guard(this, [=](
|
FileDialog::GetOpenPaths(this, tr::lng_choose_files(tr::now), filter, crl::guard(this, [=](
|
||||||
FileDialog::OpenResult &&result) {
|
FileDialog::OpenResult &&result) {
|
||||||
if (result.paths.isEmpty() && result.remoteContent.isEmpty()) {
|
if (result.paths.isEmpty() && result.remoteContent.isEmpty()) {
|
||||||
|
|
|
@ -266,11 +266,7 @@ void ScheduledWidget::chooseAttach() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto filter = FileDialog::AllFilesFilter()
|
const auto filter = FileDialog::AllOrImagesFilter();
|
||||||
+ qsl(";;Image files (*")
|
|
||||||
+ cImgExtensions().join(qsl(" *"))
|
|
||||||
+ qsl(")");
|
|
||||||
|
|
||||||
FileDialog::GetOpenPaths(this, tr::lng_choose_files(tr::now), filter, crl::guard(this, [=](
|
FileDialog::GetOpenPaths(this, tr::lng_choose_files(tr::now), filter, crl::guard(this, [=](
|
||||||
FileDialog::OpenResult &&result) {
|
FileDialog::OpenResult &&result) {
|
||||||
if (result.paths.isEmpty() && result.remoteContent.isEmpty()) {
|
if (result.paths.isEmpty() && result.remoteContent.isEmpty()) {
|
||||||
|
|
|
@ -853,10 +853,7 @@ void EditScans::ChooseScan(
|
||||||
Fn<void(ReadScanError)> errorCallback) {
|
Fn<void(ReadScanError)> errorCallback) {
|
||||||
Expects(parent != nullptr);
|
Expects(parent != nullptr);
|
||||||
|
|
||||||
const auto filter = FileDialog::AllFilesFilter()
|
const auto filter = FileDialog::AllOrImagesFilter();
|
||||||
+ qsl(";;Image files (*")
|
|
||||||
+ cImgExtensions().join(qsl(" *"))
|
|
||||||
+ qsl(")");
|
|
||||||
const auto guardedCallback = crl::guard(parent, doneCallback);
|
const auto guardedCallback = crl::guard(parent, doneCallback);
|
||||||
const auto guardedError = crl::guard(parent, errorCallback);
|
const auto guardedError = crl::guard(parent, errorCallback);
|
||||||
const auto onMainError = [=](ReadScanError error) {
|
const auto onMainError = [=](ReadScanError error) {
|
||||||
|
|
|
@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/widgets/checkbox.h"
|
#include "ui/widgets/checkbox.h"
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
#include "ui/widgets/labels.h"
|
#include "ui/widgets/labels.h"
|
||||||
|
#include "ui/chat/attach/attach_extensions.h"
|
||||||
#include "ui/layers/generic_box.h"
|
#include "ui/layers/generic_box.h"
|
||||||
#include "ui/effects/radial_animation.h"
|
#include "ui/effects/radial_animation.h"
|
||||||
#include "ui/toast/toast.h"
|
#include "ui/toast/toast.h"
|
||||||
|
@ -600,10 +601,9 @@ void BackgroundRow::updateImage() {
|
||||||
void ChooseFromFile(
|
void ChooseFromFile(
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionController*> controller,
|
||||||
not_null<QWidget*> parent) {
|
not_null<QWidget*> parent) {
|
||||||
const auto &imgExtensions = cImgExtensions();
|
|
||||||
auto filters = QStringList(
|
auto filters = QStringList(
|
||||||
qsl("Theme files (*.tdesktop-theme *.tdesktop-palette *")
|
qsl("Theme files (*.tdesktop-theme *.tdesktop-palette *")
|
||||||
+ imgExtensions.join(qsl(" *"))
|
+ Ui::ImageExtensions().join(qsl(" *"))
|
||||||
+ qsl(")"));
|
+ qsl(")"));
|
||||||
filters.push_back(FileDialog::AllFilesFilter());
|
filters.push_back(FileDialog::AllFilesFilter());
|
||||||
const auto callback = crl::guard(controller, [=](
|
const auto callback = crl::guard(controller, [=](
|
||||||
|
|
|
@ -63,11 +63,7 @@ void SetupPhoto(
|
||||||
st::settingsInfoPhotoSet);
|
st::settingsInfoPhotoSet);
|
||||||
upload->setFullRadius(true);
|
upload->setFullRadius(true);
|
||||||
upload->addClickHandler([=] {
|
upload->addClickHandler([=] {
|
||||||
const auto imageExtensions = cImgExtensions();
|
const auto filter = FileDialog::ImagesOrAllFilter();
|
||||||
const auto filter = qsl("Image files (*")
|
|
||||||
+ imageExtensions.join(qsl(" *"))
|
|
||||||
+ qsl(");;")
|
|
||||||
+ FileDialog::AllFilesFilter();
|
|
||||||
const auto callback = [=](const FileDialog::OpenResult &result) {
|
const auto callback = [=](const FileDialog::OpenResult &result) {
|
||||||
if (result.paths.isEmpty() && result.remoteContent.isEmpty()) {
|
if (result.paths.isEmpty() && result.remoteContent.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "storage/localimageloader.h"
|
#include "storage/localimageloader.h"
|
||||||
#include "core/mime_type.h"
|
#include "core/mime_type.h"
|
||||||
#include "ui/image/image_prepare.h"
|
#include "ui/image/image_prepare.h"
|
||||||
|
#include "ui/chat/attach/attach_extensions.h"
|
||||||
#include "app.h"
|
#include "app.h"
|
||||||
|
|
||||||
#include <QtCore/QSemaphore>
|
#include <QtCore/QSemaphore>
|
||||||
|
@ -194,7 +195,7 @@ MimeDataState ComputeMimeDataState(const QMimeData *data) {
|
||||||
return MimeDataState::None;
|
return MimeDataState::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto imageExtensions = cImgExtensions();
|
const auto imageExtensions = Ui::ImageExtensions();
|
||||||
auto files = QStringList();
|
auto files = QStringList();
|
||||||
auto allAreSmallImages = true;
|
auto allAreSmallImages = true;
|
||||||
for (const auto &url : urls) {
|
for (const auto &url : urls) {
|
||||||
|
@ -242,7 +243,7 @@ PreparedList PrepareMediaList(const QList<QUrl> &files, int previewWidth) {
|
||||||
PreparedList PrepareMediaList(const QStringList &files, int previewWidth) {
|
PreparedList PrepareMediaList(const QStringList &files, int previewWidth) {
|
||||||
auto result = PreparedList();
|
auto result = PreparedList();
|
||||||
result.files.reserve(files.size());
|
result.files.reserve(files.size());
|
||||||
const auto extensionsToCompress = cExtensionsForCompress();
|
const auto extensionsToCompress = Ui::ExtensionsForCompression();
|
||||||
for (const auto &file : files) {
|
for (const auto &file : files) {
|
||||||
const auto fileinfo = QFileInfo(file);
|
const auto fileinfo = QFileInfo(file);
|
||||||
const auto filesize = fileinfo.size();
|
const auto filesize = fileinfo.size();
|
||||||
|
|
33
Telegram/SourceFiles/ui/chat/attach/attach_extensions.cpp
Normal file
33
Telegram/SourceFiles/ui/chat/attach/attach_extensions.cpp
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "ui/chat/attach/attach_extensions.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
|
||||||
|
const QStringList &ImageExtensions() {
|
||||||
|
static const auto result = QStringList{
|
||||||
|
u".bmp"_q,
|
||||||
|
u".jpg"_q,
|
||||||
|
u".jpeg"_q,
|
||||||
|
u".png"_q,
|
||||||
|
u".gif"_q,
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QStringList &ExtensionsForCompression() {
|
||||||
|
static const auto result = QStringList{
|
||||||
|
u".bmp"_q,
|
||||||
|
u".jpg"_q,
|
||||||
|
u".jpeg"_q,
|
||||||
|
u".png"_q,
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Ui
|
15
Telegram/SourceFiles/ui/chat/attach/attach_extensions.h
Normal file
15
Telegram/SourceFiles/ui/chat/attach/attach_extensions.h
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
|
||||||
|
[[nodiscard]] const QStringList &ImageExtensions();
|
||||||
|
[[nodiscard]] const QStringList &ExtensionsForCompression();
|
||||||
|
|
||||||
|
} // namespace Ui
|
127
Telegram/SourceFiles/ui/controls/emoji_button.cpp
Normal file
127
Telegram/SourceFiles/ui/controls/emoji_button.cpp
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "ui/controls/emoji_button.h"
|
||||||
|
|
||||||
|
#include "ui/effects/radial_animation.h"
|
||||||
|
#include "ui/effects/ripple_animation.h"
|
||||||
|
#include "styles/style_chat.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
EmojiButton::EmojiButton(QWidget *parent, const style::IconButton &st)
|
||||||
|
: RippleButton(parent, st.ripple)
|
||||||
|
, _st(st) {
|
||||||
|
resize(_st.width, _st.height);
|
||||||
|
setCursor(style::cur_pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmojiButton::paintEvent(QPaintEvent *e) {
|
||||||
|
Painter p(this);
|
||||||
|
|
||||||
|
p.fillRect(e->rect(), st::historyComposeAreaBg);
|
||||||
|
paintRipple(p, _st.rippleAreaPosition.x(), _st.rippleAreaPosition.y(), _rippleOverride ? &(*_rippleOverride)->c : nullptr);
|
||||||
|
|
||||||
|
const auto over = isOver();
|
||||||
|
const auto loadingState = _loading
|
||||||
|
? _loading->computeState()
|
||||||
|
: RadialState{ 0., 0, RadialState::kFull };
|
||||||
|
if (loadingState.shown < 1.) {
|
||||||
|
p.setOpacity(1. - loadingState.shown);
|
||||||
|
|
||||||
|
const auto icon = _iconOverride ? _iconOverride : &(over ? _st.iconOver : _st.icon);
|
||||||
|
auto position = _st.iconPosition;
|
||||||
|
if (position.x() < 0) {
|
||||||
|
position.setX((width() - icon->width()) / 2);
|
||||||
|
}
|
||||||
|
if (position.y() < 0) {
|
||||||
|
position.setY((height() - icon->height()) / 2);
|
||||||
|
}
|
||||||
|
icon->paint(p, position, width());
|
||||||
|
|
||||||
|
p.setOpacity(1.);
|
||||||
|
}
|
||||||
|
|
||||||
|
QRect inner(QPoint((width() - st::historyEmojiCircle.width()) / 2, (height() - st::historyEmojiCircle.height()) / 2), st::historyEmojiCircle);
|
||||||
|
const auto color = (_colorOverride
|
||||||
|
? *_colorOverride
|
||||||
|
: (over
|
||||||
|
? st::historyEmojiCircleFgOver
|
||||||
|
: st::historyEmojiCircleFg));
|
||||||
|
if (anim::Disabled() && _loading && _loading->animating()) {
|
||||||
|
anim::DrawStaticLoading(
|
||||||
|
p,
|
||||||
|
inner,
|
||||||
|
st::historyEmojiCircleLine,
|
||||||
|
color);
|
||||||
|
} else {
|
||||||
|
auto pen = color->p;
|
||||||
|
pen.setWidth(st::historyEmojiCircleLine);
|
||||||
|
pen.setCapStyle(Qt::RoundCap);
|
||||||
|
p.setPen(pen);
|
||||||
|
p.setBrush(Qt::NoBrush);
|
||||||
|
|
||||||
|
PainterHighQualityEnabler hq(p);
|
||||||
|
if (loadingState.arcLength < RadialState::kFull) {
|
||||||
|
p.drawArc(inner, loadingState.arcFrom, loadingState.arcLength);
|
||||||
|
} else {
|
||||||
|
p.drawEllipse(inner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmojiButton::loadingAnimationCallback() {
|
||||||
|
if (!anim::Disabled()) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmojiButton::setLoading(bool loading) {
|
||||||
|
if (loading && !_loading) {
|
||||||
|
_loading = std::make_unique<InfiniteRadialAnimation>(
|
||||||
|
[=] { loadingAnimationCallback(); },
|
||||||
|
st::defaultInfiniteRadialAnimation);
|
||||||
|
}
|
||||||
|
if (loading) {
|
||||||
|
_loading->start();
|
||||||
|
update();
|
||||||
|
} else if (_loading) {
|
||||||
|
_loading->stop();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmojiButton::setColorOverrides(const style::icon *iconOverride, const style::color *colorOverride, const style::color *rippleOverride) {
|
||||||
|
_iconOverride = iconOverride;
|
||||||
|
_colorOverride = colorOverride;
|
||||||
|
_rippleOverride = rippleOverride;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmojiButton::onStateChanged(State was, StateChangeSource source) {
|
||||||
|
RippleButton::onStateChanged(was, source);
|
||||||
|
auto wasOver = static_cast<bool>(was & StateFlag::Over);
|
||||||
|
if (isOver() != wasOver) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QPoint EmojiButton::prepareRippleStartPosition() const {
|
||||||
|
if (!_st.rippleAreaSize) {
|
||||||
|
return DisabledRippleStartPosition();
|
||||||
|
}
|
||||||
|
return mapFromGlobal(QCursor::pos()) - _st.rippleAreaPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage EmojiButton::prepareRippleMask() const {
|
||||||
|
return RippleAnimation::ellipseMask(QSize(_st.rippleAreaSize, _st.rippleAreaSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Ui
|
46
Telegram/SourceFiles/ui/controls/emoji_button.h
Normal file
46
Telegram/SourceFiles/ui/controls/emoji_button.h
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/widgets/buttons.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
|
||||||
|
class InfiniteRadialAnimation;
|
||||||
|
|
||||||
|
class EmojiButton final : public RippleButton {
|
||||||
|
public:
|
||||||
|
EmojiButton(QWidget *parent, const style::IconButton &st);
|
||||||
|
|
||||||
|
void setLoading(bool loading);
|
||||||
|
void setColorOverrides(
|
||||||
|
const style::icon *iconOverride,
|
||||||
|
const style::color *colorOverride,
|
||||||
|
const style::color *rippleOverride);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void paintEvent(QPaintEvent *e) override;
|
||||||
|
void onStateChanged(State was, StateChangeSource source) override;
|
||||||
|
|
||||||
|
QImage prepareRippleMask() const override;
|
||||||
|
QPoint prepareRippleStartPosition() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void loadingAnimationCallback();
|
||||||
|
|
||||||
|
const style::IconButton &_st;
|
||||||
|
|
||||||
|
std::unique_ptr<Ui::InfiniteRadialAnimation> _loading;
|
||||||
|
|
||||||
|
const style::icon *_iconOverride = nullptr;
|
||||||
|
const style::color *_colorOverride = nullptr;
|
||||||
|
const style::color *_rippleOverride = nullptr;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Ui
|
265
Telegram/SourceFiles/ui/controls/send_button.cpp
Normal file
265
Telegram/SourceFiles/ui/controls/send_button.cpp
Normal file
|
@ -0,0 +1,265 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "ui/controls/send_button.h"
|
||||||
|
|
||||||
|
#include "ui/effects/ripple_animation.h"
|
||||||
|
#include "styles/style_chat.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr int kWideScale = 5;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
SendButton::SendButton(QWidget *parent)
|
||||||
|
: RippleButton(parent, st::historyReplyCancel.ripple) {
|
||||||
|
resize(st::historySendSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendButton::setType(Type type) {
|
||||||
|
Expects(isSlowmode() || type != Type::Slowmode);
|
||||||
|
|
||||||
|
if (isSlowmode() && type != Type::Slowmode) {
|
||||||
|
_afterSlowmodeType = type;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_type != type) {
|
||||||
|
_contentFrom = grabContent();
|
||||||
|
_type = type;
|
||||||
|
_a_typeChanged.stop();
|
||||||
|
_contentTo = grabContent();
|
||||||
|
_a_typeChanged.start(
|
||||||
|
[=] { update(); },
|
||||||
|
0.,
|
||||||
|
1.,
|
||||||
|
st::historyRecordVoiceDuration);
|
||||||
|
setPointerCursor(_type != Type::Slowmode);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
if (_type != Type::Record) {
|
||||||
|
_recordActive = false;
|
||||||
|
_a_recordActive.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendButton::setRecordActive(bool recordActive) {
|
||||||
|
if (_recordActive != recordActive) {
|
||||||
|
_recordActive = recordActive;
|
||||||
|
_a_recordActive.start(
|
||||||
|
[=] { recordAnimationCallback(); },
|
||||||
|
_recordActive ? 0. : 1.,
|
||||||
|
_recordActive ? 1. : 0,
|
||||||
|
st::historyRecordVoiceDuration);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendButton::setSlowmodeDelay(int seconds) {
|
||||||
|
Expects(seconds >= 0 && seconds < kSlowmodeDelayLimit);
|
||||||
|
|
||||||
|
if (_slowmodeDelay == seconds) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_slowmodeDelay = seconds;
|
||||||
|
_slowmodeDelayText = isSlowmode()
|
||||||
|
? u"%1:%2"_q.arg(seconds / 60).arg(seconds % 60, 2, 10, QChar('0'))
|
||||||
|
: QString();
|
||||||
|
setType(isSlowmode() ? Type::Slowmode : _afterSlowmodeType);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendButton::finishAnimating() {
|
||||||
|
_a_typeChanged.stop();
|
||||||
|
_a_recordActive.stop();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendButton::mouseMoveEvent(QMouseEvent *e) {
|
||||||
|
AbstractButton::mouseMoveEvent(e);
|
||||||
|
if (_recording) {
|
||||||
|
if (_recordUpdateCallback) {
|
||||||
|
_recordUpdateCallback(e->globalPos());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendButton::paintEvent(QPaintEvent *e) {
|
||||||
|
Painter p(this);
|
||||||
|
|
||||||
|
auto over = (isDown() || isOver());
|
||||||
|
auto changed = _a_typeChanged.value(1.);
|
||||||
|
if (changed < 1.) {
|
||||||
|
PainterHighQualityEnabler hq(p);
|
||||||
|
p.setOpacity(1. - changed);
|
||||||
|
auto targetRect = QRect((1 - kWideScale) / 2 * width(), (1 - kWideScale) / 2 * height(), kWideScale * width(), kWideScale * height());
|
||||||
|
auto hiddenWidth = anim::interpolate(0, (1 - kWideScale) / 2 * width(), changed);
|
||||||
|
auto hiddenHeight = anim::interpolate(0, (1 - kWideScale) / 2 * height(), changed);
|
||||||
|
p.drawPixmap(targetRect.marginsAdded(QMargins(hiddenWidth, hiddenHeight, hiddenWidth, hiddenHeight)), _contentFrom);
|
||||||
|
p.setOpacity(changed);
|
||||||
|
auto shownWidth = anim::interpolate((1 - kWideScale) / 2 * width(), 0, changed);
|
||||||
|
auto shownHeight = anim::interpolate((1 - kWideScale) / 2 * height(), 0, changed);
|
||||||
|
p.drawPixmap(targetRect.marginsAdded(QMargins(shownWidth, shownHeight, shownWidth, shownHeight)), _contentTo);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (_type) {
|
||||||
|
case Type::Record: paintRecord(p, over); break;
|
||||||
|
case Type::Save: paintSave(p, over); break;
|
||||||
|
case Type::Cancel: paintCancel(p, over); break;
|
||||||
|
case Type::Send: paintSend(p, over); break;
|
||||||
|
case Type::Schedule: paintSchedule(p, over); break;
|
||||||
|
case Type::Slowmode: paintSlowmode(p); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendButton::paintRecord(Painter &p, bool over) {
|
||||||
|
auto recordActive = recordActiveRatio();
|
||||||
|
if (!isDisabled()) {
|
||||||
|
auto rippleColor = anim::color(st::historyAttachEmoji.ripple.color, st::historyRecordVoiceRippleBgActive, recordActive);
|
||||||
|
paintRipple(p, (width() - st::historyAttachEmoji.rippleAreaSize) / 2, st::historyAttachEmoji.rippleAreaPosition.y(), &rippleColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto fastIcon = [&] {
|
||||||
|
if (isDisabled()) {
|
||||||
|
return &st::historyRecordVoice;
|
||||||
|
} else if (recordActive == 1.) {
|
||||||
|
return &st::historyRecordVoiceActive;
|
||||||
|
} else if (over) {
|
||||||
|
return &st::historyRecordVoiceOver;
|
||||||
|
}
|
||||||
|
return &st::historyRecordVoice;
|
||||||
|
};
|
||||||
|
fastIcon()->paintInCenter(p, rect());
|
||||||
|
if (!isDisabled() && recordActive > 0. && recordActive < 1.) {
|
||||||
|
p.setOpacity(recordActive);
|
||||||
|
st::historyRecordVoiceActive.paintInCenter(p, rect());
|
||||||
|
p.setOpacity(1.);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendButton::paintSave(Painter &p, bool over) {
|
||||||
|
const auto &saveIcon = over
|
||||||
|
? st::historyEditSaveIconOver
|
||||||
|
: st::historyEditSaveIcon;
|
||||||
|
saveIcon.paint(p, st::historySendIconPosition, width());
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendButton::paintCancel(Painter &p, bool over) {
|
||||||
|
paintRipple(p, (width() - st::historyAttachEmoji.rippleAreaSize) / 2, st::historyAttachEmoji.rippleAreaPosition.y());
|
||||||
|
|
||||||
|
const auto &cancelIcon = over
|
||||||
|
? st::historyReplyCancelIconOver
|
||||||
|
: st::historyReplyCancelIcon;
|
||||||
|
cancelIcon.paintInCenter(p, rect());
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendButton::paintSend(Painter &p, bool over) {
|
||||||
|
const auto &sendIcon = over
|
||||||
|
? st::historySendIconOver
|
||||||
|
: st::historySendIcon;
|
||||||
|
if (isDisabled()) {
|
||||||
|
const auto color = st::historyRecordVoiceFg->c;
|
||||||
|
sendIcon.paint(p, st::historySendIconPosition, width(), color);
|
||||||
|
} else {
|
||||||
|
sendIcon.paint(p, st::historySendIconPosition, width());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendButton::paintSchedule(Painter &p, bool over) {
|
||||||
|
{
|
||||||
|
PainterHighQualityEnabler hq(p);
|
||||||
|
p.setPen(Qt::NoPen);
|
||||||
|
p.setBrush(over ? st::historySendIconFgOver : st::historySendIconFg);
|
||||||
|
p.drawEllipse(
|
||||||
|
st::historyScheduleIconPosition.x(),
|
||||||
|
st::historyScheduleIconPosition.y(),
|
||||||
|
st::historyScheduleIcon.width(),
|
||||||
|
st::historyScheduleIcon.height());
|
||||||
|
}
|
||||||
|
st::historyScheduleIcon.paint(
|
||||||
|
p,
|
||||||
|
st::historyScheduleIconPosition,
|
||||||
|
width());
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendButton::paintSlowmode(Painter &p) {
|
||||||
|
p.setFont(st::normalFont);
|
||||||
|
p.setPen(st::windowSubTextFg);
|
||||||
|
p.drawText(
|
||||||
|
rect().marginsRemoved(st::historySlowmodeCounterMargins),
|
||||||
|
_slowmodeDelayText,
|
||||||
|
style::al_center);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendButton::onStateChanged(State was, StateChangeSource source) {
|
||||||
|
RippleButton::onStateChanged(was, source);
|
||||||
|
|
||||||
|
auto down = (state() & StateFlag::Down);
|
||||||
|
if ((was & StateFlag::Down) != down) {
|
||||||
|
if (down) {
|
||||||
|
if (_type == Type::Record) {
|
||||||
|
_recording = true;
|
||||||
|
if (_recordStartCallback) {
|
||||||
|
_recordStartCallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (_recording) {
|
||||||
|
_recording = false;
|
||||||
|
if (_recordStopCallback) {
|
||||||
|
_recordStopCallback(_recordActive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SendButton::isSlowmode() const {
|
||||||
|
return (_slowmodeDelay > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
QPixmap SendButton::grabContent() {
|
||||||
|
auto result = QImage(
|
||||||
|
kWideScale * size() * style::DevicePixelRatio(),
|
||||||
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
|
result.setDevicePixelRatio(style::DevicePixelRatio());
|
||||||
|
result.fill(Qt::transparent);
|
||||||
|
{
|
||||||
|
Painter p(&result);
|
||||||
|
p.drawPixmap(
|
||||||
|
(kWideScale - 1) / 2 * width(),
|
||||||
|
(kWideScale - 1) / 2 * height(),
|
||||||
|
GrabWidget(this));
|
||||||
|
}
|
||||||
|
return Ui::PixmapFromImage(std::move(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage SendButton::prepareRippleMask() const {
|
||||||
|
auto size = (_type == Type::Record)
|
||||||
|
? st::historyAttachEmoji.rippleAreaSize
|
||||||
|
: st::historyReplyCancel.rippleAreaSize;
|
||||||
|
return RippleAnimation::ellipseMask(QSize(size, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
QPoint SendButton::prepareRippleStartPosition() const {
|
||||||
|
auto real = mapFromGlobal(QCursor::pos());
|
||||||
|
auto size = (_type == Type::Record)
|
||||||
|
? st::historyAttachEmoji.rippleAreaSize
|
||||||
|
: st::historyReplyCancel.rippleAreaSize;
|
||||||
|
auto y = (_type == Type::Record)
|
||||||
|
? st::historyAttachEmoji.rippleAreaPosition.y()
|
||||||
|
: (height() - st::historyReplyCancel.rippleAreaSize) / 2;
|
||||||
|
return real - QPoint((width() - size) / 2, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendButton::recordAnimationCallback() {
|
||||||
|
update();
|
||||||
|
if (_recordAnimationCallback) {
|
||||||
|
_recordAnimationCallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Ui
|
92
Telegram/SourceFiles/ui/controls/send_button.h
Normal file
92
Telegram/SourceFiles/ui/controls/send_button.h
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/widgets/buttons.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
|
||||||
|
class SendButton final : public RippleButton {
|
||||||
|
public:
|
||||||
|
SendButton(QWidget *parent);
|
||||||
|
|
||||||
|
static constexpr auto kSlowmodeDelayLimit = 100 * 60;
|
||||||
|
|
||||||
|
enum class Type {
|
||||||
|
Send,
|
||||||
|
Schedule,
|
||||||
|
Save,
|
||||||
|
Record,
|
||||||
|
Cancel,
|
||||||
|
Slowmode,
|
||||||
|
};
|
||||||
|
[[nodiscard]] Type type() const {
|
||||||
|
return _type;
|
||||||
|
}
|
||||||
|
void setType(Type state);
|
||||||
|
void setRecordActive(bool recordActive);
|
||||||
|
void setSlowmodeDelay(int seconds);
|
||||||
|
void finishAnimating();
|
||||||
|
|
||||||
|
void setRecordStartCallback(Fn<void()> callback) {
|
||||||
|
_recordStartCallback = std::move(callback);
|
||||||
|
}
|
||||||
|
void setRecordUpdateCallback(Fn<void(QPoint globalPos)> callback) {
|
||||||
|
_recordUpdateCallback = std::move(callback);
|
||||||
|
}
|
||||||
|
void setRecordStopCallback(Fn<void(bool active)> callback) {
|
||||||
|
_recordStopCallback = std::move(callback);
|
||||||
|
}
|
||||||
|
void setRecordAnimationCallback(Fn<void()> callback) {
|
||||||
|
_recordAnimationCallback = std::move(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] float64 recordActiveRatio() {
|
||||||
|
return _a_recordActive.value(_recordActive ? 1. : 0.);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void mouseMoveEvent(QMouseEvent *e) override;
|
||||||
|
void paintEvent(QPaintEvent *e) override;
|
||||||
|
void onStateChanged(State was, StateChangeSource source) override;
|
||||||
|
|
||||||
|
QImage prepareRippleMask() const override;
|
||||||
|
QPoint prepareRippleStartPosition() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void recordAnimationCallback();
|
||||||
|
[[nodiscard]] QPixmap grabContent();
|
||||||
|
[[nodiscard]] bool isSlowmode() const;
|
||||||
|
|
||||||
|
void paintRecord(Painter &p, bool over);
|
||||||
|
void paintSave(Painter &p, bool over);
|
||||||
|
void paintCancel(Painter &p, bool over);
|
||||||
|
void paintSend(Painter &p, bool over);
|
||||||
|
void paintSchedule(Painter &p, bool over);
|
||||||
|
void paintSlowmode(Painter &p);
|
||||||
|
|
||||||
|
Type _type = Type::Send;
|
||||||
|
Type _afterSlowmodeType = Type::Send;
|
||||||
|
bool _recordActive = false;
|
||||||
|
QPixmap _contentFrom, _contentTo;
|
||||||
|
|
||||||
|
Ui::Animations::Simple _a_typeChanged;
|
||||||
|
Ui::Animations::Simple _a_recordActive;
|
||||||
|
|
||||||
|
bool _recording = false;
|
||||||
|
Fn<void()> _recordStartCallback;
|
||||||
|
Fn<void(bool active)> _recordStopCallback;
|
||||||
|
Fn<void(QPoint globalPos)> _recordUpdateCallback;
|
||||||
|
Fn<void()> _recordAnimationCallback;
|
||||||
|
|
||||||
|
int _slowmodeDelay = 0;
|
||||||
|
QString _slowmodeDelayText;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Ui
|
|
@ -43,8 +43,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr int kWideScale = 5;
|
|
||||||
|
|
||||||
QString CropTitle(not_null<PeerData*> peer) {
|
QString CropTitle(not_null<PeerData*> peer) {
|
||||||
if (peer->isChat() || peer->isMegagroup()) {
|
if (peer->isChat() || peer->isMegagroup()) {
|
||||||
return tr::lng_create_group_crop(tr::now);
|
return tr::lng_create_group_crop(tr::now);
|
||||||
|
@ -122,11 +120,7 @@ void ShowChoosePhotoBox(
|
||||||
QPointer<QWidget> parent,
|
QPointer<QWidget> parent,
|
||||||
const QString &title,
|
const QString &title,
|
||||||
Callback &&callback) {
|
Callback &&callback) {
|
||||||
auto imgExtensions = cImgExtensions();
|
auto filter = FileDialog::ImagesOrAllFilter();
|
||||||
auto filter = qsl("Image files (*")
|
|
||||||
+ imgExtensions.join(qsl(" *"))
|
|
||||||
+ qsl(");;")
|
|
||||||
+ FileDialog::AllFilesFilter();
|
|
||||||
auto handleChosenPhoto = [
|
auto handleChosenPhoto = [
|
||||||
title,
|
title,
|
||||||
callback = std::forward<Callback>(callback)
|
callback = std::forward<Callback>(callback)
|
||||||
|
@ -185,342 +179,6 @@ void HistoryDownButton::setUnreadCount(int unreadCount) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EmojiButton::EmojiButton(QWidget *parent, const style::IconButton &st)
|
|
||||||
: RippleButton(parent, st.ripple)
|
|
||||||
, _st(st) {
|
|
||||||
resize(_st.width, _st.height);
|
|
||||||
setCursor(style::cur_pointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmojiButton::paintEvent(QPaintEvent *e) {
|
|
||||||
Painter p(this);
|
|
||||||
|
|
||||||
p.fillRect(e->rect(), st::historyComposeAreaBg);
|
|
||||||
paintRipple(p, _st.rippleAreaPosition.x(), _st.rippleAreaPosition.y(), _rippleOverride ? &(*_rippleOverride)->c : nullptr);
|
|
||||||
|
|
||||||
const auto over = isOver();
|
|
||||||
const auto loadingState = _loading
|
|
||||||
? _loading->computeState()
|
|
||||||
: Ui::RadialState{ 0., 0, FullArcLength };
|
|
||||||
if (loadingState.shown < 1.) {
|
|
||||||
p.setOpacity(1. - loadingState.shown);
|
|
||||||
|
|
||||||
const auto icon = _iconOverride ? _iconOverride : &(over ? _st.iconOver : _st.icon);
|
|
||||||
auto position = _st.iconPosition;
|
|
||||||
if (position.x() < 0) {
|
|
||||||
position.setX((width() - icon->width()) / 2);
|
|
||||||
}
|
|
||||||
if (position.y() < 0) {
|
|
||||||
position.setY((height() - icon->height()) / 2);
|
|
||||||
}
|
|
||||||
icon->paint(p, position, width());
|
|
||||||
|
|
||||||
p.setOpacity(1.);
|
|
||||||
}
|
|
||||||
|
|
||||||
QRect inner(QPoint((width() - st::historyEmojiCircle.width()) / 2, (height() - st::historyEmojiCircle.height()) / 2), st::historyEmojiCircle);
|
|
||||||
const auto color = (_colorOverride
|
|
||||||
? *_colorOverride
|
|
||||||
: (over
|
|
||||||
? st::historyEmojiCircleFgOver
|
|
||||||
: st::historyEmojiCircleFg));
|
|
||||||
if (anim::Disabled() && _loading && _loading->animating()) {
|
|
||||||
anim::DrawStaticLoading(
|
|
||||||
p,
|
|
||||||
inner,
|
|
||||||
st::historyEmojiCircleLine,
|
|
||||||
color);
|
|
||||||
} else {
|
|
||||||
auto pen = color->p;
|
|
||||||
pen.setWidth(st::historyEmojiCircleLine);
|
|
||||||
pen.setCapStyle(Qt::RoundCap);
|
|
||||||
p.setPen(pen);
|
|
||||||
p.setBrush(Qt::NoBrush);
|
|
||||||
|
|
||||||
PainterHighQualityEnabler hq(p);
|
|
||||||
if (loadingState.arcLength < FullArcLength) {
|
|
||||||
p.drawArc(inner, loadingState.arcFrom, loadingState.arcLength);
|
|
||||||
} else {
|
|
||||||
p.drawEllipse(inner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmojiButton::loadingAnimationCallback() {
|
|
||||||
if (!anim::Disabled()) {
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmojiButton::setLoading(bool loading) {
|
|
||||||
if (loading && !_loading) {
|
|
||||||
_loading = std::make_unique<Ui::InfiniteRadialAnimation>(
|
|
||||||
[=] { loadingAnimationCallback(); },
|
|
||||||
st::defaultInfiniteRadialAnimation);
|
|
||||||
}
|
|
||||||
if (loading) {
|
|
||||||
_loading->start();
|
|
||||||
update();
|
|
||||||
} else if (_loading) {
|
|
||||||
_loading->stop();
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmojiButton::setColorOverrides(const style::icon *iconOverride, const style::color *colorOverride, const style::color *rippleOverride) {
|
|
||||||
_iconOverride = iconOverride;
|
|
||||||
_colorOverride = colorOverride;
|
|
||||||
_rippleOverride = rippleOverride;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmojiButton::onStateChanged(State was, StateChangeSource source) {
|
|
||||||
RippleButton::onStateChanged(was, source);
|
|
||||||
auto wasOver = static_cast<bool>(was & StateFlag::Over);
|
|
||||||
if (isOver() != wasOver) {
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QPoint EmojiButton::prepareRippleStartPosition() const {
|
|
||||||
if (!_st.rippleAreaSize) {
|
|
||||||
return DisabledRippleStartPosition();
|
|
||||||
}
|
|
||||||
return mapFromGlobal(QCursor::pos()) - _st.rippleAreaPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage EmojiButton::prepareRippleMask() const {
|
|
||||||
return RippleAnimation::ellipseMask(QSize(_st.rippleAreaSize, _st.rippleAreaSize));
|
|
||||||
}
|
|
||||||
|
|
||||||
SendButton::SendButton(QWidget *parent) : RippleButton(parent, st::historyReplyCancel.ripple) {
|
|
||||||
resize(st::historySendSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SendButton::setType(Type type) {
|
|
||||||
Expects(isSlowmode() || type != Type::Slowmode);
|
|
||||||
|
|
||||||
if (isSlowmode() && type != Type::Slowmode) {
|
|
||||||
_afterSlowmodeType = type;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (_type != type) {
|
|
||||||
_contentFrom = grabContent();
|
|
||||||
_type = type;
|
|
||||||
_a_typeChanged.stop();
|
|
||||||
_contentTo = grabContent();
|
|
||||||
_a_typeChanged.start([=] { update(); }, 0., 1., st::historyRecordVoiceDuration);
|
|
||||||
setPointerCursor(_type != Type::Slowmode);
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
if (_type != Type::Record) {
|
|
||||||
_recordActive = false;
|
|
||||||
_a_recordActive.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SendButton::setRecordActive(bool recordActive) {
|
|
||||||
if (_recordActive != recordActive) {
|
|
||||||
_recordActive = recordActive;
|
|
||||||
_a_recordActive.start([this] { recordAnimationCallback(); }, _recordActive ? 0. : 1., _recordActive ? 1. : 0, st::historyRecordVoiceDuration);
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SendButton::setSlowmodeDelay(int seconds) {
|
|
||||||
Expects(seconds >= 0 && seconds < kSlowmodeDelayLimit);
|
|
||||||
|
|
||||||
if (_slowmodeDelay == seconds) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_slowmodeDelay = seconds;
|
|
||||||
_slowmodeDelayText = isSlowmode()
|
|
||||||
? qsl("%1:%2").arg(seconds / 60).arg(seconds % 60, 2, 10, QChar('0'))
|
|
||||||
: QString();
|
|
||||||
setType(isSlowmode() ? Type::Slowmode : _afterSlowmodeType);
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SendButton::finishAnimating() {
|
|
||||||
_a_typeChanged.stop();
|
|
||||||
_a_recordActive.stop();
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SendButton::mouseMoveEvent(QMouseEvent *e) {
|
|
||||||
AbstractButton::mouseMoveEvent(e);
|
|
||||||
if (_recording) {
|
|
||||||
if (_recordUpdateCallback) {
|
|
||||||
_recordUpdateCallback(e->globalPos());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SendButton::paintEvent(QPaintEvent *e) {
|
|
||||||
Painter p(this);
|
|
||||||
|
|
||||||
auto over = (isDown() || isOver());
|
|
||||||
auto changed = _a_typeChanged.value(1.);
|
|
||||||
if (changed < 1.) {
|
|
||||||
PainterHighQualityEnabler hq(p);
|
|
||||||
p.setOpacity(1. - changed);
|
|
||||||
auto targetRect = QRect((1 - kWideScale) / 2 * width(), (1 - kWideScale) / 2 * height(), kWideScale * width(), kWideScale * height());
|
|
||||||
auto hiddenWidth = anim::interpolate(0, (1 - kWideScale) / 2 * width(), changed);
|
|
||||||
auto hiddenHeight = anim::interpolate(0, (1 - kWideScale) / 2 * height(), changed);
|
|
||||||
p.drawPixmap(targetRect.marginsAdded(QMargins(hiddenWidth, hiddenHeight, hiddenWidth, hiddenHeight)), _contentFrom);
|
|
||||||
p.setOpacity(changed);
|
|
||||||
auto shownWidth = anim::interpolate((1 - kWideScale) / 2 * width(), 0, changed);
|
|
||||||
auto shownHeight = anim::interpolate((1 - kWideScale) / 2 * height(), 0, changed);
|
|
||||||
p.drawPixmap(targetRect.marginsAdded(QMargins(shownWidth, shownHeight, shownWidth, shownHeight)), _contentTo);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch (_type) {
|
|
||||||
case Type::Record: paintRecord(p, over); break;
|
|
||||||
case Type::Save: paintSave(p, over); break;
|
|
||||||
case Type::Cancel: paintCancel(p, over); break;
|
|
||||||
case Type::Send: paintSend(p, over); break;
|
|
||||||
case Type::Schedule: paintSchedule(p, over); break;
|
|
||||||
case Type::Slowmode: paintSlowmode(p); break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SendButton::paintRecord(Painter &p, bool over) {
|
|
||||||
auto recordActive = recordActiveRatio();
|
|
||||||
if (!isDisabled()) {
|
|
||||||
auto rippleColor = anim::color(st::historyAttachEmoji.ripple.color, st::historyRecordVoiceRippleBgActive, recordActive);
|
|
||||||
paintRipple(p, (width() - st::historyAttachEmoji.rippleAreaSize) / 2, st::historyAttachEmoji.rippleAreaPosition.y(), &rippleColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto fastIcon = [&] {
|
|
||||||
if (isDisabled()) {
|
|
||||||
return &st::historyRecordVoice;
|
|
||||||
} else if (recordActive == 1.) {
|
|
||||||
return &st::historyRecordVoiceActive;
|
|
||||||
} else if (over) {
|
|
||||||
return &st::historyRecordVoiceOver;
|
|
||||||
}
|
|
||||||
return &st::historyRecordVoice;
|
|
||||||
};
|
|
||||||
fastIcon()->paintInCenter(p, rect());
|
|
||||||
if (!isDisabled() && recordActive > 0. && recordActive < 1.) {
|
|
||||||
p.setOpacity(recordActive);
|
|
||||||
st::historyRecordVoiceActive.paintInCenter(p, rect());
|
|
||||||
p.setOpacity(1.);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SendButton::paintSave(Painter &p, bool over) {
|
|
||||||
const auto &saveIcon = over
|
|
||||||
? st::historyEditSaveIconOver
|
|
||||||
: st::historyEditSaveIcon;
|
|
||||||
saveIcon.paint(p, st::historySendIconPosition, width());
|
|
||||||
}
|
|
||||||
|
|
||||||
void SendButton::paintCancel(Painter &p, bool over) {
|
|
||||||
paintRipple(p, (width() - st::historyAttachEmoji.rippleAreaSize) / 2, st::historyAttachEmoji.rippleAreaPosition.y());
|
|
||||||
|
|
||||||
const auto &cancelIcon = over
|
|
||||||
? st::historyReplyCancelIconOver
|
|
||||||
: st::historyReplyCancelIcon;
|
|
||||||
cancelIcon.paintInCenter(p, rect());
|
|
||||||
}
|
|
||||||
|
|
||||||
void SendButton::paintSend(Painter &p, bool over) {
|
|
||||||
const auto &sendIcon = over
|
|
||||||
? st::historySendIconOver
|
|
||||||
: st::historySendIcon;
|
|
||||||
if (isDisabled()) {
|
|
||||||
const auto color = st::historyRecordVoiceFg->c;
|
|
||||||
sendIcon.paint(p, st::historySendIconPosition, width(), color);
|
|
||||||
} else {
|
|
||||||
sendIcon.paint(p, st::historySendIconPosition, width());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SendButton::paintSchedule(Painter &p, bool over) {
|
|
||||||
{
|
|
||||||
PainterHighQualityEnabler hq(p);
|
|
||||||
p.setPen(Qt::NoPen);
|
|
||||||
p.setBrush(over ? st::historySendIconFgOver : st::historySendIconFg);
|
|
||||||
p.drawEllipse(
|
|
||||||
st::historyScheduleIconPosition.x(),
|
|
||||||
st::historyScheduleIconPosition.y(),
|
|
||||||
st::historyScheduleIcon.width(),
|
|
||||||
st::historyScheduleIcon.height());
|
|
||||||
}
|
|
||||||
st::historyScheduleIcon.paint(
|
|
||||||
p,
|
|
||||||
st::historyScheduleIconPosition,
|
|
||||||
width());
|
|
||||||
}
|
|
||||||
|
|
||||||
void SendButton::paintSlowmode(Painter &p) {
|
|
||||||
p.setFont(st::normalFont);
|
|
||||||
p.setPen(st::windowSubTextFg);
|
|
||||||
p.drawText(
|
|
||||||
rect().marginsRemoved(st::historySlowmodeCounterMargins),
|
|
||||||
_slowmodeDelayText,
|
|
||||||
style::al_center);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SendButton::onStateChanged(State was, StateChangeSource source) {
|
|
||||||
RippleButton::onStateChanged(was, source);
|
|
||||||
|
|
||||||
auto down = (state() & StateFlag::Down);
|
|
||||||
if ((was & StateFlag::Down) != down) {
|
|
||||||
if (down) {
|
|
||||||
if (_type == Type::Record) {
|
|
||||||
_recording = true;
|
|
||||||
if (_recordStartCallback) {
|
|
||||||
_recordStartCallback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (_recording) {
|
|
||||||
_recording = false;
|
|
||||||
if (_recordStopCallback) {
|
|
||||||
_recordStopCallback(_recordActive);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SendButton::isSlowmode() const {
|
|
||||||
return (_slowmodeDelay > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
QPixmap SendButton::grabContent() {
|
|
||||||
auto result = QImage(kWideScale * size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
|
|
||||||
result.setDevicePixelRatio(cRetinaFactor());
|
|
||||||
result.fill(Qt::transparent);
|
|
||||||
{
|
|
||||||
Painter p(&result);
|
|
||||||
p.drawPixmap(
|
|
||||||
(kWideScale - 1) / 2 * width(),
|
|
||||||
(kWideScale - 1) / 2 * height(),
|
|
||||||
GrabWidget(this));
|
|
||||||
}
|
|
||||||
return App::pixmapFromImageInPlace(std::move(result));
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage SendButton::prepareRippleMask() const {
|
|
||||||
auto size = (_type == Type::Record) ? st::historyAttachEmoji.rippleAreaSize : st::historyReplyCancel.rippleAreaSize;
|
|
||||||
return Ui::RippleAnimation::ellipseMask(QSize(size, size));
|
|
||||||
}
|
|
||||||
|
|
||||||
QPoint SendButton::prepareRippleStartPosition() const {
|
|
||||||
auto real = mapFromGlobal(QCursor::pos());
|
|
||||||
auto size = (_type == Type::Record) ? st::historyAttachEmoji.rippleAreaSize : st::historyReplyCancel.rippleAreaSize;
|
|
||||||
auto y = (_type == Type::Record) ? st::historyAttachEmoji.rippleAreaPosition.y() : (height() - st::historyReplyCancel.rippleAreaSize) / 2;
|
|
||||||
return real - QPoint((width() - size) / 2, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SendButton::recordAnimationCallback() {
|
|
||||||
update();
|
|
||||||
if (_recordAnimationCallback) {
|
|
||||||
_recordAnimationCallback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UserpicButton::UserpicButton(
|
UserpicButton::UserpicButton(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
const QString &cropTitle,
|
const QString &cropTitle,
|
||||||
|
|
|
@ -34,8 +34,6 @@ struct Information;
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
|
|
||||||
class InfiniteRadialAnimation;
|
|
||||||
|
|
||||||
class HistoryDownButton : public RippleButton {
|
class HistoryDownButton : public RippleButton {
|
||||||
public:
|
public:
|
||||||
HistoryDownButton(QWidget *parent, const style::TwoIconButton &st);
|
HistoryDownButton(QWidget *parent, const style::TwoIconButton &st);
|
||||||
|
@ -58,111 +56,6 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class EmojiButton : public RippleButton {
|
|
||||||
public:
|
|
||||||
EmojiButton(QWidget *parent, const style::IconButton &st);
|
|
||||||
|
|
||||||
void setLoading(bool loading);
|
|
||||||
void setColorOverrides(const style::icon *iconOverride, const style::color *colorOverride, const style::color *rippleOverride);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void paintEvent(QPaintEvent *e) override;
|
|
||||||
void onStateChanged(State was, StateChangeSource source) override;
|
|
||||||
|
|
||||||
QImage prepareRippleMask() const override;
|
|
||||||
QPoint prepareRippleStartPosition() const override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void loadingAnimationCallback();
|
|
||||||
|
|
||||||
const style::IconButton &_st;
|
|
||||||
|
|
||||||
std::unique_ptr<Ui::InfiniteRadialAnimation> _loading;
|
|
||||||
|
|
||||||
const style::icon *_iconOverride = nullptr;
|
|
||||||
const style::color *_colorOverride = nullptr;
|
|
||||||
const style::color *_rippleOverride = nullptr;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
class SendButton : public RippleButton {
|
|
||||||
public:
|
|
||||||
SendButton(QWidget *parent);
|
|
||||||
|
|
||||||
static constexpr auto kSlowmodeDelayLimit = 100 * 60;
|
|
||||||
|
|
||||||
enum class Type {
|
|
||||||
Send,
|
|
||||||
Schedule,
|
|
||||||
Save,
|
|
||||||
Record,
|
|
||||||
Cancel,
|
|
||||||
Slowmode,
|
|
||||||
};
|
|
||||||
Type type() const {
|
|
||||||
return _type;
|
|
||||||
}
|
|
||||||
void setType(Type state);
|
|
||||||
void setRecordActive(bool recordActive);
|
|
||||||
void setSlowmodeDelay(int seconds);
|
|
||||||
void finishAnimating();
|
|
||||||
|
|
||||||
void setRecordStartCallback(Fn<void()> callback) {
|
|
||||||
_recordStartCallback = std::move(callback);
|
|
||||||
}
|
|
||||||
void setRecordUpdateCallback(Fn<void(QPoint globalPos)> callback) {
|
|
||||||
_recordUpdateCallback = std::move(callback);
|
|
||||||
}
|
|
||||||
void setRecordStopCallback(Fn<void(bool active)> callback) {
|
|
||||||
_recordStopCallback = std::move(callback);
|
|
||||||
}
|
|
||||||
void setRecordAnimationCallback(Fn<void()> callback) {
|
|
||||||
_recordAnimationCallback = std::move(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
float64 recordActiveRatio() {
|
|
||||||
return _a_recordActive.value(_recordActive ? 1. : 0.);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void mouseMoveEvent(QMouseEvent *e) override;
|
|
||||||
void paintEvent(QPaintEvent *e) override;
|
|
||||||
void onStateChanged(State was, StateChangeSource source) override;
|
|
||||||
|
|
||||||
QImage prepareRippleMask() const override;
|
|
||||||
QPoint prepareRippleStartPosition() const override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void recordAnimationCallback();
|
|
||||||
QPixmap grabContent();
|
|
||||||
bool isSlowmode() const;
|
|
||||||
|
|
||||||
void paintRecord(Painter &p, bool over);
|
|
||||||
void paintSave(Painter &p, bool over);
|
|
||||||
void paintCancel(Painter &p, bool over);
|
|
||||||
void paintSend(Painter &p, bool over);
|
|
||||||
void paintSchedule(Painter &p, bool over);
|
|
||||||
void paintSlowmode(Painter &p);
|
|
||||||
|
|
||||||
Type _type = Type::Send;
|
|
||||||
Type _afterSlowmodeType = Type::Send;
|
|
||||||
bool _recordActive = false;
|
|
||||||
QPixmap _contentFrom, _contentTo;
|
|
||||||
|
|
||||||
Ui::Animations::Simple _a_typeChanged;
|
|
||||||
Ui::Animations::Simple _a_recordActive;
|
|
||||||
|
|
||||||
bool _recording = false;
|
|
||||||
Fn<void()> _recordStartCallback;
|
|
||||||
Fn<void(bool active)> _recordStopCallback;
|
|
||||||
Fn<void(QPoint globalPos)> _recordUpdateCallback;
|
|
||||||
Fn<void()> _recordAnimationCallback;
|
|
||||||
|
|
||||||
int _slowmodeDelay = 0;
|
|
||||||
QString _slowmodeDelayText;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
class UserpicButton : public RippleButton {
|
class UserpicButton : public RippleButton {
|
||||||
public:
|
public:
|
||||||
enum class Role {
|
enum class Role {
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#include <QtGui/QPainter>
|
#include <QtGui/QPainter>
|
||||||
#include <QtGui/QImage>
|
#include <QtGui/QImage>
|
||||||
#include <QtGui/QPixmap>
|
#include <QtGui/QPixmap>
|
||||||
|
#include <QtGui/QtEvents>
|
||||||
|
|
||||||
#include <QtWidgets/QWidget>
|
#include <QtWidgets/QWidget>
|
||||||
|
|
||||||
|
@ -34,3 +35,4 @@
|
||||||
|
|
||||||
#include "ui/text/text.h"
|
#include "ui/text/text.h"
|
||||||
#include "ui/effects/animations.h"
|
#include "ui/effects/animations.h"
|
||||||
|
#include "styles/palette.h"
|
||||||
|
|
|
@ -31,17 +31,24 @@ nice_target_sources(td_ui ${src_loc}
|
||||||
PRIVATE
|
PRIVATE
|
||||||
${style_files}
|
${style_files}
|
||||||
|
|
||||||
ui/ui_pch.h
|
ui/chat/attach/attach_extensions.cpp
|
||||||
|
ui/chat/attach/attach_extensions.h
|
||||||
ui/chat/message_bar.cpp
|
ui/chat/message_bar.cpp
|
||||||
ui/chat/message_bar.h
|
ui/chat/message_bar.h
|
||||||
ui/chat/pinned_bar.cpp
|
ui/chat/pinned_bar.cpp
|
||||||
ui/chat/pinned_bar.h
|
ui/chat/pinned_bar.h
|
||||||
|
ui/controls/emoji_button.cpp
|
||||||
|
ui/controls/emoji_button.h
|
||||||
|
ui/controls/send_button.cpp
|
||||||
|
ui/controls/send_button.h
|
||||||
ui/text/format_values.cpp
|
ui/text/format_values.cpp
|
||||||
ui/text/format_values.h
|
ui/text/format_values.h
|
||||||
ui/text/text_options.cpp
|
ui/text/text_options.cpp
|
||||||
ui/text/text_options.h
|
ui/text/text_options.h
|
||||||
ui/toasts/common_toasts.cpp
|
ui/toasts/common_toasts.cpp
|
||||||
ui/toasts/common_toasts.h
|
ui/toasts/common_toasts.h
|
||||||
|
|
||||||
|
ui/ui_pch.h
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(td_ui
|
target_include_directories(td_ui
|
||||||
|
|
Loading…
Add table
Reference in a new issue