mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-08-06 11:52:58 +00:00
Marked ephemeral bot commands with hover hint in autocomplete.
This commit is contained in:
@@ -860,6 +860,9 @@ mentionFg: windowSubTextFg;
|
||||
mentionFgOver: windowSubTextFgOver;
|
||||
mentionFgActive: windowActiveTextFg;
|
||||
mentionFgOverActive: windowActiveTextFg;
|
||||
mentionEphemeralIcon: icon {{ "chat/ephemeral_badge", mentionFg }};
|
||||
mentionEphemeralIconOver: icon {{ "chat/ephemeral_badge", mentionFgOver }};
|
||||
mentionEphemeralIconSkip: 5px;
|
||||
|
||||
autocompleteSearchPadding: margins(16px, 5px, 16px, 5px);
|
||||
autocompleteRowPadding: margins(16px, 5px, 16px, 5px);
|
||||
|
||||
@@ -37,7 +37,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/widgets/tooltip.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/effects/path_shift_gradient.h"
|
||||
#include "ui/painter.h"
|
||||
@@ -52,6 +55,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_widgets.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
@@ -59,6 +63,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace ChatHelpers {
|
||||
namespace {
|
||||
|
||||
constexpr auto kEphemeralHintHoverDelay = crl::time(500);
|
||||
|
||||
[[nodiscard]] QString PrimaryUsername(not_null<UserData*> user) {
|
||||
const auto &usernames = user->usernames();
|
||||
return usernames.empty() ? user->username() : usernames.front();
|
||||
@@ -111,6 +117,7 @@ public:
|
||||
botCommandChosen() const;
|
||||
rpl::producer<FieldAutocomplete::StickerChosen> stickerChosen() const;
|
||||
rpl::producer<ScrollTo> scrollToRequested() const;
|
||||
rpl::producer<QRect> ephemeralIconHovered() const;
|
||||
|
||||
void onParentGeometryChanged();
|
||||
|
||||
@@ -132,6 +139,8 @@ private:
|
||||
void setSel(int sel, bool scroll = false);
|
||||
void showPreview();
|
||||
void selectByMouse(QPoint global);
|
||||
[[nodiscard]] QRect ephemeralIconRect(int index) const;
|
||||
void updateEphemeralIconHover(QPoint position);
|
||||
|
||||
QSize stickerBoundingBox() const;
|
||||
void setupLottie(StickerSuggestion &suggestion);
|
||||
@@ -158,6 +167,7 @@ private:
|
||||
int _stickersPerRow = 1;
|
||||
int _sel = -1;
|
||||
int _down = -1;
|
||||
int _ephemeralIconHover = -1;
|
||||
std::optional<QPoint> _lastMousePosition;
|
||||
bool _mouseSelection = false;
|
||||
|
||||
@@ -177,6 +187,7 @@ private:
|
||||
rpl::event_stream<FieldAutocomplete::BotCommandChosen> _botCommandChosen;
|
||||
rpl::event_stream<FieldAutocomplete::StickerChosen> _stickerChosen;
|
||||
rpl::event_stream<ScrollTo> _scrollToRequested;
|
||||
rpl::event_stream<QRect> _ephemeralIconHovered;
|
||||
|
||||
base::Timer _previewTimer;
|
||||
|
||||
@@ -214,6 +225,7 @@ struct FieldAutocomplete::BotCommandRow {
|
||||
QString description;
|
||||
Ui::PeerUserpicView userpic;
|
||||
Ui::Text::String descriptionText;
|
||||
bool ephemeral = false;
|
||||
};
|
||||
|
||||
FieldAutocomplete::FieldAutocomplete(
|
||||
@@ -224,7 +236,8 @@ FieldAutocomplete::FieldAutocomplete(
|
||||
, _show(std::move(show))
|
||||
, _session(&_show->session())
|
||||
, _st(stOverride ? *stOverride : st::defaultEmojiPan)
|
||||
, _scroll(this) {
|
||||
, _scroll(this)
|
||||
, _ephemeralHintTimer([=] { showPendingEphemeralHint(); }) {
|
||||
hide();
|
||||
|
||||
_scroll->setGeometry(rect());
|
||||
@@ -245,6 +258,16 @@ FieldAutocomplete::FieldAutocomplete(
|
||||
_scroll->scrollToY(data.top, data.bottom);
|
||||
}, lifetime());
|
||||
|
||||
_scroll->scrollTopValue(
|
||||
) | rpl::skip(1) | rpl::on_next([=] {
|
||||
hideEphemeralHint();
|
||||
}, lifetime());
|
||||
|
||||
_inner->ephemeralIconHovered(
|
||||
) | rpl::on_next([=](QRect iconRect) {
|
||||
ephemeralIconHovered(iconRect);
|
||||
}, lifetime());
|
||||
|
||||
_scroll->show();
|
||||
_inner->show();
|
||||
|
||||
@@ -671,10 +694,11 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
|
||||
not_null<UserData*> user,
|
||||
const Data::BotCommand &command) {
|
||||
return BotCommandRow{
|
||||
user,
|
||||
command.command,
|
||||
command.description,
|
||||
user->activeUserpicView()
|
||||
.user = user,
|
||||
.command = command.command,
|
||||
.description = command.description,
|
||||
.userpic = user->activeUserpicView(),
|
||||
.ephemeral = command.ephemeral,
|
||||
};
|
||||
};
|
||||
brows.reserve(cnt);
|
||||
@@ -787,6 +811,58 @@ void FieldAutocomplete::rowsUpdated(
|
||||
_inner->rowsUpdated();
|
||||
}
|
||||
|
||||
void FieldAutocomplete::createEphemeralHint(QRect rect) {
|
||||
const auto parent = parentWidget();
|
||||
_ephemeralHint = base::make_unique_q<Ui::ImportantTooltip>(
|
||||
parent,
|
||||
object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>(
|
||||
parent,
|
||||
Ui::MakeNiceTooltipLabel(
|
||||
parent,
|
||||
tr::lng_ephemeral_command_tooltip(Ui::Text::WithEntities),
|
||||
st::dialogsStoriesTooltipMaxWidth,
|
||||
st::ttlMediaImportantTooltipLabel),
|
||||
st::defaultImportantTooltip.padding),
|
||||
st::dialogsStoriesTooltip);
|
||||
_ephemeralHint->pointAt(rect, RectPart::Top);
|
||||
_ephemeralHint->toggleAnimated(true);
|
||||
}
|
||||
|
||||
void FieldAutocomplete::ephemeralIconHovered(QRect iconRect) {
|
||||
const auto parent = parentWidget();
|
||||
if (iconRect.isEmpty() || !parent || isHidden() || _hiding) {
|
||||
hideEphemeralHint();
|
||||
return;
|
||||
}
|
||||
_ephemeralHintRect = Ui::MapFrom(parent, _inner.data(), iconRect);
|
||||
if (_ephemeralHint && !_ephemeralHint->isHidden()) {
|
||||
_ephemeralHint->pointAt(_ephemeralHintRect, RectPart::Top);
|
||||
_ephemeralHint->toggleAnimated(true);
|
||||
} else {
|
||||
_ephemeralHintTimer.callOnce(kEphemeralHintHoverDelay);
|
||||
}
|
||||
}
|
||||
|
||||
void FieldAutocomplete::showPendingEphemeralHint() {
|
||||
if (_ephemeralHintRect.isEmpty() || isHidden() || _hiding) {
|
||||
return;
|
||||
}
|
||||
if (_ephemeralHint) {
|
||||
_ephemeralHint->pointAt(_ephemeralHintRect, RectPart::Top);
|
||||
_ephemeralHint->toggleAnimated(true);
|
||||
} else {
|
||||
createEphemeralHint(_ephemeralHintRect);
|
||||
}
|
||||
}
|
||||
|
||||
void FieldAutocomplete::hideEphemeralHint() {
|
||||
_ephemeralHintRect = QRect();
|
||||
_ephemeralHintTimer.cancel();
|
||||
if (_ephemeralHint) {
|
||||
_ephemeralHint->toggleAnimated(false);
|
||||
}
|
||||
}
|
||||
|
||||
void FieldAutocomplete::setBoundings(QRect boundings) {
|
||||
_boundings = boundings;
|
||||
recount();
|
||||
@@ -829,6 +905,8 @@ void FieldAutocomplete::recount(bool resetScroll) {
|
||||
}
|
||||
|
||||
void FieldAutocomplete::hideFast() {
|
||||
hideEphemeralHint();
|
||||
_ephemeralHint = nullptr;
|
||||
_a_opacity.stop();
|
||||
hideFinish();
|
||||
}
|
||||
@@ -837,6 +915,7 @@ void FieldAutocomplete::hideAnimated() {
|
||||
if (isHidden() || _hiding) {
|
||||
return;
|
||||
}
|
||||
hideEphemeralHint();
|
||||
|
||||
if (_cache.isNull()) {
|
||||
_scroll->show();
|
||||
@@ -1209,6 +1288,19 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
|
||||
auto addleft = commandTextWidth + st::mentionPadding.left();
|
||||
auto widthleft = mentionwidth - addleft;
|
||||
|
||||
if (row.ephemeral) {
|
||||
const auto &icon = selected
|
||||
? st::mentionEphemeralIconOver
|
||||
: st::mentionEphemeralIcon;
|
||||
icon.paint(
|
||||
p,
|
||||
mentionleft + addleft,
|
||||
(i * st::mentionHeight
|
||||
+ (st::mentionHeight - icon.height()) / 2),
|
||||
width());
|
||||
addleft += icon.width() + st::mentionEphemeralIconSkip;
|
||||
widthleft -= icon.width() + st::mentionEphemeralIconSkip;
|
||||
}
|
||||
if (!row.description.isEmpty()
|
||||
&& row.descriptionText.isEmpty()) {
|
||||
row.descriptionText.setText(
|
||||
@@ -1243,6 +1335,7 @@ void FieldAutocomplete::Inner::resizeEvent(QResizeEvent *e) {
|
||||
|
||||
void FieldAutocomplete::Inner::mouseMoveEvent(QMouseEvent *e) {
|
||||
const auto globalPosition = e->globalPos();
|
||||
updateEphemeralIconHover(e->pos());
|
||||
if (!_lastMousePosition) {
|
||||
_lastMousePosition = globalPosition;
|
||||
return;
|
||||
@@ -1253,6 +1346,57 @@ void FieldAutocomplete::Inner::mouseMoveEvent(QMouseEvent *e) {
|
||||
selectByMouse(globalPosition);
|
||||
}
|
||||
|
||||
void FieldAutocomplete::Inner::updateEphemeralIconHover(QPoint position) {
|
||||
const auto inCommands = !_brows->empty()
|
||||
&& _srows->empty()
|
||||
&& _mrows->empty()
|
||||
&& _hrows->empty();
|
||||
const auto index = inCommands ? (position.y() / st::mentionHeight) : -1;
|
||||
const auto good = (index >= 0)
|
||||
&& (index < int(_brows->size()))
|
||||
&& _brows->at(index).ephemeral
|
||||
&& ephemeralIconRect(index).contains(position);
|
||||
const auto hovered = good ? index : -1;
|
||||
if (_ephemeralIconHover == hovered) {
|
||||
return;
|
||||
}
|
||||
_ephemeralIconHover = hovered;
|
||||
_ephemeralIconHovered.fire(good
|
||||
? ephemeralIconRect(hovered)
|
||||
: QRect());
|
||||
}
|
||||
|
||||
QRect FieldAutocomplete::Inner::ephemeralIconRect(int index) const {
|
||||
const auto &row = _brows->at(index);
|
||||
if (!row.ephemeral) {
|
||||
return QRect();
|
||||
}
|
||||
const auto filter = _parent->filter();
|
||||
const auto hasUsername = filter.indexOf('@') > 0;
|
||||
const auto botStatus = _parent->chat()
|
||||
? _parent->chat()->botStatus
|
||||
: ((_parent->channel() && _parent->channel()->isMegagroup())
|
||||
? _parent->channel()->mgInfo->botStatus
|
||||
: Data::BotStatus::NoBots);
|
||||
auto toHighlight = row.command;
|
||||
if (hasUsername || botStatus != Data::BotStatus::NoBots) {
|
||||
toHighlight += '@' + PrimaryUsername(row.user);
|
||||
}
|
||||
const auto mentionleft = 2 * st::mentionPadding.left()
|
||||
+ st::mentionPhotoSize;
|
||||
const auto left = mentionleft
|
||||
+ st::semiboldFont->width('/' + toHighlight)
|
||||
+ st::mentionPadding.left();
|
||||
const auto top = index * st::mentionHeight
|
||||
+ (st::mentionHeight - st::mentionEphemeralIcon.height()) / 2;
|
||||
return QRect(
|
||||
left,
|
||||
top,
|
||||
st::mentionEphemeralIcon.width(),
|
||||
st::mentionEphemeralIcon.height());
|
||||
}
|
||||
|
||||
|
||||
void FieldAutocomplete::Inner::clearSel(bool hidden) {
|
||||
_overDelete = false;
|
||||
_mouseSelection = false;
|
||||
@@ -1478,6 +1622,10 @@ void FieldAutocomplete::Inner::enterEventHook(QEnterEvent *e) {
|
||||
|
||||
void FieldAutocomplete::Inner::leaveEventHook(QEvent *e) {
|
||||
setMouseTracking(false);
|
||||
if (_ephemeralIconHover >= 0) {
|
||||
_ephemeralIconHover = -1;
|
||||
_ephemeralIconHovered.fire(QRect());
|
||||
}
|
||||
if (_mouseSelection) {
|
||||
setSel(-1);
|
||||
_mouseSelection = false;
|
||||
@@ -1536,6 +1684,10 @@ void FieldAutocomplete::Inner::rowsUpdated() {
|
||||
if (_srows->empty()) {
|
||||
_stickersLifetime.destroy();
|
||||
}
|
||||
if (_ephemeralIconHover >= 0) {
|
||||
_ephemeralIconHover = -1;
|
||||
_ephemeralIconHovered.fire(QRect());
|
||||
}
|
||||
}
|
||||
|
||||
auto FieldAutocomplete::Inner::getLottieRenderer()
|
||||
@@ -1721,6 +1873,10 @@ auto FieldAutocomplete::Inner::scrollToRequested() const
|
||||
return _scrollToRequested.events();
|
||||
}
|
||||
|
||||
rpl::producer<QRect> FieldAutocomplete::Inner::ephemeralIconHovered() const {
|
||||
return _ephemeralIconHovered.events();
|
||||
}
|
||||
|
||||
void InitFieldAutocomplete(
|
||||
std::unique_ptr<FieldAutocomplete> &autocomplete,
|
||||
FieldAutocompleteDescriptor &&descriptor) {
|
||||
|
||||
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/rp_widget.h"
|
||||
#include "base/timer.h"
|
||||
#include "base/object_ptr.h"
|
||||
#include "base/unique_qptr.h"
|
||||
|
||||
namespace style {
|
||||
struct EmojiPan;
|
||||
@@ -22,6 +23,7 @@ namespace Ui {
|
||||
class PopupMenu;
|
||||
class ScrollArea;
|
||||
class InputField;
|
||||
class ImportantTooltip;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Lottie {
|
||||
@@ -165,6 +167,10 @@ private:
|
||||
void updateFiltered(bool resetScroll = false);
|
||||
void recount(bool resetScroll = false);
|
||||
StickerRows getStickerSuggestions();
|
||||
void createEphemeralHint(QRect rect);
|
||||
void ephemeralIconHovered(QRect iconRect);
|
||||
void showPendingEphemeralHint();
|
||||
void hideEphemeralHint();
|
||||
|
||||
const std::shared_ptr<Show> _show;
|
||||
const not_null<Main::Session*> _session;
|
||||
@@ -197,6 +203,10 @@ private:
|
||||
|
||||
bool _hiding = false;
|
||||
|
||||
base::unique_qptr<Ui::ImportantTooltip> _ephemeralHint;
|
||||
base::Timer _ephemeralHintTimer;
|
||||
QRect _ephemeralHintRect;
|
||||
|
||||
Ui::Animations::Simple _a_opacity;
|
||||
rpl::event_stream<> _refreshRequests;
|
||||
rpl::event_stream<> _stickersUpdateRequests;
|
||||
|
||||
Reference in New Issue
Block a user