Show a tooltip about the emoji section toggling.

This commit is contained in:
John Preston 2017-05-17 12:40:03 +03:00
parent 0eb2d28d90
commit b88a49d2f8
12 changed files with 391 additions and 11 deletions

View File

@ -541,3 +541,7 @@ callBarMuteRipple: dialogsRippleBgActive; // active phone call bar mute and hang
callBarBgMuted: #8f8f8f | dialogsUnreadBgMuted; // phone call bar with muted mic background
callBarUnmuteRipple: #7f7f7f | shadowFg; // phone call bar with muted mic mute and hangup button ripple effect
callBarFg: dialogsNameFgActive; // phone call bar text and icons
importantTooltipBg: toastBg;
importantTooltipFg: toastFg;
importantTooltipFgLink: mediaviewTextLinkFg;

View File

@ -780,6 +780,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_emoji_category5" = "Travel & Places";
"lng_emoji_category6" = "Objects";
"lng_emoji_category7" = "Symbols & Flags";
"lng_emoji_hide_panel" = "Click here to hide the emoji sidebar";
"lng_recent_stickers" = "Frequently used";
"lng_switch_stickers" = "Stickers";

View File

@ -58,6 +58,7 @@ QByteArray AuthSessionData::serialize() const {
for (auto i = _variables.soundOverrides.cbegin(), e = _variables.soundOverrides.cend(); i != e; ++i) {
stream << i.key() << i.value();
}
stream << qint32(_variables.tabbedSelectorSectionTooltipShown);
}
return result;
}
@ -77,6 +78,7 @@ void AuthSessionData::constructFromSerialized(const QByteArray &serialized) {
qint32 emojiPanTab = static_cast<qint32>(EmojiPanelTab::Emoji);
qint32 lastSeenWarningSeen = 0;
qint32 tabbedSelectorSectionEnabled = 1;
qint32 tabbedSelectorSectionTooltipShown = 0;
QMap<QString, QString> soundOverrides;
stream >> emojiPanTab;
stream >> lastSeenWarningSeen;
@ -94,6 +96,9 @@ void AuthSessionData::constructFromSerialized(const QByteArray &serialized) {
}
}
}
if (!stream.atEnd()) {
stream >> tabbedSelectorSectionTooltipShown;
}
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: Bad data for AuthSessionData::constructFromSerialized()"));
return;
@ -108,6 +113,7 @@ void AuthSessionData::constructFromSerialized(const QByteArray &serialized) {
_variables.lastSeenWarningSeen = (lastSeenWarningSeen == 1);
_variables.tabbedSelectorSectionEnabled = (tabbedSelectorSectionEnabled == 1);
_variables.soundOverrides = std::move(soundOverrides);
_variables.tabbedSelectorSectionTooltipShown = tabbedSelectorSectionTooltipShown;
}
QString AuthSessionData::getSoundPath(const QString &key) const {

View File

@ -96,12 +96,19 @@ public:
_variables.soundOverrides.clear();
}
QString getSoundPath(const QString &key) const;
void setTabbedSelectorSectionTooltipShown(int shown) {
_variables.tabbedSelectorSectionTooltipShown = shown;
}
int tabbedSelectorSectionTooltipShown() const {
return _variables.tabbedSelectorSectionTooltipShown;
}
private:
struct Variables {
bool lastSeenWarningSeen = false;
EmojiPanelTab emojiPanelTab = EmojiPanelTab::Emoji;
bool tabbedSelectorSectionEnabled = true;
int tabbedSelectorSectionTooltipShown = 0;
QMap<QString, QString> soundOverrides;
};

View File

@ -225,6 +225,9 @@ historyAttachEmoji: IconButton(historyAttach) {
iconOver: icon {{ "send_control_emoji", historyComposeIconFgOver }};
iconPosition: point(15px, 15px);
}
historyAttachEmojiFgActive: windowBgActive;
historyAttachEmojiActive: icon {{ "send_control_emoji", historyAttachEmojiFgActive }};
historyAttachEmojiTooltipDelta: 4px;
historyEmojiCircle: size(20px, 20px);
historyEmojiCirclePeriod: 1500;
historyEmojiCircleDuration: 500;

View File

@ -35,6 +35,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/widgets/buttons.h"
#include "ui/widgets/inner_dropdown.h"
#include "ui/widgets/dropdown_menu.h"
#include "ui/widgets/labels.h"
#include "ui/effects/ripple_animation.h"
#include "inline_bots/inline_bot_result.h"
#include "data/data_drafts.h"
@ -74,10 +75,12 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
namespace {
constexpr auto kStickersUpdateTimeout = 3600000; // update not more than once in an hour
constexpr auto kSaveTabbedSelectorSectionTimeout = 1000;
constexpr auto kSaveTabbedSelectorSectionTimeoutMs = 1000;
constexpr auto kMessagesPerPageFirst = 30;
constexpr auto kMessagesPerPage = 50;
constexpr auto kPreloadHeightsCount = 3; // when 3 screens to scroll left make a preload request
constexpr auto kTabbedSelectorToggleTooltipTimeoutMs = 3000;
constexpr auto kTabbedSelectorToggleTooltipCount = 3;
ApiWrap::RequestMessageDataCallback replyEditMessageDataCallback() {
return [](ChannelData *channel, MsgId msgId) {
@ -739,6 +742,9 @@ void HistoryWidget::orderWidgets() {
if (_tabbedPanel) {
_tabbedPanel->raise();
}
if (_tabbedSelectorToggleTooltip) {
_tabbedSelectorToggleTooltip->raise();
}
_attachDragDocument->raise();
_attachDragPhoto->raise();
}
@ -2195,6 +2201,7 @@ void HistoryWidget::updateControlsVisibility() {
update();
}
}
checkTabbedSelectorToggleTooltip();
updateMouseTracking();
}
@ -3723,6 +3730,7 @@ void HistoryWidget::updateTabbedSelectorSectionShown() {
if (_tabbedSectionUsed) {
_tabbedSection.create(this, _controller, _tabbedPanel->takeSelector());
_tabbedSection->setCancelledCallback([this] { setInnerFocus(); });
_tabbedSelectorToggle->setColorOverrides(&st::historyAttachEmojiActive, &st::historyRecordVoiceFgActive, &st::historyRecordVoiceRippleBgActive);
_rightShadow.create(this, st::shadowFg);
auto destroyingPanel = std::move(_tabbedPanel);
updateControlsVisibility();
@ -3730,11 +3738,37 @@ void HistoryWidget::updateTabbedSelectorSectionShown() {
_tabbedPanel.create(this, _controller, _tabbedSection->takeSelector());
_tabbedSelectorToggle->installEventFilter(_tabbedPanel);
_tabbedSection.destroy();
_tabbedSelectorToggle->setColorOverrides(nullptr, nullptr, nullptr);
_rightShadow.destroy();
_tabbedSelectorToggleTooltipShown = false;
}
checkTabbedSelectorToggleTooltip();
orderWidgets();
}
void HistoryWidget::checkTabbedSelectorToggleTooltip() {
if (_tabbedSection && !_tabbedSection->isHidden() && !_tabbedSelectorToggle->isHidden()) {
if (!_tabbedSelectorToggleTooltipShown) {
auto shownCount = AuthSession::Current().data().tabbedSelectorSectionTooltipShown();
if (shownCount < kTabbedSelectorToggleTooltipCount) {
AuthSession::Current().data().setTabbedSelectorSectionTooltipShown(shownCount + 1);
AuthSession::Current().saveDataDelayed(kTabbedSelectorToggleTooltipTimeoutMs);
_tabbedSelectorToggleTooltipShown = true;
_tabbedSelectorToggleTooltip.create(this, object_ptr<Ui::FlatLabel>(this, lang(lng_emoji_hide_panel), Ui::FlatLabel::InitType::Simple, st::defaultImportantTooltipLabel), st::defaultImportantTooltip);
updateTabbedSelectorToggleTooltipGeometry();
_tabbedSelectorToggleTooltip->setHiddenCallback([this] {
_tabbedSelectorToggleTooltip.destroy();
});
_tabbedSelectorToggleTooltip->hideAfter(kTabbedSelectorToggleTooltipTimeoutMs);
_tabbedSelectorToggleTooltip->toggleAnimated(true);
}
}
} else {
_tabbedSelectorToggleTooltip.destroy();
}
}
int HistoryWidget::tabbedSelectorSectionWidth() const {
return st::emojiPanWidth;
}
@ -3747,14 +3781,14 @@ void HistoryWidget::toggleTabbedSelectorMode() {
auto sectionEnabled = AuthSession::Current().data().tabbedSelectorSectionEnabled();
if (_tabbedSection) {
AuthSession::Current().data().setTabbedSelectorSectionEnabled(false);
AuthSession::Current().saveDataDelayed(kSaveTabbedSelectorSectionTimeout);
AuthSession::Current().saveDataDelayed(kSaveTabbedSelectorSectionTimeoutMs);
updateTabbedSelectorSectionShown();
recountChatWidth();
updateControlsGeometry();
} else if (_controller->provideChatWidth(minimalWidthForTabbedSelectorSection())) {
if (!AuthSession::Current().data().tabbedSelectorSectionEnabled()) {
AuthSession::Current().data().setTabbedSelectorSectionEnabled(true);
AuthSession::Current().saveDataDelayed(kSaveTabbedSelectorSectionTimeout);
AuthSession::Current().saveDataDelayed(kSaveTabbedSelectorSectionTimeoutMs);
}
updateTabbedSelectorSectionShown();
recountChatWidth();
@ -3884,6 +3918,7 @@ void HistoryWidget::moveFieldControls() {
auto right = (width() - _chatWidth) + st::historySendRight;
_send->moveToRight(right, buttonsBottom); right += _send->width();
_tabbedSelectorToggle->moveToRight(right, buttonsBottom);
updateTabbedSelectorToggleTooltipGeometry();
_botKeyboardHide->moveToRight(right, buttonsBottom); right += _botKeyboardHide->width();
_botKeyboardShow->moveToRight(right, buttonsBottom);
_botCommandStart->moveToRight(right, buttonsBottom);
@ -3904,6 +3939,15 @@ void HistoryWidget::moveFieldControls() {
_muteUnmute->setGeometry(fullWidthButtonRect);
}
void HistoryWidget::updateTabbedSelectorToggleTooltipGeometry() {
if (_tabbedSelectorToggleTooltip) {
auto toggle = _tabbedSelectorToggle->geometry();
auto margin = st::historyAttachEmojiTooltipDelta;
auto margins = QMargins(margin, margin, margin, margin);
_tabbedSelectorToggleTooltip->pointAt(toggle.marginsRemoved(margins));
}
}
void HistoryWidget::updateFieldSize() {
auto kbShowShown = _history && !_kbShown && _keyboard->hasMarkup();
auto fieldWidth = _chatWidth - _attachToggle->width() - st::historySendRight;

View File

@ -510,6 +510,8 @@ private:
// like send button, emoji button and others.
void moveFieldControls();
void updateFieldSize();
void updateTabbedSelectorToggleTooltipGeometry();
void checkTabbedSelectorToggleTooltip();
bool historyHasNotFreezedUnreadBar(History *history) const;
bool canWriteMessage() const;
@ -755,6 +757,8 @@ private:
mtpRequestId _reportSpamRequest = 0;
object_ptr<Ui::IconButton> _attachToggle;
object_ptr<Ui::EmojiButton> _tabbedSelectorToggle;
object_ptr<Ui::ImportantTooltip> _tabbedSelectorToggleTooltip = { nullptr };
bool _tabbedSelectorToggleTooltipShown = false;
object_ptr<Ui::IconButton> _botKeyboardShow;
object_ptr<Ui::IconButton> _botKeyboardHide;
object_ptr<Ui::IconButton> _botCommandStart;

View File

@ -90,17 +90,17 @@ void EmojiButton::paintEvent(QPaintEvent *e) {
auto ms = getms();
p.fillRect(e->rect(), st::historyComposeAreaBg);
paintRipple(p, _st.rippleAreaPosition.x(), _st.rippleAreaPosition.y(), ms);
paintRipple(p, _st.rippleAreaPosition.x(), _st.rippleAreaPosition.y(), ms, _rippleOverride ? &(*_rippleOverride)->c : nullptr);
auto loading = a_loading.current(ms, _loading ? 1 : 0);
p.setOpacity(1 - loading);
auto over = isOver();
auto icon = &(over ? _st.iconOver : _st.icon);
auto icon = _iconOverride ? _iconOverride : &(over ? _st.iconOver : _st.icon);
icon->paint(p, _st.iconPosition, width());
p.setOpacity(1.);
auto pen = (over ? st::historyEmojiCircleFgOver : st::historyEmojiCircleFg)->p;
auto pen = _colorOverride ? (*_colorOverride)->p : (over ? st::historyEmojiCircleFgOver : st::historyEmojiCircleFg)->p;
pen.setWidth(st::historyEmojiCircleLine);
pen.setCapStyle(Qt::RoundCap);
p.setPen(pen);
@ -130,6 +130,13 @@ void EmojiButton::setLoading(bool loading) {
}
}
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);

View File

@ -55,6 +55,7 @@ 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;
@ -64,17 +65,21 @@ protected:
QPoint prepareRippleStartPosition() const override;
private:
void step_loading(TimeMs ms, bool timer) {
if (timer) {
update();
}
}
const style::IconButton &_st;
bool _loading = false;
Animation a_loading;
BasicAnimation _a_loading;
void step_loading(TimeMs ms, bool timer) {
if (timer) {
update();
}
}
const style::icon *_iconOverride = nullptr;
const style::color *_colorOverride = nullptr;
const style::color *_rippleOverride = nullptr;
};

View File

@ -202,4 +202,213 @@ void Tooltip::Hide() {
}
}
ImportantTooltip::ImportantTooltip(QWidget *parent, object_ptr<TWidget> content, const style::ImportantTooltip &st) : TWidget(parent)
, _st(st)
, _content(std::move(content)) {
_content->setParent(this);
_hideTimer.setCallback([this] { toggleAnimated(false); });
hide();
}
void ImportantTooltip::pointAt(QRect area, Side side) {
if (_area == area && _side == side) {
return;
}
setArea(area);
countApproachSide(side);
updateGeometry();
update();
}
void ImportantTooltip::setArea(QRect area) {
Expects(parentWidget() != nullptr);
_area = area;
auto point = parentWidget()->mapToGlobal(_area.center());
_useTransparency = Platform::TranslucentWindowsSupported(point);
setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency);
auto contentWidth = parentWidget()->rect().marginsRemoved(_st.padding).width();
accumulate_min(contentWidth, _content->naturalWidth());
_content->resizeToWidth(contentWidth);
auto size = _content->rect().marginsAdded(_st.padding).size();
if (_useTransparency) {
size.setHeight(size.height() + _st.arrow);
}
if (size.width() < 2 * (_st.arrowSkipMin + _st.arrow)) {
size.setWidth(2 * (_st.arrowSkipMin + _st.arrow));
}
resize(size);
}
void ImportantTooltip::countApproachSide(Side preferSide) {
Expects(parentWidget() != nullptr);
auto requiredSpace = countInner().height() + _st.shift;
if (_useTransparency) {
requiredSpace += _st.arrow;
}
auto available = parentWidget()->rect();
auto availableAbove = _area.y() - available.y();
auto availableBelow = (available.y() + available.height()) - (_area.y() + _area.height());
auto allowedAbove = (availableAbove >= requiredSpace + _st.margin.top());
auto allowedBelow = (availableBelow >= requiredSpace + _st.margin.bottom());
if ((allowedAbove && allowedBelow) || (!allowedAbove && !allowedBelow)) {
_side = preferSide;
} else {
_side = (allowedAbove ? SideFlag::Up : SideFlag::Down) | (preferSide & (SideFlag::Left | SideFlag::Center | SideFlag::Right));
}
if (_useTransparency) {
auto arrow = QImage(QSize(_st.arrow * 2, _st.arrow) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
arrow.fill(Qt::transparent);
arrow.setDevicePixelRatio(cRetinaFactor());
{
Painter p(&arrow);
PainterHighQualityEnabler hq(p);
QPainterPath path;
path.moveTo(0, 0);
path.lineTo(2 * _st.arrow, 0);
path.lineTo(_st.arrow, _st.arrow);
path.lineTo(0, 0);
p.fillPath(path, _st.bg);
}
if (_side & SideFlag::Down) {
arrow = std::move(arrow).transformed(QTransform(1, 0, 0, -1, 0, 0));
}
_arrow = App::pixmapFromImageInPlace(std::move(arrow));
}
}
void ImportantTooltip::toggleAnimated(bool visible) {
if (_visible == isHidden()) {
setVisible(_visible);
}
if (_visible != visible) {
updateGeometry();
_visible = visible;
refreshAnimationCache();
if (_visible) {
show();
} else if (isHidden()) {
return;
}
hideChildren();
_visibleAnimation.start([this] { animationCallback(); }, _visible ? 0. : 1., _visible ? 1. : 0., _st.duration, anim::easeOutCirc);
}
}
void ImportantTooltip::hideAfter(TimeMs timeout) {
_hideTimer.callOnce(timeout);
}
void ImportantTooltip::animationCallback() {
updateGeometry();
update();
checkAnimationFinish();
}
void ImportantTooltip::refreshAnimationCache() {
if (_cache.isNull() && _useTransparency) {
auto animation = base::take(_visibleAnimation);
auto visible = std::exchange(_visible, true);
showChildren();
_cache = myGrab(this);
_visible = base::take(visible);
_visibleAnimation = base::take(animation);
}
}
void ImportantTooltip::toggleFast(bool visible) {
if (_visible == isHidden()) {
setVisible(_visible);
}
if (_visibleAnimation.animating() || _visible != visible) {
_visibleAnimation.finish();
_visible = visible;
checkAnimationFinish();
}
}
void ImportantTooltip::checkAnimationFinish() {
if (!_visibleAnimation.animating()) {
_cache = QPixmap();
showChildren();
setVisible(_visible);
if (!_visible && _hiddenCallback) {
_hiddenCallback();
}
}
}
void ImportantTooltip::updateGeometry() {
Expects(parentWidget() != nullptr);
auto parent = parentWidget();
auto areaMiddle = _area.x() + (_area.width() / 2);
auto left = areaMiddle - (width() / 2);
if (_side & SideFlag::Left) {
left = areaMiddle + _st.arrowSkip - width();
} else if (_side & SideFlag::Right) {
left = areaMiddle - _st.arrowSkip;
}
accumulate_min(left, parent->width() - _st.margin.right() - width());
accumulate_max(left, _st.margin.left());
accumulate_max(left, areaMiddle + _st.arrow + _st.arrowSkipMin - width());
accumulate_min(left, areaMiddle - _st.arrow - _st.arrowSkipMin);
auto countTop = [this] {
auto shift = anim::interpolate(_st.shift, 0, _visibleAnimation.current(_visible ? 1. : 0.));
if (_side & SideFlag::Up) {
return _area.y() - height() - shift;
}
return _area.y() + _area.height() + shift;
};
move(left, countTop());
}
void ImportantTooltip::resizeEvent(QResizeEvent *e) {
auto inner = countInner();
auto contentTop = _st.padding.top();
if (_useTransparency && (_side & SideFlag::Down)) {
contentTop += _st.arrow;
}
_content->moveToLeft(_st.padding.left(), contentTop);
}
QRect ImportantTooltip::countInner() const {
return _content->geometry().marginsAdded(_st.padding);
}
void ImportantTooltip::paintEvent(QPaintEvent *e) {
Painter p(this);
auto inner = countInner();
if (_useTransparency) {
Platform::StartTranslucentPaint(p, e);
if (!_cache.isNull()) {
auto opacity = _visibleAnimation.current(_visible ? 1. : 0.);
p.setOpacity(opacity);
p.drawPixmap(0, 0, _cache);
} else {
if (!_visible) {
return;
}
p.setBrush(_st.bg);
p.setPen(Qt::NoPen);
{
PainterHighQualityEnabler hq(p);
p.drawRoundedRect(inner, _st.radius, _st.radius);
}
auto areaMiddle = _area.x() + (_area.width() / 2) - x();
auto arrowLeft = areaMiddle - _st.arrow;
if (_side & SideFlag::Up) {
p.drawPixmapLeft(arrowLeft, inner.y() + inner.height(), width(), _arrow);
} else {
p.drawPixmapLeft(arrowLeft, inner.y() - _st.arrow, width(), _arrow);
}
}
} else {
p.fillRect(inner, QColor(_st.bg->c.red(), _st.bg->c.green(), _st.bg->c.blue()));
}
}
} // namespace Ui

View File

@ -21,6 +21,7 @@
namespace style {
struct Tooltip;
struct ImportantTooltip;
} // namespace style
namespace Ui {
@ -74,4 +75,56 @@ private:
};
class ImportantTooltip : public TWidget {
public:
ImportantTooltip(QWidget *parent, object_ptr<TWidget> content, const style::ImportantTooltip &st);
enum class SideFlag {
Up = 0x01,
Down = 0x02,
Left = 0x04,
Center = 0x08,
Right = 0x0c,
};
Q_DECLARE_FLAGS(Side, SideFlag);
void pointAt(QRect area, Side preferSide = Side(SideFlag::Up) | SideFlag::Left);
void toggleAnimated(bool visible);
void toggleFast(bool visible);
void hideAfter(TimeMs timeout);
void setHiddenCallback(base::lambda<void()> callback) {
_hiddenCallback = std::move(callback);
}
protected:
void resizeEvent(QResizeEvent *e);
void paintEvent(QPaintEvent *e);
private:
void animationCallback();
QRect countInner() const;
void setArea(QRect area);
void countApproachSide(Side preferSide);
void updateGeometry();
void checkAnimationFinish();
void refreshAnimationCache();
base::Timer _hideTimer;
const style::ImportantTooltip &_st;
object_ptr<TWidget> _content;
QRect _area;
Side _side = Side(SideFlag::Up) | SideFlag::Left;
QPixmap _arrow;
Animation _visibleAnimation;
bool _visible = false;
base::lambda<void()> _hiddenCallback;
bool _useTransparency = true;
QPixmap _cache;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(ImportantTooltip::Side);
} // namespace Ui

View File

@ -446,6 +446,18 @@ Tooltip {
linesMax: int;
}
ImportantTooltip {
bg: color;
margin: margins;
padding: margins;
radius: pixels;
arrow: pixels;
arrowSkipMin: pixels;
arrowSkip: pixels;
shift: pixels;
duration: int;
}
defaultLabelSimple: LabelSimple {
font: normalFont;
maxWidth: 0px;
@ -824,6 +836,31 @@ defaultTooltip: Tooltip {
linesMax: 12;
}
defaultImportantTooltip: ImportantTooltip {
bg: importantTooltipBg;
margin: margins(4px, 4px, 4px, 4px);
padding: margins(13px, 9px, 13px, 10px);
radius: 6px;
arrow: 9px;
arrowSkipMin: 24px;
arrowSkip: 66px;
shift: 12px;
duration: 200;
}
defaultImportantTooltipLabel: FlatLabel(defaultFlatLabel) {
style: TextStyle(defaultTextStyle) {
font: font(14px);
linkFont: font(14px);
linkFontOver: font(14px underline);
}
textFg: importantTooltipFg;
palette: TextPalette(defaultTextPalette) {
linkFg: importantTooltipFgLink;
selectLinkFg: importantTooltipFgLink;
}
}
historyToDownBelow: icon {
{ "history_down_shadow", historyToDownShadow },
{ "history_down_circle", historyToDownBg, point(4px, 4px) },