Nice animations when selecting shared media items.

This commit is contained in:
John Preston 2017-11-24 19:47:07 +04:00
parent 9dc39cb758
commit 0ced28f991
11 changed files with 442 additions and 189 deletions

View file

@ -534,7 +534,12 @@ void HistoryTopBarWidget::showSelected(SelectedState state) {
setCursor(hasSelected ? style::cur_default : style::cur_pointer);
updateMembersShowArea();
_selectedShown.start([this] { selectedShowCallback(); }, hasSelected ? 0. : 1., hasSelected ? 1. : 0., st::topBarSlideDuration, anim::easeOutCirc);
_selectedShown.start(
[this] { selectedShowCallback(); },
hasSelected ? 0. : 1.,
hasSelected ? 1. : 0.,
st::slideWrapDuration,
anim::easeOutCirc);
} else {
updateControlsGeometry();
}

View file

@ -25,10 +25,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "lang/lang_keys.h"
#include "info/info_wrap_widget.h"
#include "storage/storage_shared_media.h"
#include "ui/effects/numbers_animation.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/widgets/shadow.h"
#include "ui/wrap/fade_wrap.h"
#include "mainwidget.h"
#include "boxes/confirm_box.h"
#include "boxes/peer_list_controllers.h"
@ -44,7 +44,7 @@ TopBarOverride::TopBarOverride(
, _items(std::move(items))
, _canDelete(computeCanDelete())
, _cancel(this, _st.mediaCancel)
, _text(this, generateText(), Ui::FlatLabel::InitType::Simple, _st.title)
, _text(this, _st.title, _st.titlePosition.y(), generateText())
, _forward(this, _st.mediaForward)
, _delete(this, _st.mediaDelete) {
setAttribute(Qt::WA_OpaquePaintEvent);
@ -55,17 +55,18 @@ TopBarOverride::TopBarOverride(
_delete->addClickHandler([this] { performDelete(); });
}
QString TopBarOverride::generateText() const {
Ui::StringWithNumbers TopBarOverride::generateText() const {
using Data = Ui::StringWithNumbers;
using Type = Storage::SharedMediaType;
auto phrase = [&] {
switch (_items.type) {
case Type::Photo: return lng_media_selected_photo;
case Type::Video: return lng_media_selected_video;
case Type::File: return lng_media_selected_file;
case Type::MusicFile: return lng_media_selected_song;
case Type::Link: return lng_media_selected_link;
case Type::VoiceFile: return lng_media_selected_audio;
// case Type::RoundFile: return lng_media_selected_round;
case Type::Photo: return lng_media_selected_photo__generic<Data>;
case Type::Video: return lng_media_selected_video__generic<Data>;
case Type::File: return lng_media_selected_file__generic<Data>;
case Type::MusicFile: return lng_media_selected_song__generic<Data>;
case Type::Link: return lng_media_selected_link__generic<Data>;
case Type::VoiceFile: return lng_media_selected_audio__generic<Data>;
// case Type::RoundFile: return lng_media_selected_round__generic<Data>;
}
Unexpected("Type in TopBarOverride::generateText()");
}();
@ -82,7 +83,7 @@ void TopBarOverride::setItems(SelectedItems &&items) {
_items = std::move(items);
_canDelete = computeCanDelete();
_text->setText(generateText());
_text->setValue(generateText());
updateControlsVisibility();
updateControlsGeometry(width());
}
@ -110,8 +111,14 @@ void TopBarOverride::updateControlsGeometry(int newWidth) {
right += _delete->width();
}
_forward->moveToRight(right, 0, newWidth);
_cancel->moveToLeft(0, 0);
_text->moveToLeft(_cancel->width(), _st.titlePosition.y());
right += _forward->width();
auto left = 0;
_cancel->moveToLeft(left, 0);
left += _cancel->width();
const auto availableWidth = newWidth - left - right;
_text->setGeometryToLeft(left, 0, availableWidth, _st.height, newWidth);
}
void TopBarOverride::updateControlsVisibility() {

View file

@ -30,7 +30,8 @@ struct InfoTopBar;
namespace Ui {
class IconButton;
class FlatLabel;
class LabelWithNumbers;
struct StringWithNumbers;
} // namespace Ui
namespace Info {
@ -54,7 +55,7 @@ protected:
private:
void updateControlsVisibility();
void updateControlsGeometry(int newWidth);
QString generateText() const;
Ui::StringWithNumbers generateText() const;
[[nodiscard]] bool computeCanDelete() const;
[[nodiscard]] SelectedItemSet collectItems() const;
@ -65,7 +66,7 @@ private:
SelectedItems _items;
bool _canDelete = false;
object_ptr<Ui::IconButton> _cancel;
object_ptr<Ui::FlatLabel> _text;
object_ptr<Ui::LabelWithNumbers> _text;
object_ptr<Ui::IconButton> _forward;
object_ptr<Ui::IconButton> _delete;
rpl::event_stream<> _correctionCancelRequests;

View file

@ -46,7 +46,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "lang/lang_keys.h"
#include "styles/style_info.h"
#include "styles/style_profile.h"
#include "styles/style_window.h"
namespace Info {
namespace {
@ -449,7 +448,7 @@ void WrapWidget::toggleTopBarOverride(bool shown) {
[this] { topBarOverrideStep(); },
_topBarOverrideShown ? 0. : 1.,
_topBarOverrideShown ? 1. : 0.,
st::topBarSlideDuration,
st::slideWrapDuration,
anim::easeOutCirc);
}

View file

@ -0,0 +1,261 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "ui/effects/numbers_animation.h"
#include "lang/lang_tag.h"
#include "styles/style_widgets.h"
namespace Ui {
NumbersAnimation::NumbersAnimation(
const style::font &font,
base::lambda<void()> animationCallback)
: _font(font)
, _animationCallback(std::move(animationCallback)) {
for (auto ch = '0'; ch != '9'; ++ch) {
accumulate_max(_digitWidth, _font->m.width(ch));
}
}
void NumbersAnimation::setText(const QString &text, int value) {
if (_a_ready.animating(getms())) {
_delayedText = text;
_delayedValue = value;
} else {
realSetText(text, value);
}
}
void NumbersAnimation::animationCallback() {
if (_animationCallback) {
_animationCallback();
}
if (_widthChangedCallback) {
_widthChangedCallback();
}
if (!_a_ready.animating()) {
if (!_delayedText.isEmpty()) {
setText(_delayedText, _delayedValue);
}
}
}
void NumbersAnimation::realSetText(QString text, int value) {
_delayedText = QString();
_delayedValue = 0;
_growing = (value > _value);
_value = value;
auto newSize = text.size();
while (_digits.size() < newSize) {
_digits.push_front(Digit());
}
while (_digits.size() > newSize && !_digits.front().to.unicode()) {
_digits.pop_front();
}
auto oldSize = _digits.size();
auto animating = false;
for (auto i = 0, size = _digits.size(); i != size; ++i) {
auto &digit = _digits[i];
digit.from = digit.to;
digit.fromWidth = digit.toWidth;
digit.to = (newSize + i < size) ? QChar(0) : text[newSize + i - size];
digit.toWidth = digit.to.unicode() ? _font->m.width(digit.to) : 0;
if (digit.from != digit.to) {
animating = true;
}
if (!digit.from.unicode()) {
--oldSize;
}
}
_fromWidth = oldSize * _digitWidth;
_toWidth = newSize * _digitWidth;
if (animating) {
_a_ready.start(
[this] { animationCallback(); },
0.,
1.,
st::slideWrapDuration);
}
}
int NumbersAnimation::countWidth() const {
return anim::interpolate(
_fromWidth,
_toWidth,
anim::easeOutCirc(1., _a_ready.current(1.)));
}
void NumbersAnimation::stepAnimation(TimeMs ms) {
_a_ready.step(ms);
}
void NumbersAnimation::finishAnimating() {
auto width = countWidth();
_a_ready.finish();
if (_widthChangedCallback && countWidth() != width) {
_widthChangedCallback();
}
if (!_delayedText.isEmpty()) {
setText(_delayedText, _delayedValue);
}
}
void NumbersAnimation::paint(Painter &p, int x, int y, int outerWidth) {
auto digitsCount = _digits.size();
if (!digitsCount) return;
auto progress = anim::easeOutCirc(1., _a_ready.current(1.));
auto width = anim::interpolate(_fromWidth, _toWidth, progress);
QString singleChar('0');
if (rtl()) x = outerWidth - x - width;
x += width - _digits.size() * _digitWidth;
auto fromTop = anim::interpolate(0, _font->height, progress) * (_growing ? 1 : -1);
auto toTop = anim::interpolate(_font->height, 0, progress) * (_growing ? -1 : 1);
for (auto i = 0; i != digitsCount; ++i) {
auto &digit = _digits[i];
auto from = digit.from;
auto to = digit.to;
if (from == to) {
p.setOpacity(1.);
singleChar[0] = from;
p.drawText(x + (_digitWidth - digit.fromWidth) / 2, y + _font->ascent, singleChar);
} else {
if (from.unicode()) {
p.setOpacity(1. - progress);
singleChar[0] = from;
p.drawText(x + (_digitWidth - digit.fromWidth) / 2, y + fromTop + _font->ascent, singleChar);
}
if (to.unicode()) {
p.setOpacity(progress);
singleChar[0] = to;
p.drawText(x + (_digitWidth - digit.toWidth) / 2, y + toTop + _font->ascent, singleChar);
}
}
x += _digitWidth;
}
p.setOpacity(1.);
}
LabelWithNumbers::LabelWithNumbers(
QWidget *parent,
const style::FlatLabel &st,
int textTop,
const StringWithNumbers &value)
: RpWidget(parent)
, _st(st)
, _textTop(textTop)
, _before(GetBefore(value))
, _after(GetAfter(value))
, _numbers(_st.style.font, [this] { update(); })
, _beforeWidth(_st.style.font->width(_before))
, _afterWidth(st.style.font->width(_after)) {
Expects((value.offset < 0) == (value.length == 0));
const auto numbers = GetNumbers(value);
_numbers.setText(numbers, numbers.toInt());
_numbers.finishAnimating();
}
QString LabelWithNumbers::GetBefore(const StringWithNumbers &value) {
return value.text.mid(0, value.offset);
}
QString LabelWithNumbers::GetAfter(const StringWithNumbers &value) {
return (value.offset >= 0)
? value.text.mid(value.offset + value.length)
: QString();
}
QString LabelWithNumbers::GetNumbers(const StringWithNumbers &value) {
return (value.offset >= 0)
? value.text.mid(value.offset, value.length)
: QString();
}
void LabelWithNumbers::setValue(const StringWithNumbers &value) {
_before = GetBefore(value);
_after = GetAfter(value);
const auto numbers = GetNumbers(value);
_numbers.setText(numbers, numbers.toInt());
const auto oldBeforeWidth = std::exchange(
_beforeWidth,
_st.style.font->width(_before));
_beforeWidthAnimation.start(
[this] { update(); },
oldBeforeWidth,
_beforeWidth,
st::slideWrapDuration,
anim::easeOutCirc);
_afterWidth = _st.style.font->width(_after);
}
void LabelWithNumbers::paintEvent(QPaintEvent *e) {
Painter p(this);
const auto ms = getms();
const auto beforeWidth = _beforeWidthAnimation.current(ms, _beforeWidth);
_numbers.stepAnimation(ms);
p.setFont(_st.style.font);
p.setBrush(Qt::NoBrush);
p.setPen(_st.textFg);
auto left = 0;
const auto outerWidth = width();
p.setClipRect(0, 0, left + beforeWidth, height());
p.drawTextLeft(left, _textTop, outerWidth, _before, _beforeWidth);
left += beforeWidth;
p.setClipping(false);
_numbers.paint(p, left, _textTop, outerWidth);
left += _numbers.countWidth();
const auto availableWidth = outerWidth - left;
const auto text = (availableWidth < _afterWidth)
? _st.style.font->elided(_after, availableWidth)
: _after;
const auto textWidth = (availableWidth < _afterWidth) ? -1 : _afterWidth;
p.drawTextLeft(left, _textTop, outerWidth, text, textWidth);
}
} // namespace Ui
namespace Lang {
Ui::StringWithNumbers ReplaceTag<Ui::StringWithNumbers>::Call(
Ui::StringWithNumbers &&original,
ushort tag,
const Ui::StringWithNumbers &replacement) {
original.offset = FindTagReplacementPosition(original.text, tag);
original.text = ReplaceTag<QString>::Call(
std::move(original.text),
tag,
replacement.text);
original.length = replacement.text.size();
return std::move(original);
}
} // namespace Lang

View file

@ -0,0 +1,138 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "ui/rp_widget.h"
namespace style {
struct FlatLabel;
} // namespace style
namespace Ui {
class NumbersAnimation {
public:
NumbersAnimation(
const style::font &font,
base::lambda<void()> animationCallback);
void setWidthChangedCallback(base::lambda<void()> callback) {
_widthChangedCallback = std::move(callback);
}
void setText(const QString &text, int value);
void stepAnimation(TimeMs ms);
void finishAnimating();
void paint(Painter &p, int x, int y, int outerWidth);
int countWidth() const;
private:
struct Digit {
QChar from = 0;
QChar to = 0;
int fromWidth = 0;
int toWidth = 0;
};
void animationCallback();
void realSetText(QString text, int value);
const style::font &_font;
QList<Digit> _digits;
int _digitWidth = 0;
int _fromWidth = 0;
int _toWidth = 0;
Animation _a_ready;
QString _delayedText;
int _delayedValue = 0;
int _value = 0;
bool _growing = false;
base::lambda<void()> _animationCallback;
base::lambda<void()> _widthChangedCallback;
};
struct StringWithNumbers {
QString text;
int offset = -1;
int length = 0;
};
class LabelWithNumbers : public Ui::RpWidget {
public:
LabelWithNumbers(
QWidget *parent,
const style::FlatLabel &st,
int textTop,
const StringWithNumbers &value);
void setValue(const StringWithNumbers &value);
protected:
void paintEvent(QPaintEvent *e) override;
private:
static QString GetBefore(const StringWithNumbers &value);
static QString GetAfter(const StringWithNumbers &value);
static QString GetNumbers(const StringWithNumbers &value);
const style::FlatLabel &_st;
int _textTop;
QString _before;
QString _after;
NumbersAnimation _numbers;
int _beforeWidth = 0;
int _afterWidth = 0;
Animation _beforeWidthAnimation;
};
} // namespace Ui
namespace Lang {
template <typename ResultString>
struct StartReplacements;
template <>
struct StartReplacements<Ui::StringWithNumbers> {
static inline Ui::StringWithNumbers Call(QString &&langString) {
return { std::move(langString) };
}
};
template <typename ResultString>
struct ReplaceTag;
template <>
struct ReplaceTag<Ui::StringWithNumbers> {
static Ui::StringWithNumbers Call(
Ui::StringWithNumbers &&original,
ushort tag,
const Ui::StringWithNumbers &replacement);
};
} // namespace Lang

View file

@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/effects/ripple_animation.h"
#include "ui/effects/cross_animation.h"
#include "ui/effects/numbers_animation.h"
#include "lang/lang_instance.h"
namespace Ui {
@ -192,168 +193,6 @@ void FlatButton::paintEvent(QPaintEvent *e) {
p.drawText(r, _text, style::al_top);
}
class RoundButton::Numbers {
public:
Numbers(const style::RoundButton &st, base::lambda<void()> animationCallback);
void setWidthChangedCallback(base::lambda<void()> callback) {
_widthChangedCallback = std::move(callback);
}
void setText(const QString &text, int value);
void stepAnimation(TimeMs ms);
void finishAnimating();
void paint(Painter &p, int x, int y, int outerWidth);
int countWidth() const;
private:
struct Digit {
QChar from = 0;
QChar to = 0;
int fromWidth = 0;
int toWidth = 0;
};
void animationCallback();
void realSetText(QString text, int value);
const style::RoundButton &_st;
QList<Digit> _digits;
int _digitWidth = 0;
int _fromWidth = 0;
int _toWidth = 0;
Animation _a_ready;
QString _delayedText;
int _delayedValue = 0;
int _value = 0;
bool _growing = false;
base::lambda<void()> _animationCallback;
base::lambda<void()> _widthChangedCallback;
};
RoundButton::Numbers::Numbers(const style::RoundButton &st, base::lambda<void()> animationCallback)
: _st(st)
, _animationCallback(std::move(animationCallback)) {
for (auto ch = '0'; ch != '9'; ++ch) {
accumulate_max(_digitWidth, _st.font->m.width(ch));
}
}
void RoundButton::Numbers::setText(const QString &text, int value) {
if (_a_ready.animating(getms())) {
_delayedText = text;
_delayedValue = value;
} else {
realSetText(text, value);
}
}
void RoundButton::Numbers::animationCallback() {
if (_animationCallback) {
_animationCallback();
}
if (_widthChangedCallback) {
_widthChangedCallback();
}
if (!_a_ready.animating()) {
if (!_delayedText.isEmpty()) {
setText(_delayedText, _delayedValue);
}
}
}
void RoundButton::Numbers::realSetText(QString text, int value) {
_delayedText = QString();
_delayedValue = 0;
_growing = (value > _value);
_value = value;
auto newSize = text.size();
while (_digits.size() < newSize) {
_digits.push_front(Digit());
}
while (_digits.size() > newSize && !_digits.front().to.unicode()) {
_digits.pop_front();
}
auto oldSize = _digits.size();
auto animating = false;
for (auto i = 0, size = _digits.size(); i != size; ++i) {
auto &digit = _digits[i];
digit.from = digit.to;
digit.fromWidth = digit.toWidth;
digit.to = (newSize + i < size) ? QChar(0) : text[newSize + i - size];
digit.toWidth = digit.to.unicode() ? _st.font->m.width(digit.to) : 0;
if (digit.from != digit.to) {
animating = true;
}
if (!digit.from.unicode()) {
--oldSize;
}
}
_fromWidth = oldSize * _digitWidth;
_toWidth = newSize * _digitWidth;
if (animating) {
_a_ready.start([this] { animationCallback(); }, 0., 1., _st.numbersDuration);
}
}
int RoundButton::Numbers::countWidth() const {
return anim::interpolate(_fromWidth, _toWidth, anim::easeOutCirc(1., _a_ready.current(1.)));
}
void RoundButton::Numbers::stepAnimation(TimeMs ms) {
_a_ready.step(ms);
}
void RoundButton::Numbers::finishAnimating() {
auto width = countWidth();
_a_ready.finish();
if (_widthChangedCallback && countWidth() != width) {
_widthChangedCallback();
}
if (!_delayedText.isEmpty()) {
setText(_delayedText, _delayedValue);
}
}
void RoundButton::Numbers::paint(Painter &p, int x, int y, int outerWidth) {
auto digitsCount = _digits.size();
if (!digitsCount) return;
auto progress = anim::easeOutCirc(1., _a_ready.current(1.));
auto width = anim::interpolate(_fromWidth, _toWidth, progress);
QString singleChar('0');
if (rtl()) x = outerWidth - x - width;
x += width - _digits.size() * _digitWidth;
auto fromTop = anim::interpolate(0, _st.font->height, progress) * (_growing ? 1 : -1);
auto toTop = anim::interpolate(_st.font->height, 0, progress) * (_growing ? -1 : 1);
for (auto i = 0; i != digitsCount; ++i) {
auto &digit = _digits[i];
auto from = digit.from;
auto to = digit.to;
if (from.unicode()) {
p.setOpacity(1. - progress);
singleChar[0] = from;
p.drawText(x + (_digitWidth - digit.fromWidth) / 2, y + fromTop + _st.font->ascent, singleChar);
}
if (to.unicode()) {
p.setOpacity(progress);
singleChar[0] = to;
p.drawText(x + (_digitWidth - digit.toWidth) / 2, y + toTop + _st.font->ascent, singleChar);
}
x += _digitWidth;
}
p.setOpacity(1.);
}
RoundButton::RoundButton(QWidget *parent, base::lambda<QString()> textFactory, const style::RoundButton &st) : RippleButton(parent, st.ripple)
, _textFactory(std::move(textFactory))
, _st(st) {
@ -376,7 +215,9 @@ void RoundButton::setNumbersText(const QString &numbersText, int numbers) {
_numbers.reset();
} else {
if (!_numbers) {
_numbers = std::make_unique<Numbers>(_st, [this] { numbersAnimationCallback(); });
_numbers = std::make_unique<NumbersAnimation>(_st.font, [this] {
numbersAnimationCallback();
});
}
_numbers->setText(numbersText, numbers);
}
@ -385,7 +226,9 @@ void RoundButton::setNumbersText(const QString &numbersText, int numbers) {
void RoundButton::setWidthChangedCallback(base::lambda<void()> callback) {
if (!_numbers) {
_numbers = std::make_unique<Numbers>(_st, [this] { numbersAnimationCallback(); });
_numbers = std::make_unique<NumbersAnimation>(_st.font, [this] {
numbersAnimationCallback();
});
}
_numbers->setWidthChangedCallback(std::move(callback));
}

View file

@ -28,6 +28,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
namespace Ui {
class RippleAnimation;
class NumbersAnimation;
class LinkButton : public AbstractButton {
public:
@ -152,8 +153,7 @@ private:
base::lambda<QString()> _textFactory;
int _textWidth;
class Numbers;
std::unique_ptr<Numbers> _numbers;
std::unique_ptr<NumbersAnimation> _numbers;
int _fullWidthOverride = 0;

View file

@ -77,7 +77,6 @@ RoundButton {
numbersTextFg: color;
numbersTextFgOver: color;
numbersSkip: pixels;
numbersDuration: int;
width: pixels;
height: pixels;
@ -564,7 +563,6 @@ defaultActiveButton: RoundButton {
textBgOver: activeButtonBgOver;
numbersSkip: 7px;
numbersDuration: 200;
width: -34px;
height: 34px;

View file

@ -304,7 +304,6 @@ topBarInfoButton: UserpicButton(defaultUserpicButton) {
photoSize: 42px;
photoPosition: point(2px, -1px);
}
topBarSlideDuration: 200;
themeEditorSampleSize: size(90px, 51px);
themeEditorMargin: margins(17px, 10px, 17px, 10px);

View file

@ -510,6 +510,8 @@
<(src_loc)/ui/effects/cross_animation.h
<(src_loc)/ui/effects/fade_animation.cpp
<(src_loc)/ui/effects/fade_animation.h
<(src_loc)/ui/effects/numbers_animation.cpp
<(src_loc)/ui/effects/numbers_animation.h
<(src_loc)/ui/effects/panel_animation.cpp
<(src_loc)/ui/effects/panel_animation.h
<(src_loc)/ui/effects/radial_animation.cpp