Merge branch 'dev'

This commit is contained in:
John Preston 2016-10-24 16:44:48 +03:00
commit 53e48beb03
85 changed files with 5787 additions and 4155 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View file

@ -91,15 +91,6 @@ boxTitleFont: font(boxFontSize bold);
boxTitlePosition: point(26px, 28px);
boxTitleHeight: 54px;
boxBlueTitleBg: #6393b5;
boxBlueTitleAdditionalFg: #dae9f5;
boxBlueTitleAdditionalSkip: 12px;
boxBlueTitlePosition: point(23px, 18px);
boxBlueCloseIcon: sprite(120px, 108px, 12px, 12px);
boxBlueCloseBg: #c8e1f0;
boxBlueCloseDuration: 150;
boxBlueShadow: sprite(132px, 108px, 1px, 4px);
boxButtonFont: font(boxFontSize semibold);
defaultBoxButton: RoundButton {
textFg: #2f9fea;
@ -193,6 +184,7 @@ defaultInputArea: InputArea {
heightMax: 128px;
}
defaultInputField: InputField {
textBg: white;
textFg: black;
textMargins: margins(0px, 6px, 0px, 4px);
textAlign: align(topleft);
@ -216,15 +208,6 @@ defaultInputField: InputField {
height: 32px;
}
dialogsSearchField: InputField(defaultInputField) {
textMargins: margins(34px, 7px, 34px, 7px);
iconSprite: sprite(227px, 21px, 24px, 24px);
iconPosition: point(6px, 5px);
width: 240px;
height: 34px;
}
defaultCheckbox: Checkbox {
textFg: black;
textBg: white;
@ -343,52 +326,8 @@ boxScroll: flatScroll(solidScroll) {
boxScrollSkip: 6px;
boxScrollShadowBg: #00000012;
boxSearchField: InputField(defaultInputField) {
textMargins: margins(41px, 16px, 41px, 0px);
placeholderFg: #999;
placeholderFgActive: #aaa;
placeholderMargins: margins(4px, 0px, 4px, 0px);
border: 0px;
borderActive: 0px;
borderError: 0px;
height: 48px;
iconSprite: sprite(227px, 21px, 24px, 24px);
iconPosition: point(15px, 14px);
font: normalFont;
}
boxSearchCancel: iconedButton {
color: white;
bgColor: white;
overBgColor: white;
font: font(fsize);
opacity: 0.3;
overOpacity: 0.4;
textPos: point(0px, 0px);
downTextPos: point(0px, 0px);
duration: 150;
cursor: cursor(pointer);
icon: sprite(133px, 108px, 12px, 12px);
iconPos: point(8px, 18px);
downIcon: sprite(133px, 108px, 12px, 12px);
downIconPos: point(8px, 18px);
width: 41px;
height: 48px;
}
titleBg: #6389a8;
titleHeight: 39px;
titleIconPos: point(7px, 7px);
titleIconImg: sprite(161px, 100px, 26px, 26px);
titleFont: font(17px);
titlePos: point(44px, 29px);
titleMenuOffset: 36px;
@ -788,12 +727,11 @@ dlgFilter: flatInput(inpDefGray) {
bgColor: #f2f2f2;
phColor: #949494;
phFocusColor: #a4a4a4;
imgRect: sprite(227px, 21px, 24px, 24px);
icon: icon {{ "box_search_icon", #aaaaaa, point(10px, 9px) }};
width: 240px;
height: 34px;
textMrg: margins(34px, 2px, 34px, 4px);
imgPos: point(6px, 5px);
}
topBarHeight: 54px;
@ -959,8 +897,6 @@ msgDateImgBgSelected: #1c4a7187;
msgDateImgPadding: point(8px, 2px);
msgDateImgCheckSpace: 4px;
msgDogImg: sprite(216px, 92px, 126px, 126px);
collapseButton: flatButton(btnDefFlat) {
font: msgServiceFont;
overFont: msgServiceFont;
@ -1340,9 +1276,6 @@ layerPadding: margins(10px, 10px, 10px, 10px);
contactPadding: margins(49px, 22px, 0px, 6px);
contactSkip: 13px;
contactPhoneSkip: 30px;
contactUserIcon: sprite(120px, 90px, 18px, 18px);
contactPhoneIcon: sprite(138px, 90px, 18px, 18px);
contactIconTop: 10px;
contactsPhotoSize: 42px;
contactsPadding: margins(16px, 7px, 16px, 7px);
@ -1354,15 +1287,7 @@ contactsStatusFg: #999999;
contactsStatusFgOver: #7c99b2;
contactsStatusFgOnline: #3b8dcc;
contactsBgOver: overBg;
contactsBgActive: #6f9cbd;
contactsCheckPosition: point(8px, 16px);
contactsCheckIcon: sprite(187px, 61px, 18px, 14px);
contactsCheckActiveIcon: sprite(187px, 75px, 18px, 14px);
contactsNewItemHeight: 53px;
contactsNewItemIcon: sprite(307px, 248px, 22px, 16px);
contactsNewItemIconPosition: point(29px, 19px);
contactsNewItemTop: 18px;
contactsNewItemFg: #4b82af;
contactsAboutBg: #f7f7f7;
contactsAboutShadow: #0000001F;
contactsAdminCheckbox: Checkbox(defaultBoxCheckbox) {
@ -1886,8 +1811,6 @@ medviewSaveMsgShown: 2000;
medviewSaveMsgHiding: 2500;
medviewSaveMsg: #000000b2;
mvTransparentBrush: sprite(9px, 124px, 8px, 8px);
// Mac specific
macAccessoryWidth: 450.;

View file

@ -130,8 +130,7 @@ flatInput {
font: font;
cursor: cursor;
imgRect: sprite;
imgPos: point;
icon: icon;
borderWidth: pixels;
borderColor: color;
@ -394,6 +393,7 @@ InputArea {
}
InputField {
textBg: color;
textFg: color;
textMargins: margins;
textAlign: align;
@ -418,9 +418,6 @@ InputField {
width: pixels;
height: pixels;
iconSprite: sprite;
iconPosition: point;
}
PeerAvatarButton {

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 708 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 579 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

View file

@ -2819,41 +2819,7 @@ namespace {
uint64 max = qMax(1ULL, components[maxtomin[0]]), mid = qMax(1ULL, components[maxtomin[1]]), min = qMax(1ULL, components[maxtomin[2]]);
QImage dog = App::sprite().toImage().copy(st::msgDogImg.rect());
QImage::Format f = dog.format();
if (f != QImage::Format_ARGB32 && f != QImage::Format_ARGB32_Premultiplied) {
dog = dog.convertToFormat(QImage::Format_ARGB32_Premultiplied);
}
uchar *dogBits = dog.bits();
if (max != min) {
float64 coef = float64(mid - min) / float64(max - min);
for (int i = 0, s = dog.width() * dog.height() * 4; i < s; i += 4) {
int dogmaxtomin[3] = { i, i + 1, i + 2 };
if (dogBits[dogmaxtomin[0]] < dogBits[dogmaxtomin[1]]) {
qSwap(dogmaxtomin[0], dogmaxtomin[1]);
}
if (dogBits[dogmaxtomin[1]] < dogBits[dogmaxtomin[2]]) {
qSwap(dogmaxtomin[1], dogmaxtomin[2]);
if (dogBits[dogmaxtomin[0]] < dogBits[dogmaxtomin[1]]) {
qSwap(dogmaxtomin[0], dogmaxtomin[1]);
}
}
uchar result[3];
result[maxtomin[0]] = dogBits[dogmaxtomin[0]];
result[maxtomin[2]] = dogBits[dogmaxtomin[2]];
result[maxtomin[1]] = uchar(qRound(result[maxtomin[2]] + (result[maxtomin[0]] - result[maxtomin[2]]) * coef));
dogBits[i] = result[2];
dogBits[i + 1] = result[1];
dogBits[i + 2] = result[0];
}
} else {
for (int i = 0, s = dog.width() * dog.height() * 4; i < s; i += 4) {
uchar b = dogBits[i], g = dogBits[i + 1], r = dogBits[i + 2];
dogBits[i] = dogBits[i + 1] = dogBits[i + 2] = (r + r + b + g + g + g) / 6;
}
}
Window::chatBackground()->init(id, pixmapFromImageInPlace(std_::move(img)), pixmapFromImageInPlace(std_::move(dog)));
Window::chatBackground()->init(id, pixmapFromImageInPlace(std_::move(img)));
memcpy(componentsScroll, components, sizeof(components));
memcpy(componentsPoint, components, sizeof(components));

View file

@ -26,16 +26,17 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "abstractbox.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "styles/style_boxes.h"
void BlueTitleShadow::paintEvent(QPaintEvent *e) {
Painter p(this);
QRect r(e->rect());
p.drawPixmap(QRect(r.left(), 0, r.width(), height()), App::sprite(), st::boxBlueShadow.rect());
st::boxBlueTitleShadow.fill(p, QRect(r.left(), 0, r.width(), height()));
}
BlueTitleClose::BlueTitleClose(QWidget *parent) : Button(parent)
, a_iconFg(st::boxBlueCloseBg->c)
, a_iconFg(st::boxBlueCloseFg->c)
, _a_over(animation(this, &BlueTitleClose::step_over)) {
resize(st::boxTitleHeight, st::boxTitleHeight);
setCursor(style::cur_pointer);
@ -44,7 +45,7 @@ BlueTitleClose::BlueTitleClose(QWidget *parent) : Button(parent)
void BlueTitleClose::onStateChange(int oldState, ButtonStateChangeSource source) {
if ((oldState & StateOver) != (_state & StateOver)) {
a_iconFg.start(((_state & StateOver) ? st::white : st::boxBlueCloseBg)->c);
a_iconFg.start(((_state & StateOver) ? st::boxBlueCloseOverFg : st::boxBlueCloseFg)->c);
_a_over.start();
}
}
@ -57,28 +58,23 @@ void BlueTitleClose::step_over(float64 ms, bool timer) {
} else {
a_iconFg.update(dt, anim::linear);
}
if (timer) update((st::boxTitleHeight - st::boxBlueCloseIcon.pxWidth()) / 2, (st::boxTitleHeight - st::boxBlueCloseIcon.pxHeight()) / 2, st::boxBlueCloseIcon.pxWidth(), st::boxBlueCloseIcon.pxHeight());
if (timer) update((st::boxTitleHeight - st::boxBlueCloseIcon.width()) / 2, (st::boxTitleHeight - st::boxBlueCloseIcon.height()) / 2, st::boxBlueCloseIcon.width(), st::boxBlueCloseIcon.height());
}
void BlueTitleClose::paintEvent(QPaintEvent *e) {
Painter p(this);
QRect r(e->rect()), s((st::boxTitleHeight - st::boxBlueCloseIcon.pxWidth()) / 2, (st::boxTitleHeight - st::boxBlueCloseIcon.pxHeight()) / 2, st::boxBlueCloseIcon.pxWidth(), st::boxBlueCloseIcon.pxHeight());
QRect r(e->rect()), s((st::boxTitleHeight - st::boxBlueCloseIcon.width()) / 2, (st::boxTitleHeight - st::boxBlueCloseIcon.height()) / 2, st::boxBlueCloseIcon.width(), st::boxBlueCloseIcon.height());
if (!s.contains(r)) {
p.fillRect(r, st::boxBlueTitleBg->b);
p.fillRect(r, st::boxBlueTitleBg);
}
if (s.intersects(r)) {
p.fillRect(s.intersected(r), a_iconFg.current());
p.drawSprite(s.topLeft(), st::boxBlueCloseIcon);
st::boxBlueCloseIcon.paint(p, s.topLeft(), width());
}
}
AbstractBox::AbstractBox(int32 w) : LayerWidget()
, _maxHeight(0)
, _closed(false)
, _blueTitle(false)
, _blueClose(0)
, _blueShadow(0) {
AbstractBox::AbstractBox(int w) : LayerWidget() {
setAttribute(Qt::WA_OpaquePaintEvent);
resize(w, 0);
}
@ -101,7 +97,7 @@ void AbstractBox::resizeEvent(QResizeEvent *e) {
}
if (_blueShadow) {
_blueShadow->moveToLeft(0, st::boxTitleHeight);
_blueShadow->resize(width(), st::boxBlueShadow.pxHeight());
_blueShadow->resize(width(), st::boxBlueTitleShadow.height());
}
LayerWidget::resizeEvent(e);
}
@ -165,8 +161,8 @@ void AbstractBox::resizeMaxHeight(int32 newWidth, int32 maxHeight) {
}
}
int32 AbstractBox::countHeight() const {
return qMin(_maxHeight, App::wnd()->height() - int32(2 * st::boxVerticalMargin));
int AbstractBox::countHeight() const {
return qMin(_maxHeight, App::wnd()->height() - 2 * st::boxVerticalMargin);
}
void AbstractBox::onClose() {
@ -201,24 +197,28 @@ ScrollableBox::ScrollableBox(const style::flatScroll &scroll, int32 w) : Abstrac
}
void ScrollableBox::resizeEvent(QResizeEvent *e) {
_scroll->setGeometry(0, _topSkip, width(), height() - _topSkip - _bottomSkip);
updateScrollGeometry();
AbstractBox::resizeEvent(e);
}
void ScrollableBox::init(QWidget *inner, int bottomSkip, int topSkip) {
_bottomSkip = bottomSkip;
_topSkip = topSkip;
_scroll->setWidget(inner);
_scroll->setFocusPolicy(Qt::NoFocus);
ScrollableBox::resizeEvent(nullptr);
}
void ScrollableBox::initOwned(QWidget *inner, int bottomSkip, int topSkip) {
void ScrollableBox::init(ScrolledWidget *inner, int bottomSkip, int topSkip) {
_bottomSkip = bottomSkip;
_topSkip = topSkip;
_scroll->setOwnedWidget(inner);
_scroll->setFocusPolicy(Qt::NoFocus);
ScrollableBox::resizeEvent(nullptr);
updateScrollGeometry();
}
void ScrollableBox::setScrollSkips(int bottomSkip, int topSkip) {
if (_topSkip != topSkip || _bottomSkip != bottomSkip) {
_topSkip = topSkip;
_bottomSkip = bottomSkip;
updateScrollGeometry();
}
}
void ScrollableBox::updateScrollGeometry() {
_scroll->setGeometry(0, _topSkip, width(), height() - _topSkip - _bottomSkip);
}
ItemListBox::ItemListBox(const style::flatScroll &scroll, int32 w) : ScrollableBox(scroll, w) {

View file

@ -52,7 +52,7 @@ class AbstractBox : public LayerWidget, protected base::Subscriber {
Q_OBJECT
public:
AbstractBox(int32 w = st::boxWideWidth);
AbstractBox(int w = st::boxWideWidth);
void parentResized() override;
void showDone() override {
showAll();
@ -83,14 +83,14 @@ protected:
}
private:
int32 _maxHeight;
int32 countHeight() const;
int _maxHeight = 0;
int countHeight() const;
bool _closed;
bool _closed = false;
bool _blueTitle;
BlueTitleClose *_blueClose;
BlueTitleShadow *_blueShadow;
bool _blueTitle = false;
BlueTitleClose *_blueClose = nullptr;
BlueTitleShadow *_blueShadow = nullptr;
};
@ -105,8 +105,8 @@ public:
ScrollableBox(const style::flatScroll &scroll, int w = st::boxWideWidth);
protected:
void init(QWidget *inner, int bottomSkip = st::boxScrollSkip, int topSkip = st::boxTitleHeight);
void initOwned(QWidget *inner, int bottomSkip = st::boxScrollSkip, int topSkip = st::boxTitleHeight);
void init(ScrolledWidget *inner, int bottomSkip = st::boxScrollSkip, int topSkip = st::boxTitleHeight);
void setScrollSkips(int bottomSkip = st::boxScrollSkip, int topSkip = st::boxTitleHeight);
void resizeEvent(QResizeEvent *e) override;
@ -115,8 +115,10 @@ protected:
}
private:
void updateScrollGeometry();
ChildWidget<ScrollArea> _scroll;
int32 _topSkip, _bottomSkip;
int _topSkip, _bottomSkip;
};

View file

@ -109,8 +109,8 @@ void AddContactBox::paintEvent(QPaintEvent *e) {
paintTitle(p, _boxTitle);
if (_retry.isHidden()) {
p.drawSpriteLeft(st::boxPadding.left(), _first.y() + st::contactIconTop, width(), st::contactUserIcon);
p.drawSpriteLeft(st::boxPadding.left(), _phone.y() + st::contactIconTop, width(), st::contactPhoneIcon);
st::contactUserIcon.paint(p, st::boxPadding.left(), _first.y() + st::contactIconTop, width());
st::contactPhoneIcon.paint(p, st::boxPadding.left(), _phone.y() + st::contactIconTop, width());
} else {
p.setPen(st::black->p);
p.setFont(st::boxTextFont->f);

View file

@ -27,11 +27,41 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "window/chat_background.h"
#include "styles/style_overview.h"
BackgroundInner::BackgroundInner() :
_bgCount(0), _rows(0), _over(-1), _overDown(-1) {
BackgroundBox::BackgroundBox() : ItemListBox(st::backgroundScroll)
, _inner(this) {
init(_inner);
connect(_inner, SIGNAL(backgroundChosen(int)), this, SLOT(onBackgroundChosen(int)));
prepare();
}
void BackgroundBox::paintEvent(QPaintEvent *e) {
Painter p(this);
if (paint(p)) return;
paintTitle(p, lang(lng_backgrounds_header));
}
void BackgroundBox::onBackgroundChosen(int index) {
if (index >= 0 && index < App::cServerBackgrounds().size()) {
const App::WallPaper &paper(App::cServerBackgrounds().at(index));
if (App::main()) App::main()->setChatBackground(paper);
using Update = Window::ChatBackgroundUpdate;
Window::chatBackground()->notify(Update(Update::Type::Start, !paper.id));
}
onClose();
}
BackgroundBox::Inner::Inner(QWidget *parent) : ScrolledWidget(parent)
, _bgCount(0)
, _rows(0)
, _over(-1)
, _overDown(-1) {
if (App::cServerBackgrounds().isEmpty()) {
resize(BackgroundsInRow * (st::backgroundSize.width() + st::backgroundPadding) + st::backgroundPadding, 2 * (st::backgroundSize.height() + st::backgroundPadding) + st::backgroundPadding);
MTP::send(MTPaccount_GetWallPapers(), rpcDone(&BackgroundInner::gotWallpapers));
MTP::send(MTPaccount_GetWallPapers(), rpcDone(&Inner::gotWallpapers));
} else {
updateWallpapers();
}
@ -40,7 +70,7 @@ _bgCount(0), _rows(0), _over(-1), _overDown(-1) {
setMouseTracking(true);
}
void BackgroundInner::gotWallpapers(const MTPVector<MTPWallPaper> &result) {
void BackgroundBox::Inner::gotWallpapers(const MTPVector<MTPWallPaper> &result) {
App::WallPapers wallpapers;
wallpapers.push_back(App::WallPaper(0, ImagePtr(st::msgBG0), ImagePtr(st::msgBG0)));
@ -98,7 +128,7 @@ void BackgroundInner::gotWallpapers(const MTPVector<MTPWallPaper> &result) {
updateWallpapers();
}
void BackgroundInner::updateWallpapers() {
void BackgroundBox::Inner::updateWallpapers() {
_bgCount = App::cServerBackgrounds().size();
_rows = _bgCount / BackgroundsInRow;
if (_bgCount % BackgroundsInRow) ++_rows;
@ -111,7 +141,7 @@ void BackgroundInner::updateWallpapers() {
}
}
void BackgroundInner::paintEvent(QPaintEvent *e) {
void BackgroundBox::Inner::paintEvent(QPaintEvent *e) {
QRect r(e->rect());
Painter p(this);
@ -145,7 +175,7 @@ void BackgroundInner::paintEvent(QPaintEvent *e) {
}
}
void BackgroundInner::mouseMoveEvent(QMouseEvent *e) {
void BackgroundBox::Inner::mouseMoveEvent(QMouseEvent *e) {
int x = e->pos().x(), y = e->pos().y();
int row = int((y - st::backgroundPadding) / (st::backgroundSize.height() + st::backgroundPadding));
if (y - row * (st::backgroundSize.height() + st::backgroundPadding) > st::backgroundPadding + st::backgroundSize.height()) row = _rows + 1;
@ -161,11 +191,11 @@ void BackgroundInner::mouseMoveEvent(QMouseEvent *e) {
}
}
void BackgroundInner::mousePressEvent(QMouseEvent *e) {
void BackgroundBox::Inner::mousePressEvent(QMouseEvent *e) {
_overDown = _over;
}
void BackgroundInner::mouseReleaseEvent(QMouseEvent *e) {
void BackgroundBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
if (_overDown == _over && _over >= 0) {
emit backgroundChosen(_over);
} else if (_over < 0) {
@ -173,33 +203,5 @@ void BackgroundInner::mouseReleaseEvent(QMouseEvent *e) {
}
}
void BackgroundInner::resizeEvent(QResizeEvent *e) {
}
BackgroundBox::BackgroundBox() : ItemListBox(st::backgroundScroll)
, _inner() {
init(&_inner);
connect(&_inner, SIGNAL(backgroundChosen(int)), this, SLOT(onBackgroundChosen(int)));
prepare();
}
void BackgroundBox::paintEvent(QPaintEvent *e) {
Painter p(this);
if (paint(p)) return;
paintTitle(p, lang(lng_backgrounds_header));
}
void BackgroundBox::onBackgroundChosen(int index) {
if (index >= 0 && index < App::cServerBackgrounds().size()) {
const App::WallPaper &paper(App::cServerBackgrounds().at(index));
if (App::main()) App::main()->setChatBackground(paper);
using Update = Window::ChatBackgroundUpdate;
Window::chatBackground()->notify(Update(Update::Type::Start, !paper.id));
}
onClose();
void BackgroundBox::Inner::resizeEvent(QResizeEvent *e) {
}

View file

@ -23,11 +23,30 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "abstractbox.h"
#include "core/lambda_wrap.h"
class BackgroundInner : public TWidget, public RPCSender, private base::Subscriber {
class BackgroundBox : public ItemListBox {
Q_OBJECT
public:
BackgroundInner();
BackgroundBox();
public slots:
void onBackgroundChosen(int index);
protected:
void paintEvent(QPaintEvent *e) override;
private:
class Inner;
ChildWidget<Inner> _inner;
};
// This class is hold in header because it requires Qt preprocessing.
class BackgroundBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber {
Q_OBJECT
public:
Inner(QWidget *parent);
signals:
void backgroundChosen(int index);
@ -47,20 +66,3 @@ private:
int32 _over, _overDown;
};
class BackgroundBox : public ItemListBox {
Q_OBJECT
public:
BackgroundBox();
public slots:
void onBackgroundChosen(int index);
protected:
void paintEvent(QPaintEvent *e) override;
private:
BackgroundInner _inner;
};

View file

@ -19,6 +19,17 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
using "basic.style";
using "ui/widgets/widgets.style";
boxBlueTitleBg: #6393b5;
boxBlueTitleAdditionalFg: #dae9f5;
boxBlueTitleAdditionalSkip: 12px;
boxBlueTitlePosition: point(23px, 18px);
boxBlueTitleShadow: icon {{ "box_title_shadow", windowShadowFg }};
boxBlueCloseFg: #c8e1f0;
boxBlueCloseOverFg: #ffffff;
boxBlueCloseIcon: icon {{ "box_button_close", boxBlueTitleBg }};
boxBlueCloseDuration: 150;
confirmInviteTitle: flatLabel(labelDefFlat) {
font: font(16px semibold);
@ -67,26 +78,108 @@ aboutRevokePublicLabel: flatLabel(labelDefFlat) {
textFg: windowTextFg;
}
contactUserIcon: icon {{ "add_contact_user", #999999 }};
contactPhoneIcon: icon {{ "add_contact_phone", #999999 }};
contactIconTop: 10px;
contactsNewItemHeight: 53px;
contactsNewItemIcon: icon {{ "contacts_add", #749fc2, point(29px, 19px) }};
contactsNewItemTop: 18px;
contactsNewItemFg: #4b82af;
contactsMultiSelect: MultiSelect {
padding: margins(8px, 8px, 8px, 8px);
maxHeight: 104px;
scroll: flatScroll(solidScroll) {
deltat: 3px;
deltab: 3px;
round: 1px;
width: 8px;
deltax: 3px;
hiding: 1000;
}
item: MultiSelectItem {
padding: margins(6px, 7px, 12px, 0px);
maxWidth: 128px;
height: 32px;
font: normalFont;
textBg: contactsBgOver;
textFg: windowTextFg;
textActiveBg: windowActiveBg;
textActiveFg: white;
deleteFg: white;
deleteLeft: 10px;
deleteStroke: 2px;
duration: 150;
minScale: 0.3;
}
itemSkip: 8px;
field: InputField(defaultInputField) {
textBg: transparent;
textMargins: margins(2px, 7px, 2px, 0px);
placeholderFg: #999;
placeholderFgActive: #aaa;
placeholderMargins: margins(2px, 0px, 2px, 0px);
border: 0px;
borderActive: 0px;
borderError: 0px;
height: 32px;
font: normalFont;
}
fieldMinWidth: 42px;
fieldIcon: icon {{ "box_search_icon", #aaaaaa, point(11px, 9px) }};
fieldIconSkip: 36px;
fieldCancel: IconButton {
width: 41px;
height: 48px;
opacity: 0.3;
overOpacity: 0.4;
icon: icon {{ "box_search_cancel", #000000 }};
iconPosition: point(8px, 18px);
downIconPosition: point(8px, 19px);
duration: 150;
}
fieldCancelSkip: 34px;
}
contactsPhotoCheckbox: RoundImageCheckbox {
imageRadius: 21px;
imageSmallRadius: 18px;
selectWidth: 2px;
selectFg: windowActiveBg;
selectDuration: 150;
checkBorder: windowBg;
checkBg: windowActiveBg;
checkRadius: 10px;
checkSmallRadius: 3px;
checkIcon: icon {{ "default_checkbox_check", windowBg, point(3px, 6px) }};
}
contactsPhotoDisabledCheckFg: #bbbbbb;
contactsNameCheckedFg: #2b88b8;
localStorageBoxSkip: 10px;
shareRowsTop: 12px;
shareRowHeight: 108px;
sharePhotoRadius: 28px;
sharePhotoSmallRadius: 24px;
sharePhotoTop: 6px;
shareSelectWidth: 2px;
shareSelectFg: windowActiveBg;
shareCheckBorder: windowBg;
shareCheckBg: windowActiveBg;
shareCheckRadius: 10px;
shareCheckSmallRadius: 3px;
shareCheckIcon: icon {{ "default_checkbox_check", windowBg, point(3px, 6px) }};
sharePhotoCheckbox: RoundImageCheckbox(contactsPhotoCheckbox) {
imageRadius: 28px;
imageSmallRadius: 24px;
}
shareNameFont: font(11px);
shareNameFg: windowTextFg;
shareNameActiveFg: btnYesColor;
shareNameTop: 6px;
shareColumnSkip: 6px;
shareSelectDuration: 150;
shareActivateDuration: 150;
shareScrollDuration: 300;

File diff suppressed because it is too large Load diff

View file

@ -22,174 +22,27 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "abstractbox.h"
#include "core/single_timer.h"
#include "ui/effects/round_image_checkbox.h"
#include "boxes/members_box.h"
namespace Dialogs {
class Row;
class IndexedList;
} // namespace Dialogs
enum MembersFilter {
MembersFilterRecent,
MembersFilterAdmins,
};
using MembersAlreadyIn = OrderedSet<UserData*>;
namespace Ui {
class MultiSelect;
template <typename Widget>
class WidgetSlideWrap;
} // namespace Ui
QString cantInviteError();
class ConfirmBox;
class ContactsInner : public TWidget, public RPCSender, private base::Subscriber {
Q_OBJECT
private:
struct ContactData;
public:
ContactsInner(CreatingGroupType creating = CreatingGroupNone);
ContactsInner(ChannelData *channel, MembersFilter membersFilter, const MembersAlreadyIn &already);
ContactsInner(ChatData *chat, MembersFilter membersFilter);
ContactsInner(UserData *bot);
void init();
void initList();
void paintDialog(Painter &p, PeerData *peer, ContactData *data, bool sel);
void updateFilter(QString filter = QString());
void selectSkip(int32 dir);
void selectSkipPage(int32 h, int32 dir);
QVector<UserData*> selected();
QVector<MTPInputUser> selectedInputs();
PeerData *selectedUser();
bool allAdmins() const {
return _allAdmins.checked();
}
void loadProfilePhotos(int32 yFrom);
void chooseParticipant();
void changeCheckState(Dialogs::Row *row);
void changeCheckState(ContactData *data, PeerData *peer);
void peopleReceived(const QString &query, const QVector<MTPPeer> &people);
void refresh();
ChatData *chat() const;
ChannelData *channel() const;
MembersFilter membersFilter() const;
UserData *bot() const;
CreatingGroupType creating() const;
bool sharingBotGame() const;
int32 selectedCount() const;
bool hasAlreadyMembersInChannel() const {
return !_already.isEmpty();
}
void saving(bool flag);
~ContactsInner();
signals:
void mustScrollTo(int ymin, int ymax);
void selectAllQuery();
void searchByUsername();
void chosenChanged();
void adminAdded();
void addRequested();
public slots:
void onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow);
void updateSel();
void peerUpdated(PeerData *peer);
void onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars);
void onAddBot();
void onAddAdmin();
void onNoAddAdminBox(QObject *obj);
void onAllAdminsChanged();
protected:
void paintEvent(QPaintEvent *e) override;
void enterEvent(QEvent *e) override;
void leaveEvent(QEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
private:
void updateSelectedRow();
void addAdminDone(const MTPUpdates &result, mtpRequestId req);
bool addAdminFail(const RPCError &error, mtpRequestId req);
template <typename FilterCallback>
void addDialogsToList(FilterCallback callback);
int32 _rowHeight;
int _newItemHeight = 0;
bool _newItemSel = false;
ChatData *_chat = nullptr;
ChannelData *_channel = nullptr;
MembersFilter _membersFilter = MembersFilterRecent;
UserData *_bot = nullptr;
CreatingGroupType _creating = CreatingGroupNone;
MembersAlreadyIn _already;
Checkbox _allAdmins;
int32 _aboutWidth;
Text _aboutAllAdmins, _aboutAdmins;
PeerData *_addToPeer = nullptr;
UserData *_addAdmin = nullptr;
mtpRequestId _addAdminRequestId = 0;
ConfirmBox *_addAdminBox = nullptr;
int32 _time;
std_::unique_ptr<Dialogs::IndexedList> _customList;
Dialogs::IndexedList *_contacts = nullptr;
Dialogs::Row *_sel = nullptr;
QString _filter;
typedef QVector<Dialogs::Row*> FilteredDialogs;
FilteredDialogs _filtered;
int _filteredSel = -1;
bool _mouseSel = false;
int _selCount = 0;
struct ContactData {
Text name;
QString online;
bool onlineColor;
bool inchat;
bool check;
inline Ui::RoundImageCheckbox::PaintRoundImage PaintUserpicCallback(PeerData *peer) {
return [peer](Painter &p, int x, int y, int outerWidth, int size) {
peer->paintUserpicLeft(p, size, x, y, outerWidth);
};
typedef QMap<PeerData*, ContactData*> ContactsData;
ContactsData _contactsData;
typedef QMap<PeerData*, bool> CheckedContacts;
CheckedContacts _checkedContacts;
ContactData *contactData(Dialogs::Row *row);
bool _searching = false;
QString _lastQuery;
typedef QVector<PeerData*> ByUsernameRows;
typedef QVector<ContactData*> ByUsernameDatas;
ByUsernameRows _byUsername, _byUsernameFiltered;
ByUsernameDatas d_byUsername, d_byUsernameFiltered; // filtered is partly subset of d_byUsername, partly subset of _byUsernameDatas
ByUsernameDatas _byUsernameDatas;
int _byUsernameSel = -1;
QPoint _lastMousePos;
LinkButton _addContactLnk;
bool _saving = false;
bool _allAdminsChecked = false;
};
}
class ContactsBox : public ItemListBox, public RPCSender {
Q_OBJECT
@ -205,10 +58,7 @@ public:
signals:
void adminAdded();
public slots:
void onFilterUpdate();
void onFilterCancel();
void onChosenChanged();
private slots:
void onScroll();
void onInvite();
@ -231,15 +81,21 @@ protected:
private:
void init();
int getTopScrollSkip() const;
void updateScrollSkips();
void onFilterUpdate(const QString &filter);
void onPeerSelectedChanged(PeerData *peer, bool checked);
void addPeerToMultiSelect(PeerData *peer, bool skipAnimation = false);
ContactsInner _inner;
InputField _filter;
IconedButton _filterCancel;
class Inner;
ChildWidget<Inner> _inner;
ChildWidget<Ui::WidgetSlideWrap<Ui::MultiSelect>> _select;
BoxButton _next, _cancel;
MembersFilter _membersFilter;
ScrollableBoxShadow _topShadow, *_bottomShadow;
ScrollableBoxShadow _topShadow;
ScrollableBoxShadow *_bottomShadow = nullptr;
void peopleReceived(const MTPcontacts_Found &result, mtpRequestId req);
bool peopleFailed(const RPCError &error, mtpRequestId req);
@ -255,7 +111,7 @@ private:
typedef QMap<mtpRequestId, QString> PeopleQueries;
PeopleQueries _peopleQueries;
int32 _saveRequestId;
mtpRequestId _saveRequestId = 0;
// saving admins
void saveAdminsDone(const MTPUpdates &result);
@ -275,50 +131,75 @@ private:
};
class MembersInner : public TWidget, public RPCSender, private base::Subscriber {
// This class is hold in header because it requires Qt preprocessing.
class ContactsBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber {
Q_OBJECT
private:
struct MemberData;
public:
MembersInner(ChannelData *channel, MembersFilter filter);
Inner(QWidget *parent, CreatingGroupType creating = CreatingGroupNone);
Inner(QWidget *parent, ChannelData *channel, MembersFilter membersFilter, const MembersAlreadyIn &already);
Inner(QWidget *parent, ChatData *chat, MembersFilter membersFilter);
Inner(QWidget *parent, UserData *bot);
void paintDialog(Painter &p, PeerData *peer, MemberData *data, bool sel, bool kickSel, bool kickDown);
void setPeerSelectedChangedCallback(base::lambda_unique<void(PeerData *peer, bool selected)> callback);
void peerUnselected(PeerData *peer);
void updateFilter(QString filter = QString());
void updateSelection();
void selectSkip(int32 dir);
void selectSkipPage(int32 h, int32 dir);
QVector<UserData*> selected();
QVector<MTPInputUser> selectedInputs();
bool allAdmins() const {
return _allAdmins.checked();
}
void setAllAdminsChangedCallback(base::lambda_unique<void()> allAdminsChangedCallback) {
_allAdminsChangedCallback = std_::move(allAdminsChangedCallback);
}
void loadProfilePhotos(int32 yFrom);
void chooseParticipant();
void peopleReceived(const QString &query, const QVector<MTPPeer> &people);
void refresh();
ChatData *chat() const;
ChannelData *channel() const;
MembersFilter filter() const;
MembersFilter membersFilter() const;
UserData *bot() const;
CreatingGroupType creating() const;
bool isLoaded() const {
return !_loading;
bool sharingBotGame() const;
int32 selectedCount() const;
bool hasAlreadyMembersInChannel() const {
return !_already.isEmpty();
}
void clearSel();
MembersAlreadyIn already() const;
void saving(bool flag);
~MembersInner();
~Inner();
signals:
void mustScrollTo(int ymin, int ymax);
void searchByUsername();
void adminAdded();
void addRequested();
void loaded();
public slots:
void load();
private slots:
void onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow);
void updateSel();
void peerUpdated(PeerData *peer);
void onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars);
void onKickConfirm();
void onKickBoxDestroyed(QObject *obj);
void onAddBot();
void onAddAdmin();
void onNoAddAdminBox(QObject *obj);
void onAllAdminsChanged();
protected:
void paintEvent(QPaintEvent *e) override;
@ -326,96 +207,103 @@ protected:
void leaveEvent(QEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
private:
void updateSelectedRow();
MemberData *data(int32 index);
void membersReceived(const MTPchannels_ChannelParticipants &result, mtpRequestId req);
bool membersFailed(const RPCError &error, mtpRequestId req);
void kickDone(const MTPUpdates &result, mtpRequestId req);
void kickAdminDone(const MTPUpdates &result, mtpRequestId req);
bool kickFail(const RPCError &error, mtpRequestId req);
void removeKicked();
void clear();
int32 _rowHeight, _newItemHeight;
bool _newItemSel;
ChannelData *_channel;
MembersFilter _filter;
QString _kickText;
int32 _time, _kickWidth;
int32 _sel, _kickSel, _kickDown;
bool _mouseSel;
UserData *_kickConfirm;
mtpRequestId _kickRequestId;
ConfirmBox *_kickBox;
enum MemberRole {
MemberRoleNone,
MemberRoleSelf,
MemberRoleCreator,
MemberRoleEditor,
MemberRoleModerator,
MemberRoleKicked
};
struct MemberData {
Text name;
QString online;
bool onlineColor;
bool canKick;
};
bool _loading;
mtpRequestId _loadingRequestId;
typedef QVector<UserData*> MemberRows;
typedef QVector<QDateTime> MemberDates;
typedef QVector<MemberRole> MemberRoles;
typedef QVector<MemberData*> MemberDatas;
MemberRows _rows;
MemberDates _dates;
MemberRoles _roles;
MemberDatas _datas;
int32 _aboutWidth;
Text _about;
int32 _aboutHeight;
QPoint _lastMousePos;
};
class MembersBox : public ItemListBox {
Q_OBJECT
public:
MembersBox(ChannelData *channel, MembersFilter filter);
public slots:
void onScroll();
void onAdd();
void onAdminAdded();
protected:
void keyPressEvent(QKeyEvent *e) override;
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
private:
MembersInner _inner;
struct ContactData {
ContactData() = default;
ContactData(PeerData *peer, base::lambda_wrap<void()> updateCallback);
ContactsBox *_addBox;
std_::unique_ptr<Ui::RoundImageCheckbox> checkbox;
Text name;
QString statusText;
bool statusHasOnlineColor = false;
bool disabledChecked = false;
};
SingleTimer _loadTimer;
void init();
void initList();
void updateRowWithTop(int rowTop);
int getSelectedRowTop() const;
void updateSelectedRow();
int getRowTopWithPeer(PeerData *peer) const;
void updateRowWithPeer(PeerData *peer);
void addAdminDone(const MTPUpdates &result, mtpRequestId req);
bool addAdminFail(const RPCError &error, mtpRequestId req);
void paintDialog(Painter &p, uint64 ms, PeerData *peer, ContactData *data, bool sel);
void paintDisabledCheckUserpic(Painter &p, PeerData *peer, int x, int y, int outerWidth) const;
void changeCheckState(Dialogs::Row *row);
void changeCheckState(ContactData *data, PeerData *peer);
enum class ChangeStateWay {
Default,
SkipCallback,
};
void changePeerCheckState(ContactData *data, PeerData *peer, bool checked, ChangeStateWay useCallback = ChangeStateWay::Default);
template <typename FilterCallback>
void addDialogsToList(FilterCallback callback);
bool usingMultiSelect() const {
return (_chat != nullptr) || (_creating != CreatingGroupNone && (!_channel || _membersFilter != MembersFilter::Admins));
}
base::lambda_unique<void(PeerData *peer, bool selected)> _peerSelectedChangedCallback;
int32 _rowHeight;
int _newItemHeight = 0;
bool _newItemSel = false;
ChatData *_chat = nullptr;
ChannelData *_channel = nullptr;
MembersFilter _membersFilter = MembersFilter::Recent;
UserData *_bot = nullptr;
CreatingGroupType _creating = CreatingGroupNone;
MembersAlreadyIn _already;
Checkbox _allAdmins;
int32 _aboutWidth;
Text _aboutAllAdmins, _aboutAdmins;
base::lambda_unique<void()> _allAdminsChangedCallback;
PeerData *_addToPeer = nullptr;
UserData *_addAdmin = nullptr;
mtpRequestId _addAdminRequestId = 0;
ConfirmBox *_addAdminBox = nullptr;
int32 _time;
std_::unique_ptr<Dialogs::IndexedList> _customList;
Dialogs::IndexedList *_contacts = nullptr;
Dialogs::Row *_sel = nullptr;
QString _filter;
using FilteredDialogs = QVector<Dialogs::Row*>;
FilteredDialogs _filtered;
int _filteredSel = -1;
bool _mouseSel = false;
using ContactsData = QMap<PeerData*, ContactData*>;
ContactsData _contactsData;
using CheckedContacts = OrderedSet<PeerData*>;
CheckedContacts _checkedContacts;
ContactData *contactData(Dialogs::Row *row);
bool _searching = false;
QString _lastQuery;
using ByUsernameRows = QVector<PeerData*>;
using ByUsernameDatas = QVector<ContactData*>;
ByUsernameRows _byUsername, _byUsernameFiltered;
ByUsernameDatas d_byUsername, d_byUsernameFiltered; // filtered is partly subset of d_byUsername, partly subset of _byUsernameDatas
ByUsernameDatas _byUsernameDatas;
int _byUsernameSel = -1;
QPoint _lastMousePos;
LinkButton _addContactLnk;
bool _saving = false;
bool _allAdminsChecked = false;
};

View file

@ -0,0 +1,608 @@
/*
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-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "boxes/members_box.h"
#include "styles/style_boxes.h"
#include "styles/style_dialogs.h"
#include "lang.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "boxes/contactsbox.h"
#include "boxes/confirmbox.h"
#include "observer_peer.h"
MembersBox::MembersBox(ChannelData *channel, MembersFilter filter) : ItemListBox(st::boxScroll)
, _inner(this, channel, filter) {
ItemListBox::init(_inner);
connect(_inner, SIGNAL(addRequested()), this, SLOT(onAdd()));
connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll()));
connect(_inner, SIGNAL(mustScrollTo(int, int)), scrollArea(), SLOT(scrollToY(int, int)));
connect(&_loadTimer, SIGNAL(timeout()), _inner, SLOT(load()));
prepare();
}
void MembersBox::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Down) {
_inner->selectSkip(1);
} else if (e->key() == Qt::Key_Up) {
_inner->selectSkip(-1);
} else if (e->key() == Qt::Key_PageDown) {
_inner->selectSkipPage(scrollArea()->height(), 1);
} else if (e->key() == Qt::Key_PageUp) {
_inner->selectSkipPage(scrollArea()->height(), -1);
} else {
ItemListBox::keyPressEvent(e);
}
}
void MembersBox::paintEvent(QPaintEvent *e) {
Painter p(this);
if (paint(p)) return;
QString title(lang(_inner->filter() == MembersFilter::Recent ? lng_channel_members : lng_channel_admins));
paintTitle(p, title);
}
void MembersBox::resizeEvent(QResizeEvent *e) {
ItemListBox::resizeEvent(e);
_inner->resize(width(), _inner->height());
}
void MembersBox::onScroll() {
_inner->loadProfilePhotos(scrollArea()->scrollTop());
}
void MembersBox::onAdd() {
if (_inner->filter() == MembersFilter::Recent && _inner->channel()->membersCount() >= (_inner->channel()->isMegagroup() ? Global::MegagroupSizeMax() : Global::ChatSizeMax())) {
Ui::showLayer(new MaxInviteBox(_inner->channel()->inviteLink()), KeepOtherLayers);
return;
}
ContactsBox *box = new ContactsBox(_inner->channel(), _inner->filter(), _inner->already());
if (_inner->filter() == MembersFilter::Recent) {
Ui::showLayer(box);
} else {
_addBox = box;
connect(_addBox, SIGNAL(adminAdded()), this, SLOT(onAdminAdded()));
Ui::showLayer(_addBox, KeepOtherLayers);
}
}
void MembersBox::onAdminAdded() {
if (!_addBox) return;
_addBox->onClose();
_addBox = 0;
_loadTimer.start(ReloadChannelMembersTimeout);
}
MembersBox::Inner::Inner(QWidget *parent, ChannelData *channel, MembersFilter filter) : ScrolledWidget(parent)
, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom())
, _newItemHeight((channel->amCreator() && (channel->membersCount() < (channel->isMegagroup() ? Global::MegagroupSizeMax() : Global::ChatSizeMax()) || (!channel->isMegagroup() && !channel->isPublic()) || filter == MembersFilter::Admins)) ? st::contactsNewItemHeight : 0)
, _newItemSel(false)
, _channel(channel)
, _filter(filter)
, _kickText(lang(lng_profile_kick))
, _time(0)
, _kickWidth(st::normalFont->width(_kickText))
, _sel(-1)
, _kickSel(-1)
, _kickDown(-1)
, _mouseSel(false)
, _kickConfirm(0)
, _kickRequestId(0)
, _kickBox(0)
, _loading(true)
, _loadingRequestId(0)
, _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.right())
, _about(_aboutWidth)
, _aboutHeight(0) {
subscribe(FileDownload::ImageLoaded(), [this] { update(); });
connect(App::main(), SIGNAL(peerNameChanged(PeerData*,const PeerData::Names&,const PeerData::NameFirstChars&)), this, SLOT(onPeerNameChanged(PeerData*, const PeerData::Names&, const PeerData::NameFirstChars&)));
connect(App::main(), SIGNAL(peerPhotoChanged(PeerData*)), this, SLOT(peerUpdated(PeerData*)));
refresh();
load();
}
void MembersBox::Inner::load() {
if (!_loadingRequestId) {
_loadingRequestId = MTP::send(MTPchannels_GetParticipants(_channel->inputChannel, (_filter == MembersFilter::Recent) ? MTP_channelParticipantsRecent() : MTP_channelParticipantsAdmins(), MTP_int(0), MTP_int(Global::ChatSizeMax())), rpcDone(&Inner::membersReceived), rpcFail(&Inner::membersFailed));
}
}
void MembersBox::Inner::paintEvent(QPaintEvent *e) {
QRect r(e->rect());
Painter p(this);
_time = unixtime();
p.fillRect(r, st::white->b);
int32 yFrom = r.y() - st::membersPadding.top(), yTo = r.y() + r.height() - st::membersPadding.top();
p.translate(0, st::membersPadding.top());
if (_rows.isEmpty()) {
p.setFont(st::noContactsFont->f);
p.setPen(st::noContactsColor->p);
p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center);
} else {
if (_newItemHeight) {
p.fillRect(0, 0, width(), _newItemHeight, (_newItemSel ? st::contactsBgOver : st::white)->b);
st::contactsNewItemIcon.paint(p, 0, 0, width());
p.setFont(st::contactsNameFont);
p.setPen(st::contactsNewItemFg);
p.drawTextLeft(st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(), st::contactsNewItemTop, width(), lang(_filter == MembersFilter::Admins ? lng_channel_add_admins : lng_channel_add_members));
yFrom -= _newItemHeight;
yTo -= _newItemHeight;
p.translate(0, _newItemHeight);
}
int32 from = floorclamp(yFrom, _rowHeight, 0, _rows.size());
int32 to = ceilclamp(yTo, _rowHeight, 0, _rows.size());
p.translate(0, from * _rowHeight);
for (; from < to; ++from) {
bool sel = (from == _sel);
bool kickSel = (from == _kickSel && (_kickDown < 0 || from == _kickDown));
bool kickDown = kickSel && (from == _kickDown);
paintDialog(p, _rows[from], data(from), sel, kickSel, kickDown);
p.translate(0, _rowHeight);
}
if (to == _rows.size() && _filter == MembersFilter::Recent && (_rows.size() < _channel->membersCount() || _rows.size() >= Global::ChatSizeMax())) {
p.setPen(st::stickersReorderFg);
_about.draw(p, st::contactsPadding.left(), st::stickersReorderPadding.top(), _aboutWidth, style::al_center);
}
}
}
void MembersBox::Inner::enterEvent(QEvent *e) {
setMouseTracking(true);
}
void MembersBox::Inner::leaveEvent(QEvent *e) {
_mouseSel = false;
setMouseTracking(false);
if (_sel >= 0) {
clearSel();
}
}
void MembersBox::Inner::mouseMoveEvent(QMouseEvent *e) {
_mouseSel = true;
_lastMousePos = e->globalPos();
updateSel();
}
void MembersBox::Inner::mousePressEvent(QMouseEvent *e) {
_mouseSel = true;
_lastMousePos = e->globalPos();
updateSel();
if (e->button() == Qt::LeftButton && _kickSel < 0) {
chooseParticipant();
}
_kickDown = _kickSel;
update();
}
void MembersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
_mouseSel = true;
_lastMousePos = e->globalPos();
updateSel();
if (_kickDown >= 0 && _kickDown == _kickSel && !_kickRequestId) {
_kickConfirm = _rows.at(_kickSel);
if (_kickBox) _kickBox->deleteLater();
_kickBox = new ConfirmBox((_filter == MembersFilter::Recent ? (_channel->isMegagroup() ? lng_profile_sure_kick : lng_profile_sure_kick_channel) : lng_profile_sure_kick_admin)(lt_user, _kickConfirm->firstName));
connect(_kickBox, SIGNAL(confirmed()), this, SLOT(onKickConfirm()));
connect(_kickBox, SIGNAL(destroyed(QObject*)), this, SLOT(onKickBoxDestroyed(QObject*)));
Ui::showLayer(_kickBox, KeepOtherLayers);
}
_kickDown = -1;
}
void MembersBox::Inner::onKickBoxDestroyed(QObject *obj) {
if (_kickBox == obj) {
_kickBox = 0;
}
}
void MembersBox::Inner::onKickConfirm() {
if (_filter == MembersFilter::Recent) {
_kickRequestId = MTP::send(MTPchannels_KickFromChannel(_channel->inputChannel, _kickConfirm->inputUser, MTP_bool(true)), rpcDone(&Inner::kickDone), rpcFail(&Inner::kickFail));
} else {
_kickRequestId = MTP::send(MTPchannels_EditAdmin(_channel->inputChannel, _kickConfirm->inputUser, MTP_channelRoleEmpty()), rpcDone(&Inner::kickAdminDone), rpcFail(&Inner::kickFail));
}
}
void MembersBox::Inner::paintDialog(Painter &p, PeerData *peer, MemberData *data, bool sel, bool kickSel, bool kickDown) {
UserData *user = peer->asUser();
p.fillRect(0, 0, width(), _rowHeight, (sel ? st::contactsBgOver : st::white)->b);
peer->paintUserpicLeft(p, st::contactsPhotoSize, st::contactsPadding.left(), st::contactsPadding.top(), width());
p.setPen(st::black);
int32 namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left();
int32 namew = width() - namex - st::contactsPadding.right() - (data->canKick ? (_kickWidth + st::contactsCheckPosition.x() * 2) : 0);
if (peer->isVerified()) {
auto icon = &st::dialogsVerifiedIcon;
namew -= icon->width();
icon->paint(p, namex + qMin(data->name.maxWidth(), namew), st::contactsPadding.top() + st::contactsNameTop, width());
}
data->name.drawLeftElided(p, namex, st::contactsPadding.top() + st::contactsNameTop, namew, width());
if (data->canKick) {
p.setFont((kickSel ? st::linkOverFont : st::linkFont)->f);
if (kickDown) {
p.setPen(st::btnDefLink.downColor->p);
} else {
p.setPen(st::btnDefLink.color->p);
}
p.drawTextRight(st::contactsPadding.right() + st::contactsCheckPosition.x(), st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, width(), _kickText, _kickWidth);
}
p.setFont(st::contactsStatusFont->f);
p.setPen(data->onlineColor ? st::contactsStatusFgOnline : (sel ? st::contactsStatusFgOver : st::contactsStatusFg));
p.drawTextLeft(namex, st::contactsPadding.top() + st::contactsStatusTop, width(), data->online);
}
void MembersBox::Inner::selectSkip(int32 dir) {
_time = unixtime();
_mouseSel = false;
int cur = -1;
if (_newItemHeight && _newItemSel) {
cur = 0;
} else if (_sel >= 0) {
cur = _sel + (_newItemHeight ? 1 : 0);
}
cur += dir;
if (cur <= 0) {
_newItemSel = _newItemHeight ? true : false;
_sel = (_newItemSel || _rows.isEmpty()) ? -1 : 0;
} else if (cur >= _rows.size() + (_newItemHeight ? 1 : 0)) {
_sel = -1;
} else {
_sel = cur - (_newItemHeight ? 1 : 0);
}
if (dir > 0) {
if (_sel < 0 || _sel >= _rows.size()) {
_sel = -1;
}
} else {
if (!_rows.isEmpty()) {
if (_sel < 0 && !_newItemSel) _sel = _rows.size() - 1;
}
}
if (_newItemSel) {
emit mustScrollTo(0, _newItemHeight);
} else if (_sel >= 0) {
emit mustScrollTo(_newItemHeight + _sel * _rowHeight, _newItemHeight + (_sel + 1) * _rowHeight);
}
update();
}
void MembersBox::Inner::selectSkipPage(int32 h, int32 dir) {
int32 points = h / _rowHeight;
if (!points) return;
selectSkip(points * dir);
}
void MembersBox::Inner::loadProfilePhotos(int32 yFrom) {
int32 yTo = yFrom + (parentWidget() ? parentWidget()->height() : App::wnd()->height()) * 5;
MTP::clearLoaderPriorities();
if (yTo < 0) return;
if (yFrom < 0) yFrom = 0;
if (!_rows.isEmpty()) {
int32 from = (yFrom - _newItemHeight) / _rowHeight;
if (from < 0) from = 0;
if (from < _rows.size()) {
int32 to = ((yTo - _newItemHeight) / _rowHeight) + 1;
if (to > _rows.size()) to = _rows.size();
for (; from < to; ++from) {
_rows[from]->loadUserpic();
}
}
}
}
void MembersBox::Inner::chooseParticipant() {
if (_newItemSel) {
emit addRequested();
return;
}
if (_sel < 0 || _sel >= _rows.size()) return;
if (PeerData *peer = _rows[_sel]) {
Ui::hideLayer();
Ui::showPeerProfile(peer);
}
}
void MembersBox::Inner::refresh() {
if (_rows.isEmpty()) {
resize(width(), st::membersPadding.top() + st::noContactsHeight + st::membersPadding.bottom());
_aboutHeight = 0;
} else {
_about.setText(st::boxTextFont, lng_channel_only_last_shown(lt_count, _rows.size()));
_aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom();
if (_filter != MembersFilter::Recent || (_rows.size() >= _channel->membersCount() && _rows.size() < Global::ChatSizeMax())) {
_aboutHeight = 0;
}
resize(width(), st::membersPadding.top() + _newItemHeight + _rows.size() * _rowHeight + st::membersPadding.bottom() + _aboutHeight);
}
update();
}
ChannelData *MembersBox::Inner::channel() const {
return _channel;
}
MembersFilter MembersBox::Inner::filter() const {
return _filter;
}
MembersAlreadyIn MembersBox::Inner::already() const {
MembersAlreadyIn result;
for_const (auto peer, _rows) {
if (peer->isUser()) {
result.insert(peer->asUser());
}
}
return result;
}
void MembersBox::Inner::clearSel() {
updateSelectedRow();
_newItemSel = false;
_sel = _kickSel = _kickDown = -1;
_lastMousePos = QCursor::pos();
updateSel();
}
MembersBox::Inner::MemberData *MembersBox::Inner::data(int32 index) {
if (MemberData *result = _datas.at(index)) {
return result;
}
MemberData *result = _datas[index] = new MemberData();
result->name.setText(st::contactsNameFont, _rows[index]->name, _textNameOptions);
int32 t = unixtime();
result->online = App::onlineText(_rows[index], t);// lng_mediaview_date_time(lt_date, _dates[index].date().toString(qsl("dd.MM.yy")), lt_time, _dates[index].time().toString(cTimeFormat()));
result->onlineColor = App::onlineColorUse(_rows[index], t);
if (_filter == MembersFilter::Recent) {
result->canKick = (_channel->amCreator() || _channel->amEditor() || _channel->amModerator()) ? (_roles[index] == MemberRole::None) : false;
} else if (_filter == MembersFilter::Admins) {
result->canKick = _channel->amCreator() ? (_roles[index] == MemberRole::Editor || _roles[index] == MemberRole::Moderator) : false;
} else {
result->canKick = false;
}
return result;
}
void MembersBox::Inner::clear() {
for (int32 i = 0, l = _datas.size(); i < l; ++i) {
delete _datas.at(i);
}
_datas.clear();
_rows.clear();
_dates.clear();
_roles.clear();
if (_kickBox) _kickBox->deleteLater();
clearSel();
}
MembersBox::Inner::~Inner() {
clear();
}
void MembersBox::Inner::updateSel() {
if (!_mouseSel) return;
QPoint p(mapFromGlobal(_lastMousePos));
p.setY(p.y() - st::membersPadding.top());
bool in = parentWidget()->rect().contains(parentWidget()->mapFromGlobal(_lastMousePos));
bool newItemSel = (in && p.y() >= 0 && p.y() < _newItemHeight);
int32 newSel = (in && !newItemSel && p.y() >= _newItemHeight && p.y() < _newItemHeight + _rows.size() * _rowHeight) ? ((p.y() - _newItemHeight) / _rowHeight) : -1;
int32 newKickSel = newSel;
if (newSel >= 0 && (!data(newSel)->canKick || !QRect(width() - _kickWidth - st::contactsPadding.right() - st::contactsCheckPosition.x(), _newItemHeight + newSel * _rowHeight + st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, _kickWidth, st::normalFont->height).contains(p))) {
newKickSel = -1;
}
if (newSel != _sel || newKickSel != _kickSel || newItemSel != _newItemSel) {
updateSelectedRow();
_newItemSel = newItemSel;
_sel = newSel;
_kickSel = newKickSel;
updateSelectedRow();
setCursor(_kickSel >= 0 ? style::cur_pointer : style::cur_default);
}
}
void MembersBox::Inner::peerUpdated(PeerData *peer) {
update();
}
void MembersBox::Inner::updateSelectedRow() {
if (_newItemSel) {
update(0, st::membersPadding.top(), width(), _newItemHeight);
}
if (_sel >= 0) {
update(0, st::membersPadding.top() + _newItemHeight + _sel * _rowHeight, width(), _rowHeight);
}
}
void MembersBox::Inner::onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars) {
for (int32 i = 0, l = _rows.size(); i < l; ++i) {
if (_rows.at(i) == peer) {
if (_datas.at(i)) {
_datas.at(i)->name.setText(st::contactsNameFont, peer->name, _textNameOptions);
update(0, st::membersPadding.top() + i * _rowHeight, width(), _rowHeight);
} else {
break;
}
}
}
}
void MembersBox::Inner::membersReceived(const MTPchannels_ChannelParticipants &result, mtpRequestId req) {
clear();
_loadingRequestId = 0;
if (result.type() == mtpc_channels_channelParticipants) {
const auto &d(result.c_channels_channelParticipants());
const auto &v(d.vparticipants.c_vector().v);
_rows.reserve(v.size());
_datas.reserve(v.size());
_dates.reserve(v.size());
_roles.reserve(v.size());
if (_filter == MembersFilter::Recent && _channel->membersCount() < d.vcount.v) {
_channel->setMembersCount(d.vcount.v);
if (App::main()) emit App::main()->peerUpdated(_channel);
} else if (_filter == MembersFilter::Admins && _channel->adminsCount() < d.vcount.v) {
_channel->setAdminsCount(d.vcount.v);
if (App::main()) emit App::main()->peerUpdated(_channel);
}
App::feedUsers(d.vusers);
for (QVector<MTPChannelParticipant>::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) {
int32 userId = 0, addedTime = 0;
MemberRole role = MemberRole::None;
switch (i->type()) {
case mtpc_channelParticipant:
userId = i->c_channelParticipant().vuser_id.v;
addedTime = i->c_channelParticipant().vdate.v;
break;
case mtpc_channelParticipantSelf:
role = MemberRole::Self;
userId = i->c_channelParticipantSelf().vuser_id.v;
addedTime = i->c_channelParticipantSelf().vdate.v;
break;
case mtpc_channelParticipantModerator:
role = MemberRole::Moderator;
userId = i->c_channelParticipantModerator().vuser_id.v;
addedTime = i->c_channelParticipantModerator().vdate.v;
break;
case mtpc_channelParticipantEditor:
role = MemberRole::Editor;
userId = i->c_channelParticipantEditor().vuser_id.v;
addedTime = i->c_channelParticipantEditor().vdate.v;
break;
case mtpc_channelParticipantKicked:
userId = i->c_channelParticipantKicked().vuser_id.v;
addedTime = i->c_channelParticipantKicked().vdate.v;
role = MemberRole::Kicked;
break;
case mtpc_channelParticipantCreator:
userId = i->c_channelParticipantCreator().vuser_id.v;
addedTime = _channel->date;
role = MemberRole::Creator;
break;
}
if (UserData *user = App::userLoaded(userId)) {
_rows.push_back(user);
_dates.push_back(date(addedTime));
_roles.push_back(role);
_datas.push_back(0);
}
}
// update admins if we got all of them
if (_filter == MembersFilter::Admins && _channel->isMegagroup() && _rows.size() < Global::ChatSizeMax()) {
_channel->mgInfo->lastAdmins.clear();
for (int32 i = 0, l = _rows.size(); i != l; ++i) {
if (_roles.at(i) == MemberRole::Creator || _roles.at(i) == MemberRole::Editor) {
_channel->mgInfo->lastAdmins.insert(_rows.at(i));
}
}
Notify::peerUpdatedDelayed(_channel, Notify::PeerUpdate::Flag::AdminsChanged);
}
}
if (_rows.isEmpty()) {
_rows.push_back(App::self());
_dates.push_back(date(MTP_int(_channel->date)));
_roles.push_back(MemberRole::Self);
_datas.push_back(0);
}
clearSel();
_loading = false;
refresh();
emit loaded();
}
bool MembersBox::Inner::membersFailed(const RPCError &error, mtpRequestId req) {
if (MTP::isDefaultHandledError(error)) return false;
Ui::hideLayer();
return true;
}
void MembersBox::Inner::kickDone(const MTPUpdates &result, mtpRequestId req) {
App::main()->sentUpdatesReceived(result);
if (_kickRequestId != req) return;
removeKicked();
if (_kickBox) _kickBox->onClose();
}
void MembersBox::Inner::kickAdminDone(const MTPUpdates &result, mtpRequestId req) {
if (_kickRequestId != req) return;
if (App::main()) App::main()->sentUpdatesReceived(result);
removeKicked();
if (_kickBox) _kickBox->onClose();
}
bool MembersBox::Inner::kickFail(const RPCError &error, mtpRequestId req) {
if (MTP::isDefaultHandledError(error)) return false;
if (_kickBox) _kickBox->onClose();
load();
return true;
}
void MembersBox::Inner::removeKicked() {
_kickRequestId = 0;
int32 index = _rows.indexOf(_kickConfirm);
if (index >= 0) {
_rows.removeAt(index);
delete _datas.at(index);
_datas.removeAt(index);
_dates.removeAt(index);
_roles.removeAt(index);
clearSel();
if (_filter == MembersFilter::Recent && _channel->membersCount() > 1) {
_channel->setMembersCount(_channel->membersCount() - 1);
if (App::main()) emit App::main()->peerUpdated(_channel);
} else if (_filter == MembersFilter::Admins && _channel->adminsCount() > 1) {
_channel->setAdminsCount(_channel->adminsCount() - 1);
if (App::main()) emit App::main()->peerUpdated(_channel);
}
refresh();
}
_kickConfirm = 0;
}

View file

@ -0,0 +1,178 @@
/*
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-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "abstractbox.h"
#include "core/single_timer.h"
#include "ui/effects/round_image_checkbox.h"
class ContactsBox;
class ConfirmBox;
enum class MembersFilter {
Recent,
Admins,
};
using MembersAlreadyIn = OrderedSet<UserData*>;
class MembersBox : public ItemListBox {
Q_OBJECT
public:
MembersBox(ChannelData *channel, MembersFilter filter);
public slots:
void onScroll();
void onAdd();
void onAdminAdded();
protected:
void keyPressEvent(QKeyEvent *e) override;
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
private:
class Inner;
ChildWidget<Inner> _inner;
ContactsBox *_addBox = nullptr;
SingleTimer _loadTimer;
};
// This class is hold in header because it requires Qt preprocessing.
class MembersBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber {
Q_OBJECT
public:
Inner(QWidget *parent, ChannelData *channel, MembersFilter filter);
void selectSkip(int32 dir);
void selectSkipPage(int32 h, int32 dir);
void loadProfilePhotos(int32 yFrom);
void chooseParticipant();
void refresh();
ChannelData *channel() const;
MembersFilter filter() const;
bool isLoaded() const {
return !_loading;
}
void clearSel();
MembersAlreadyIn already() const;
~Inner();
signals:
void mustScrollTo(int ymin, int ymax);
void addRequested();
void loaded();
public slots:
void load();
void updateSel();
void peerUpdated(PeerData *peer);
void onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars);
void onKickConfirm();
void onKickBoxDestroyed(QObject *obj);
protected:
void paintEvent(QPaintEvent *e) override;
void enterEvent(QEvent *e) override;
void leaveEvent(QEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
private:
struct MemberData {
Text name;
QString online;
bool onlineColor;
bool canKick;
};
void updateSelectedRow();
MemberData *data(int32 index);
void paintDialog(Painter &p, PeerData *peer, MemberData *data, bool sel, bool kickSel, bool kickDown);
void membersReceived(const MTPchannels_ChannelParticipants &result, mtpRequestId req);
bool membersFailed(const RPCError &error, mtpRequestId req);
void kickDone(const MTPUpdates &result, mtpRequestId req);
void kickAdminDone(const MTPUpdates &result, mtpRequestId req);
bool kickFail(const RPCError &error, mtpRequestId req);
void removeKicked();
void clear();
int32 _rowHeight, _newItemHeight;
bool _newItemSel;
ChannelData *_channel;
MembersFilter _filter;
QString _kickText;
int32 _time, _kickWidth;
int32 _sel, _kickSel, _kickDown;
bool _mouseSel;
UserData *_kickConfirm;
mtpRequestId _kickRequestId;
ConfirmBox *_kickBox;
enum class MemberRole {
None,
Self,
Creator,
Editor,
Moderator,
Kicked
};
bool _loading;
mtpRequestId _loadingRequestId;
typedef QVector<UserData*> MemberRows;
typedef QVector<QDateTime> MemberDates;
typedef QVector<MemberRole> MemberRoles;
typedef QVector<MemberData*> MemberDatas;
MemberRows _rows;
MemberDates _dates;
MemberRoles _roles;
MemberDatas _datas;
int32 _aboutWidth;
Text _about;
int32 _aboutHeight;
QPoint _lastMousePos;
};

View file

@ -30,213 +30,23 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "countries.h"
#include "confirmbox.h"
SessionsInner::SessionsInner(SessionsList *list, SessionData *current) : TWidget()
, _list(list)
, _current(current)
, _terminating(0)
, _terminateAll(this, lang(lng_sessions_terminate_all), st::redBoxLinkButton)
, _terminateBox(0) {
connect(&_terminateAll, SIGNAL(clicked()), this, SLOT(onTerminateAll()));
_terminateAll.hide();
setAttribute(Qt::WA_OpaquePaintEvent);
}
void SessionsInner::paintEvent(QPaintEvent *e) {
QRect r(e->rect());
Painter p(this);
p.fillRect(r, st::white->b);
int32 x = st::sessionPadding.left(), xact = st::sessionTerminateSkip + st::sessionTerminate.iconPos.x();// st::sessionTerminateSkip + st::sessionTerminate.width + st::sessionTerminateSkip;
int32 w = width();
if (_current->active.isEmpty() && _list->isEmpty()) {
p.setFont(st::noContactsFont->f);
p.setPen(st::noContactsColor->p);
p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center);
return;
}
if (r.y() <= st::sessionCurrentHeight) {
p.translate(0, st::sessionCurrentPadding.top());
p.setFont(st::sessionNameFont->f);
p.setPen(st::black->p);
p.drawTextLeft(x, st::sessionPadding.top(), w, _current->name, _current->nameWidth);
p.setFont(st::sessionActiveFont->f);
p.setPen(st::sessionActiveColor->p);
p.drawTextRight(x, st::sessionPadding.top(), w, _current->active, _current->activeWidth);
p.setFont(st::sessionInfoFont->f);
p.setPen(st::black->p);
p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height, w, _current->info, _current->infoWidth);
p.setPen(st::sessionInfoColor->p);
p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height + st::sessionInfoFont->height, w, _current->ip, _current->ipWidth);
}
p.translate(0, st::sessionCurrentHeight - st::sessionCurrentPadding.top());
if (_list->isEmpty()) {
p.setFont(st::sessionInfoFont->f);
p.setPen(st::sessionInfoColor->p);
p.drawText(QRect(st::sessionPadding.left(), 0, width() - st::sessionPadding.left() - st::sessionPadding.right(), st::noContactsHeight), lang(lng_sessions_other_desc), style::al_topleft);
return;
}
p.setFont(st::linkFont->f);
int32 count = _list->size();
int32 from = floorclamp(r.y() - st::sessionCurrentHeight, st::sessionHeight, 0, count);
int32 to = ceilclamp(r.y() + r.height() - st::sessionCurrentHeight, st::sessionHeight, 0, count);
p.translate(0, from * st::sessionHeight);
for (int32 i = from; i < to; ++i) {
const SessionData &auth(_list->at(i));
p.setFont(st::sessionNameFont->f);
p.setPen(st::black->p);
p.drawTextLeft(x, st::sessionPadding.top(), w, auth.name, auth.nameWidth);
p.setFont(st::sessionActiveFont->f);
p.setPen(st::sessionActiveColor->p);
p.drawTextRight(xact, st::sessionPadding.top(), w, auth.active, auth.activeWidth);
p.setFont(st::sessionInfoFont->f);
p.setPen(st::black->p);
p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height, w, auth.info, auth.infoWidth);
p.setPen(st::sessionInfoColor->p);
p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height + st::sessionInfoFont->height, w, auth.ip, auth.ipWidth);
p.translate(0, st::sessionHeight);
}
}
void SessionsInner::onTerminate() {
for (TerminateButtons::iterator i = _terminateButtons.begin(), e = _terminateButtons.end(); i != e; ++i) {
if (i.value()->getState() & Button::StateOver) {
_terminating = i.key();
if (_terminateBox) _terminateBox->deleteLater();
_terminateBox = new ConfirmBox(lang(lng_settings_reset_one_sure), lang(lng_settings_reset_button), st::attentionBoxButton);
connect(_terminateBox, SIGNAL(confirmed()), this, SLOT(onTerminateSure()));
connect(_terminateBox, SIGNAL(destroyed(QObject*)), this, SLOT(onNoTerminateBox(QObject*)));
Ui::showLayer(_terminateBox, KeepOtherLayers);
}
}
}
void SessionsInner::onTerminateSure() {
if (_terminateBox) {
_terminateBox->onClose();
_terminateBox = 0;
}
MTP::send(MTPaccount_ResetAuthorization(MTP_long(_terminating)), rpcDone(&SessionsInner::terminateDone, _terminating), rpcFail(&SessionsInner::terminateFail, _terminating));
TerminateButtons::iterator i = _terminateButtons.find(_terminating);
if (i != _terminateButtons.cend()) {
i.value()->clearState();
i.value()->hide();
}
}
void SessionsInner::onTerminateAll() {
if (_terminateBox) _terminateBox->deleteLater();
_terminateBox = new ConfirmBox(lang(lng_settings_reset_sure), lang(lng_settings_reset_button), st::attentionBoxButton);
connect(_terminateBox, SIGNAL(confirmed()), this, SLOT(onTerminateAllSure()));
connect(_terminateBox, SIGNAL(destroyed(QObject*)), this, SLOT(onNoTerminateBox(QObject*)));
Ui::showLayer(_terminateBox, KeepOtherLayers);
}
void SessionsInner::onTerminateAllSure() {
if (_terminateBox) {
_terminateBox->onClose();
_terminateBox = 0;
}
MTP::send(MTPauth_ResetAuthorizations(), rpcDone(&SessionsInner::terminateAllDone), rpcFail(&SessionsInner::terminateAllFail));
emit terminateAll();
}
void SessionsInner::onNoTerminateBox(QObject *obj) {
if (obj == _terminateBox) _terminateBox = 0;
}
void SessionsInner::terminateDone(uint64 hash, const MTPBool &result) {
for (int32 i = 0, l = _list->size(); i < l; ++i) {
if (_list->at(i).hash == hash) {
_list->removeAt(i);
break;
}
}
listUpdated();
emit oneTerminated();
}
bool SessionsInner::terminateFail(uint64 hash, const RPCError &error) {
if (MTP::isDefaultHandledError(error)) return false;
TerminateButtons::iterator i = _terminateButtons.find(hash);
if (i != _terminateButtons.end()) {
i.value()->show();
return true;
}
return false;
}
void SessionsInner::terminateAllDone(const MTPBool &result) {
emit allTerminated();
}
bool SessionsInner::terminateAllFail(const RPCError &error) {
if (MTP::isDefaultHandledError(error)) return false;
emit allTerminated();
return true;
}
void SessionsInner::resizeEvent(QResizeEvent *e) {
_terminateAll.moveToLeft(st::sessionPadding.left(), st::sessionCurrentPadding.top() + st::sessionHeight + st::sessionCurrentPadding.bottom());
}
void SessionsInner::listUpdated() {
if (_list->isEmpty()) {
_terminateAll.hide();
} else {
_terminateAll.show();
}
for (TerminateButtons::iterator i = _terminateButtons.begin(), e = _terminateButtons.end(); i != e; ++i) {
i.value()->move(0, -1);
}
for (int32 i = 0, l = _list->size(); i < l; ++i) {
TerminateButtons::iterator j = _terminateButtons.find(_list->at(i).hash);
if (j == _terminateButtons.cend()) {
j = _terminateButtons.insert(_list->at(i).hash, new IconedButton(this, st::sessionTerminate));
connect(j.value(), SIGNAL(clicked()), this, SLOT(onTerminate()));
}
j.value()->moveToRight(st::sessionTerminateSkip, st::sessionCurrentHeight + i * st::sessionHeight + st::sessionTerminateTop, width());
j.value()->show();
}
for (TerminateButtons::iterator i = _terminateButtons.begin(); i != _terminateButtons.cend();) {
if (i.value()->y() >= 0) {
++i;
} else {
delete i.value();
i = _terminateButtons.erase(i);
}
}
resize(width(), _list->isEmpty() ? (st::sessionCurrentHeight + st::noContactsHeight) : (st::sessionCurrentHeight + _list->size() * st::sessionHeight));
update();
}
SessionsBox::SessionsBox() : ScrollableBox(st::sessionsScroll)
, _loading(true)
, _inner(&_list, &_current)
, _inner(this, &_list, &_current)
, _shadow(this)
, _done(this, lang(lng_about_done), st::defaultBoxButton)
, _shortPollRequest(0) {
setMaxHeight(st::sessionsHeight);
connect(&_done, SIGNAL(clicked()), this, SLOT(onClose()));
connect(&_inner, SIGNAL(oneTerminated()), this, SLOT(onOneTerminated()));
connect(&_inner, SIGNAL(allTerminated()), this, SLOT(onAllTerminated()));
connect(&_inner, SIGNAL(terminateAll()), this, SLOT(onTerminateAll()));
connect(_inner, SIGNAL(oneTerminated()), this, SLOT(onOneTerminated()));
connect(_inner, SIGNAL(allTerminated()), this, SLOT(onAllTerminated()));
connect(_inner, SIGNAL(terminateAll()), this, SLOT(onTerminateAll()));
connect(App::wnd(), SIGNAL(newAuthorization()), this, SLOT(onNewAuthorization()));
connect(&_shortPollTimer, SIGNAL(timeout()), this, SLOT(onShortPollAuthorizations()));
init(&_inner, st::boxButtonPadding.bottom() + _done.height() + st::boxButtonPadding.top(), st::boxTitleHeight);
_inner.resize(width(), st::noContactsHeight);
init(_inner, st::boxButtonPadding.bottom() + _done.height() + st::boxButtonPadding.top(), st::boxTitleHeight);
_inner->resize(width(), st::noContactsHeight);
prepare();
@ -291,7 +101,7 @@ void SessionsBox::gotAuthorizations(const MTPaccount_Authorizations &result) {
for (int32 i = 0; i < l; ++i) {
const auto &d(v.at(i).c_authorization());
SessionData data;
Data data;
data.hash = d.vhash.v;
QString appName, appVer = qs(d.vapp_version), systemVer = qs(d.vsystem_version), deviceModel = qs(d.vdevice_model);
@ -383,7 +193,7 @@ void SessionsBox::gotAuthorizations(const MTPaccount_Authorizations &result) {
}
}
}
_inner.listUpdated();
_inner->listUpdated();
if (!_done.isHidden()) {
showAll();
update();
@ -430,4 +240,194 @@ void SessionsBox::onTerminateAll() {
showAll();
update();
}
}
}
SessionsBox::Inner::Inner(QWidget *parent, SessionsBox::List *list, SessionsBox::Data *current) : ScrolledWidget(parent)
, _list(list)
, _current(current)
, _terminating(0)
, _terminateAll(this, lang(lng_sessions_terminate_all), st::redBoxLinkButton)
, _terminateBox(0) {
connect(&_terminateAll, SIGNAL(clicked()), this, SLOT(onTerminateAll()));
_terminateAll.hide();
setAttribute(Qt::WA_OpaquePaintEvent);
}
void SessionsBox::Inner::paintEvent(QPaintEvent *e) {
QRect r(e->rect());
Painter p(this);
p.fillRect(r, st::white->b);
int32 x = st::sessionPadding.left(), xact = st::sessionTerminateSkip + st::sessionTerminate.iconPos.x();// st::sessionTerminateSkip + st::sessionTerminate.width + st::sessionTerminateSkip;
int32 w = width();
if (_current->active.isEmpty() && _list->isEmpty()) {
p.setFont(st::noContactsFont->f);
p.setPen(st::noContactsColor->p);
p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center);
return;
}
if (r.y() <= st::sessionCurrentHeight) {
p.translate(0, st::sessionCurrentPadding.top());
p.setFont(st::sessionNameFont->f);
p.setPen(st::black->p);
p.drawTextLeft(x, st::sessionPadding.top(), w, _current->name, _current->nameWidth);
p.setFont(st::sessionActiveFont->f);
p.setPen(st::sessionActiveColor->p);
p.drawTextRight(x, st::sessionPadding.top(), w, _current->active, _current->activeWidth);
p.setFont(st::sessionInfoFont->f);
p.setPen(st::black->p);
p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height, w, _current->info, _current->infoWidth);
p.setPen(st::sessionInfoColor->p);
p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height + st::sessionInfoFont->height, w, _current->ip, _current->ipWidth);
}
p.translate(0, st::sessionCurrentHeight - st::sessionCurrentPadding.top());
if (_list->isEmpty()) {
p.setFont(st::sessionInfoFont->f);
p.setPen(st::sessionInfoColor->p);
p.drawText(QRect(st::sessionPadding.left(), 0, width() - st::sessionPadding.left() - st::sessionPadding.right(), st::noContactsHeight), lang(lng_sessions_other_desc), style::al_topleft);
return;
}
p.setFont(st::linkFont->f);
int32 count = _list->size();
int32 from = floorclamp(r.y() - st::sessionCurrentHeight, st::sessionHeight, 0, count);
int32 to = ceilclamp(r.y() + r.height() - st::sessionCurrentHeight, st::sessionHeight, 0, count);
p.translate(0, from * st::sessionHeight);
for (int32 i = from; i < to; ++i) {
const SessionsBox::Data &auth(_list->at(i));
p.setFont(st::sessionNameFont->f);
p.setPen(st::black->p);
p.drawTextLeft(x, st::sessionPadding.top(), w, auth.name, auth.nameWidth);
p.setFont(st::sessionActiveFont->f);
p.setPen(st::sessionActiveColor->p);
p.drawTextRight(xact, st::sessionPadding.top(), w, auth.active, auth.activeWidth);
p.setFont(st::sessionInfoFont->f);
p.setPen(st::black->p);
p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height, w, auth.info, auth.infoWidth);
p.setPen(st::sessionInfoColor->p);
p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height + st::sessionInfoFont->height, w, auth.ip, auth.ipWidth);
p.translate(0, st::sessionHeight);
}
}
void SessionsBox::Inner::onTerminate() {
for (TerminateButtons::iterator i = _terminateButtons.begin(), e = _terminateButtons.end(); i != e; ++i) {
if (i.value()->getState() & Button::StateOver) {
_terminating = i.key();
if (_terminateBox) _terminateBox->deleteLater();
_terminateBox = new ConfirmBox(lang(lng_settings_reset_one_sure), lang(lng_settings_reset_button), st::attentionBoxButton);
connect(_terminateBox, SIGNAL(confirmed()), this, SLOT(onTerminateSure()));
connect(_terminateBox, SIGNAL(destroyed(QObject*)), this, SLOT(onNoTerminateBox(QObject*)));
Ui::showLayer(_terminateBox, KeepOtherLayers);
}
}
}
void SessionsBox::Inner::onTerminateSure() {
if (_terminateBox) {
_terminateBox->onClose();
_terminateBox = 0;
}
MTP::send(MTPaccount_ResetAuthorization(MTP_long(_terminating)), rpcDone(&Inner::terminateDone, _terminating), rpcFail(&Inner::terminateFail, _terminating));
TerminateButtons::iterator i = _terminateButtons.find(_terminating);
if (i != _terminateButtons.cend()) {
i.value()->clearState();
i.value()->hide();
}
}
void SessionsBox::Inner::onTerminateAll() {
if (_terminateBox) _terminateBox->deleteLater();
_terminateBox = new ConfirmBox(lang(lng_settings_reset_sure), lang(lng_settings_reset_button), st::attentionBoxButton);
connect(_terminateBox, SIGNAL(confirmed()), this, SLOT(onTerminateAllSure()));
connect(_terminateBox, SIGNAL(destroyed(QObject*)), this, SLOT(onNoTerminateBox(QObject*)));
Ui::showLayer(_terminateBox, KeepOtherLayers);
}
void SessionsBox::Inner::onTerminateAllSure() {
if (_terminateBox) {
_terminateBox->onClose();
_terminateBox = 0;
}
MTP::send(MTPauth_ResetAuthorizations(), rpcDone(&Inner::terminateAllDone), rpcFail(&Inner::terminateAllFail));
emit terminateAll();
}
void SessionsBox::Inner::onNoTerminateBox(QObject *obj) {
if (obj == _terminateBox) _terminateBox = 0;
}
void SessionsBox::Inner::terminateDone(uint64 hash, const MTPBool &result) {
for (int32 i = 0, l = _list->size(); i < l; ++i) {
if (_list->at(i).hash == hash) {
_list->removeAt(i);
break;
}
}
listUpdated();
emit oneTerminated();
}
bool SessionsBox::Inner::terminateFail(uint64 hash, const RPCError &error) {
if (MTP::isDefaultHandledError(error)) return false;
TerminateButtons::iterator i = _terminateButtons.find(hash);
if (i != _terminateButtons.end()) {
i.value()->show();
return true;
}
return false;
}
void SessionsBox::Inner::terminateAllDone(const MTPBool &result) {
emit allTerminated();
}
bool SessionsBox::Inner::terminateAllFail(const RPCError &error) {
if (MTP::isDefaultHandledError(error)) return false;
emit allTerminated();
return true;
}
void SessionsBox::Inner::resizeEvent(QResizeEvent *e) {
_terminateAll.moveToLeft(st::sessionPadding.left(), st::sessionCurrentPadding.top() + st::sessionHeight + st::sessionCurrentPadding.bottom());
}
void SessionsBox::Inner::listUpdated() {
if (_list->isEmpty()) {
_terminateAll.hide();
} else {
_terminateAll.show();
}
for (TerminateButtons::iterator i = _terminateButtons.begin(), e = _terminateButtons.end(); i != e; ++i) {
i.value()->move(0, -1);
}
for (int32 i = 0, l = _list->size(); i < l; ++i) {
TerminateButtons::iterator j = _terminateButtons.find(_list->at(i).hash);
if (j == _terminateButtons.cend()) {
j = _terminateButtons.insert(_list->at(i).hash, new IconedButton(this, st::sessionTerminate));
connect(j.value(), SIGNAL(clicked()), this, SLOT(onTerminate()));
}
j.value()->moveToRight(st::sessionTerminateSkip, st::sessionCurrentHeight + i * st::sessionHeight + st::sessionTerminateTop, width());
j.value()->show();
}
for (TerminateButtons::iterator i = _terminateButtons.begin(); i != _terminateButtons.cend();) {
if (i.value()->y() >= 0) {
++i;
} else {
delete i.value();
i = _terminateButtons.erase(i);
}
}
resize(width(), _list->isEmpty() ? (st::sessionCurrentHeight + st::noContactsHeight) : (st::sessionCurrentHeight + _list->size() * st::sessionHeight));
update();
}

View file

@ -25,20 +25,58 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
class ConfirmBox;
struct SessionData {
uint64 hash;
int32 activeTime;
int32 nameWidth, activeWidth, infoWidth, ipWidth;
QString name, active, info, ip;
};
typedef QList<SessionData> SessionsList;
class SessionsInner : public TWidget, public RPCSender {
class SessionsBox : public ScrollableBox, public RPCSender {
Q_OBJECT
public:
SessionsInner(SessionsList *list, SessionData *current);
SessionsBox();
public slots:
void onOneTerminated();
void onAllTerminated();
void onTerminateAll();
void onShortPollAuthorizations();
void onNewAuthorization();
protected:
void resizeEvent(QResizeEvent *e) override;
void paintEvent(QPaintEvent *e) override;
void showAll() override;
private:
struct Data {
uint64 hash;
int32 activeTime;
int32 nameWidth, activeWidth, infoWidth, ipWidth;
QString name, active, info, ip;
};
using List = QList<Data>;
void gotAuthorizations(const MTPaccount_Authorizations &result);
bool _loading;
Data _current;
List _list;
class Inner;
ChildWidget<Inner> _inner;
ScrollableBoxShadow _shadow;
BoxButton _done;
SingleTimer _shortPollTimer;
mtpRequestId _shortPollRequest;
};
// This class is hold in header because it requires Qt preprocessing.
class SessionsBox::Inner : public ScrolledWidget, public RPCSender {
Q_OBJECT
public:
Inner(QWidget *parent, SessionsBox::List *list, SessionsBox::Data *current);
void listUpdated();
@ -65,8 +103,8 @@ private:
void terminateAllDone(const MTPBool &res);
bool terminateAllFail(const RPCError &error);
SessionsList *_list;
SessionData *_current;
SessionsBox::List *_list;
SessionsBox::Data *_current;
typedef QMap<uint64, IconedButton*> TerminateButtons;
TerminateButtons _terminateButtons;
@ -76,39 +114,3 @@ private:
ConfirmBox *_terminateBox;
};
class SessionsBox : public ScrollableBox, public RPCSender {
Q_OBJECT
public:
SessionsBox();
public slots:
void onOneTerminated();
void onAllTerminated();
void onTerminateAll();
void onShortPollAuthorizations();
void onNewAuthorization();
protected:
void resizeEvent(QResizeEvent *e) override;
void paintEvent(QPaintEvent *e) override;
void showAll() override;
private:
void gotAuthorizations(const MTPaccount_Authorizations &result);
bool _loading;
SessionData _current;
SessionsList _list;
SessionsInner _inner;
ScrollableBoxShadow _shadow;
BoxButton _done;
SingleTimer _shortPollTimer;
mtpRequestId _shortPollRequest;
};

View file

@ -23,6 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "dialogs/dialogs_indexed_list.h"
#include "styles/style_boxes.h"
#include "styles/style_history.h"
#include "observer_peer.h"
#include "lang.h"
#include "mainwindow.h"
@ -32,36 +33,46 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "boxes/confirmbox.h"
#include "apiwrap.h"
#include "ui/toast/toast.h"
#include "ui/widgets/multi_select.h"
#include "history/history_media_types.h"
#include "boxes/contactsbox.h"
ShareBox::ShareBox(CopyCallback &&copyCallback, SubmitCallback &&submitCallback, FilterCallback &&filterCallback) : ItemListBox(st::boxScroll)
, _copyCallback(std_::move(copyCallback))
, _submitCallback(std_::move(submitCallback))
, _inner(this, std_::move(filterCallback))
, _filter(this, st::boxSearchField, lang(lng_participant_filter))
, _filterCancel(this, st::boxSearchCancel)
, _select(this, st::contactsMultiSelect, lang(lng_participant_filter))
, _copy(this, lang(lng_share_copy_link), st::defaultBoxButton)
, _share(this, lang(lng_share_confirm), st::defaultBoxButton)
, _cancel(this, lang(lng_cancel), st::cancelBoxButton)
, _topShadow(this)
, _bottomShadow(this) {
int topSkip = st::boxTitleHeight + _filter->height();
int bottomSkip = st::boxButtonPadding.top() + _share->height() + st::boxButtonPadding.bottom();
_select->resizeToWidth(st::boxWideWidth);
myEnsureResized(_select);
auto topSkip = getTopScrollSkip();
auto bottomSkip = st::boxButtonPadding.top() + _share->height() + st::boxButtonPadding.bottom();
init(_inner, bottomSkip, topSkip);
connect(_inner, SIGNAL(selectedChanged()), this, SLOT(onSelectedChanged()));
connect(_inner, SIGNAL(mustScrollTo(int,int)), this, SLOT(onMustScrollTo(int,int)));
connect(_copy, SIGNAL(clicked()), this, SLOT(onCopyLink()));
connect(_share, SIGNAL(clicked()), this, SLOT(onSubmit()));
connect(_cancel, SIGNAL(clicked()), this, SLOT(onClose()));
connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll()));
connect(_filter, SIGNAL(changed()), this, SLOT(onFilterUpdate()));
connect(_filter, SIGNAL(submitted(bool)), _inner, SLOT(onSelectActive()));
connect(_filterCancel, SIGNAL(clicked()), this, SLOT(onFilterCancel()));
connect(_inner, SIGNAL(filterCancel()), this, SLOT(onFilterCancel()));
_select->setQueryChangedCallback([this](const QString &query) { onFilterUpdate(query); });
_select->setItemRemovedCallback([this](uint64 itemId) {
if (auto peer = App::peerLoaded(itemId)) {
_inner->peerUnselected(peer);
onSelectedChanged();
update();
}
});
_select->setResizedCallback([this] { updateScrollSkips(); });
_select->setSubmittedCallback([this](bool) { _inner->onSelectActive(); });
connect(_inner, SIGNAL(searchByUsername()), this, SLOT(onNeedSearchByUsername()));
_filterCancel->setAttribute(Qt::WA_OpaquePaintEvent);
_inner->setPeerSelectedChangedCallback([this](PeerData *peer, bool checked) {
onPeerSelectedChanged(peer, checked);
});
_searchTimer.setSingleShot(true);
connect(&_searchTimer, SIGNAL(timeout()), this, SLOT(onSearchByUsername()));
@ -71,8 +82,29 @@ ShareBox::ShareBox(CopyCallback &&copyCallback, SubmitCallback &&submitCallback,
prepare();
}
int ShareBox::getTopScrollSkip() const {
auto result = st::boxTitleHeight;
if (!_select->isHidden()) {
result += _select->height();
}
return result;
}
void ShareBox::updateScrollSkips() {
auto oldScrollHeight = scrollArea()->height();
auto topSkip = getTopScrollSkip();
auto bottomSkip = st::boxButtonPadding.top() + _share->height() + st::boxButtonPadding.bottom();
setScrollSkips(bottomSkip, topSkip);
auto scrollHeightDelta = scrollArea()->height() - oldScrollHeight;
if (scrollHeightDelta) {
scrollArea()->scrollToY(scrollArea()->scrollTop() - scrollHeightDelta);
}
_topShadow->setGeometry(0, topSkip, width(), st::lineWidth);
}
bool ShareBox::onSearchByUsername(bool searchCache) {
auto query = _filter->getLastText().trimmed();
auto query = _select->getQuery();
if (query.isEmpty()) {
if (_peopleRequest) {
_peopleRequest = 0;
@ -140,7 +172,7 @@ bool ShareBox::peopleFailed(const RPCError &error, mtpRequestId requestId) {
}
void ShareBox::doSetInnerFocus() {
_filter->setFocus();
_select->setInnerFocus();
}
void ShareBox::paintEvent(QPaintEvent *e) {
@ -152,17 +184,21 @@ void ShareBox::paintEvent(QPaintEvent *e) {
void ShareBox::resizeEvent(QResizeEvent *e) {
ItemListBox::resizeEvent(e);
_filter->resize(width(), _filter->height());
_filter->moveToLeft(0, st::boxTitleHeight);
_filterCancel->moveToRight(0, st::boxTitleHeight);
_select->resizeToWidth(width());
_select->moveToLeft(0, st::boxTitleHeight);
updateScrollSkips();
_inner->resizeToWidth(width());
moveButtons();
_topShadow->setGeometry(0, st::boxTitleHeight + _filter->height(), width(), st::lineWidth);
_topShadow->setGeometry(0, getTopScrollSkip(), width(), st::lineWidth);
_bottomShadow->setGeometry(0, height() - st::boxButtonPadding.bottom() - _share->height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth);
}
void ShareBox::keyPressEvent(QKeyEvent *e) {
if (_filter->hasFocus()) {
auto focused = focusWidget();
if (_select == focused || _select->isAncestorOf(focusWidget())) {
if (e->key() == Qt::Key_Up) {
_inner->activateSkipColumn(-1);
} else if (e->key() == Qt::Key_Down) {
@ -192,13 +228,26 @@ void ShareBox::updateButtonsVisibility() {
_cancel->setVisible(hasSelected);
}
void ShareBox::onFilterCancel() {
_filter->setText(QString());
void ShareBox::onFilterUpdate(const QString &query) {
scrollArea()->scrollToY(0);
_inner->updateFilter(query);
}
void ShareBox::onFilterUpdate() {
_filterCancel->setVisible(!_filter->getLastText().isEmpty());
_inner->updateFilter(_filter->getLastText());
void ShareBox::addPeerToMultiSelect(PeerData *peer, bool skipAnimation) {
using AddItemWay = Ui::MultiSelect::AddItemWay;
auto addItemWay = skipAnimation ? AddItemWay::SkipAnimation : AddItemWay::Default;
_select->addItem(peer->id, peer->shortName(), st::windowActiveBg, PaintUserpicCallback(peer), addItemWay);
}
void ShareBox::onPeerSelectedChanged(PeerData *peer, bool checked) {
if (checked) {
addPeerToMultiSelect(peer);
_select->clearQuery();
} else {
_select->removeItem(peer->id);
}
onSelectedChanged();
update();
}
void ShareBox::onSubmit() {
@ -240,9 +289,7 @@ void ShareBox::onScroll() {
_inner->setVisibleTopBottom(scrollTop, scrollTop + scroll->height());
}
namespace internal {
ShareInner::ShareInner(QWidget *parent, ShareBox::FilterCallback &&filterCallback) : ScrolledWidget(parent)
ShareBox::Inner::Inner(QWidget *parent, ShareBox::FilterCallback &&filterCallback) : ScrolledWidget(parent)
, _filterCallback(std_::move(filterCallback))
, _chatsIndexed(std_::make_unique<Dialogs::IndexedList>(Dialogs::SortMode::Add)) {
_rowsTop = st::shareRowsTop;
@ -260,8 +307,6 @@ ShareInner::ShareInner(QWidget *parent, ShareBox::FilterCallback &&filterCallbac
_filter = qsl("a");
updateFilter();
prepareWideCheckIcons();
using UpdateFlag = Notify::PeerUpdate::Flag;
auto observeEvents = UpdateFlag::NameChanged | UpdateFlag::PhotoChanged;
subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(observeEvents, [this](const Notify::PeerUpdate &update) {
@ -270,19 +315,19 @@ ShareInner::ShareInner(QWidget *parent, ShareBox::FilterCallback &&filterCallbac
subscribe(FileDownload::ImageLoaded(), [this] { update(); });
}
void ShareInner::setVisibleTopBottom(int visibleTop, int visibleBottom) {
void ShareBox::Inner::setVisibleTopBottom(int visibleTop, int visibleBottom) {
loadProfilePhotos(visibleTop);
}
void ShareInner::activateSkipRow(int direction) {
void ShareBox::Inner::activateSkipRow(int direction) {
activateSkipColumn(direction * _columnCount);
}
int ShareInner::displayedChatsCount() const {
int ShareBox::Inner::displayedChatsCount() const {
return _filter.isEmpty() ? _chatsIndexed->size() : (_filtered.size() + d_byUsernameFiltered.size());
}
void ShareInner::activateSkipColumn(int direction) {
void ShareBox::Inner::activateSkipColumn(int direction) {
if (_active < 0) {
if (direction > 0) {
setActive(0);
@ -300,11 +345,11 @@ void ShareInner::activateSkipColumn(int direction) {
setActive(active);
}
void ShareInner::activateSkipPage(int pageHeight, int direction) {
void ShareBox::Inner::activateSkipPage(int pageHeight, int direction) {
activateSkipRow(direction * (pageHeight / _rowHeight));
}
void ShareInner::notifyPeerUpdated(const Notify::PeerUpdate &update) {
void ShareBox::Inner::notifyPeerUpdated(const Notify::PeerUpdate &update) {
if (update.flags & Notify::PeerUpdate::Flag::NameChanged) {
_chatsIndexed->peerNameChanged(update.peer, update.oldNames, update.oldNameFirstChars);
}
@ -312,7 +357,7 @@ void ShareInner::notifyPeerUpdated(const Notify::PeerUpdate &update) {
updateChat(update.peer);
}
void ShareInner::updateChat(PeerData *peer) {
void ShareBox::Inner::updateChat(PeerData *peer) {
auto i = _dataMap.find(peer);
if (i != _dataMap.cend()) {
updateChatName(i.value(), peer);
@ -320,11 +365,11 @@ void ShareInner::updateChat(PeerData *peer) {
}
}
void ShareInner::updateChatName(Chat *chat, PeerData *peer) {
void ShareBox::Inner::updateChatName(Chat *chat, PeerData *peer) {
chat->name.setText(st::shareNameFont, peer->name, _textNameOptions);
}
void ShareInner::repaintChatAtIndex(int index) {
void ShareBox::Inner::repaintChatAtIndex(int index) {
if (index < 0) return;
auto row = index / _columnCount;
@ -332,7 +377,7 @@ void ShareInner::repaintChatAtIndex(int index) {
update(rtlrect(_rowsLeft + qFloor(column * _rowWidthReal), row * _rowHeight, _rowWidth, _rowHeight, width()));
}
ShareInner::Chat *ShareInner::getChatAtIndex(int index) {
ShareBox::Inner::Chat *ShareBox::Inner::getChatAtIndex(int index) {
if (index < 0) return nullptr;
auto row = ([this, index]() -> Dialogs::Row* {
if (_filter.isEmpty()) return _chatsIndexed->rowAtY(index, 1);
@ -351,11 +396,11 @@ ShareInner::Chat *ShareInner::getChatAtIndex(int index) {
return nullptr;
}
void ShareInner::repaintChat(PeerData *peer) {
void ShareBox::Inner::repaintChat(PeerData *peer) {
repaintChatAtIndex(chatIndex(peer));
}
int ShareInner::chatIndex(PeerData *peer) const {
int ShareBox::Inner::chatIndex(PeerData *peer) const {
int index = 0;
if (_filter.isEmpty()) {
for_const (auto row, _chatsIndexed->all()) {
@ -381,7 +426,7 @@ int ShareInner::chatIndex(PeerData *peer) const {
return -1;
}
void ShareInner::loadProfilePhotos(int yFrom) {
void ShareBox::Inner::loadProfilePhotos(int yFrom) {
if (yFrom < 0) {
yFrom = 0;
}
@ -420,13 +465,14 @@ void ShareInner::loadProfilePhotos(int yFrom) {
}
}
ShareInner::Chat *ShareInner::getChat(Dialogs::Row *row) {
ShareBox::Inner::Chat *ShareBox::Inner::getChat(Dialogs::Row *row) {
auto data = static_cast<Chat*>(row->attached);
if (!data) {
auto peer = row->history()->peer;
auto i = _dataMap.constFind(peer);
if (i == _dataMap.cend()) {
_dataMap.insert(peer, data = new Chat(peer));
data = new Chat(peer, [this, peer] { repaintChat(peer); });
_dataMap.insert(peer, data);
updateChatName(data, peer);
} else {
data = i.value();
@ -436,12 +482,12 @@ ShareInner::Chat *ShareInner::getChat(Dialogs::Row *row) {
return data;
}
void ShareInner::setActive(int active) {
void ShareBox::Inner::setActive(int active) {
if (active != _active) {
auto changeNameFg = [this](int index, style::color from, style::color to) {
if (auto chat = getChatAtIndex(index)) {
chat->nameFg.start([this, chat] {
repaintChat(chat->peer);
chat->nameFg.start([this, peer = chat->peer] {
repaintChat(peer);
}, from->c, to->c, st::shareActivateDuration);
}
};
@ -453,71 +499,14 @@ void ShareInner::setActive(int active) {
emit mustScrollTo(y, y + _rowHeight);
}
void ShareInner::paintChat(Painter &p, Chat *chat, int index) {
void ShareBox::Inner::paintChat(Painter &p, uint64 ms, Chat *chat, int index) {
auto x = _rowsLeft + qFloor((index % _columnCount) * _rowWidthReal);
auto y = _rowsTop + (index / _columnCount) * _rowHeight;
auto selectionLevel = chat->selection.current(chat->selected ? 1. : 0.);
auto w = width();
auto photoLeft = (_rowWidth - (st::sharePhotoRadius * 2)) / 2;
auto outerWidth = width();
auto photoLeft = (_rowWidth - (st::sharePhotoCheckbox.imageRadius * 2)) / 2;
auto photoTop = st::sharePhotoTop;
if (chat->selection.animating()) {
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
auto userpicRadius = qRound(WideCacheScale * (st::sharePhotoRadius + (st::sharePhotoSmallRadius - st::sharePhotoRadius) * selectionLevel));
auto userpicShift = WideCacheScale * st::sharePhotoRadius - userpicRadius;
auto userpicLeft = x + photoLeft - (WideCacheScale - 1) * st::sharePhotoRadius + userpicShift;
auto userpicTop = y + photoTop - (WideCacheScale - 1) * st::sharePhotoRadius + userpicShift;
auto to = QRect(userpicLeft, userpicTop, userpicRadius * 2, userpicRadius * 2);
auto from = QRect(QPoint(0, 0), chat->wideUserpicCache.size());
p.drawPixmapLeft(to, w, chat->wideUserpicCache, from);
p.setRenderHint(QPainter::SmoothPixmapTransform, false);
} else {
if (!chat->wideUserpicCache.isNull()) {
chat->wideUserpicCache = QPixmap();
}
auto userpicRadius = chat->selected ? st::sharePhotoSmallRadius : st::sharePhotoRadius;
auto userpicShift = st::sharePhotoRadius - userpicRadius;
auto userpicLeft = x + photoLeft + userpicShift;
auto userpicTop = y + photoTop + userpicShift;
chat->peer->paintUserpicLeft(p, userpicRadius * 2, userpicLeft, userpicTop, w);
}
if (selectionLevel > 0) {
p.setRenderHint(QPainter::HighQualityAntialiasing, true);
p.setOpacity(snap(selectionLevel, 0., 1.));
p.setBrush(Qt::NoBrush);
QPen pen = st::shareSelectFg;
pen.setWidth(st::shareSelectWidth);
p.setPen(pen);
p.drawEllipse(myrtlrect(x + photoLeft, y + photoTop, st::sharePhotoRadius * 2, st::sharePhotoRadius * 2));
p.setOpacity(1.);
p.setRenderHint(QPainter::HighQualityAntialiasing, false);
}
removeFadeOutedIcons(chat);
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
for (auto &icon : chat->icons) {
auto fadeIn = icon.fadeIn.current(1.);
auto fadeOut = icon.fadeOut.current(1.);
auto iconRadius = qRound(WideCacheScale * (st::shareCheckSmallRadius + fadeOut * (st::shareCheckRadius - st::shareCheckSmallRadius)));
auto iconShift = WideCacheScale * st::shareCheckRadius - iconRadius;
auto iconLeft = x + photoLeft + 2 * st::sharePhotoRadius + st::shareSelectWidth - 2 * st::shareCheckRadius - (WideCacheScale - 1) * st::shareCheckRadius + iconShift;
auto iconTop = y + photoTop + 2 * st::sharePhotoRadius + st::shareSelectWidth - 2 * st::shareCheckRadius - (WideCacheScale - 1) * st::shareCheckRadius + iconShift;
auto to = QRect(iconLeft, iconTop, iconRadius * 2, iconRadius * 2);
auto from = QRect(QPoint(0, 0), _wideCheckIconCache.size());
auto opacity = fadeIn * fadeOut;
p.setOpacity(opacity);
if (fadeOut < 1.) {
p.drawPixmapLeft(to, w, icon.wideCheckCache, from);
} else {
auto divider = qRound((WideCacheScale - 2) * st::shareCheckRadius + fadeIn * 3 * st::shareCheckRadius);
p.drawPixmapLeft(QRect(iconLeft, iconTop, divider, iconRadius * 2), w, _wideCheckIconCache, QRect(0, 0, divider * cIntRetinaFactor(), _wideCheckIconCache.height()));
p.drawPixmapLeft(QRect(iconLeft + divider, iconTop, iconRadius * 2 - divider, iconRadius * 2), w, _wideCheckCache, QRect(divider * cIntRetinaFactor(), 0, _wideCheckCache.width() - divider * cIntRetinaFactor(), _wideCheckCache.height()));
}
}
p.setRenderHint(QPainter::SmoothPixmapTransform, false);
p.setOpacity(1.);
chat->checkbox.paint(p, ms, x + photoLeft, y + photoTop, outerWidth);
if (chat->nameFg.animating()) {
p.setPen(chat->nameFg.current());
@ -527,16 +516,20 @@ void ShareInner::paintChat(Painter &p, Chat *chat, int index) {
auto nameWidth = (_rowWidth - st::shareColumnSkip);
auto nameLeft = st::shareColumnSkip / 2;
auto nameTop = photoTop + st::sharePhotoRadius * 2 + st::shareNameTop;
chat->name.drawLeftElided(p, x + nameLeft, y + nameTop, nameWidth, w, 2, style::al_top, 0, -1, 0, true);
auto nameTop = photoTop + st::sharePhotoCheckbox.imageRadius * 2 + st::shareNameTop;
chat->name.drawLeftElided(p, x + nameLeft, y + nameTop, nameWidth, outerWidth, 2, style::al_top, 0, -1, 0, true);
}
ShareInner::Chat::Chat(PeerData *peer) : peer(peer), name(st::sharePhotoRadius * 2) {
ShareBox::Inner::Chat::Chat(PeerData *peer, base::lambda_wrap<void()> updateCallback)
: peer(peer)
, checkbox(st::sharePhotoCheckbox, std_::move(updateCallback), PaintUserpicCallback(peer))
, name(st::sharePhotoCheckbox.imageRadius * 2) {
}
void ShareInner::paintEvent(QPaintEvent *e) {
void ShareBox::Inner::paintEvent(QPaintEvent *e) {
Painter p(this);
auto ms = getms();
auto r = e->rect();
p.setClipRect(r);
p.fillRect(r, st::white);
@ -552,7 +545,7 @@ void ShareInner::paintEvent(QPaintEvent *e) {
if (indexFrom >= indexTo) {
break;
}
paintChat(p, getChat(*i), indexFrom);
paintChat(p, ms, getChat(*i), indexFrom);
++indexFrom;
}
} else {
@ -573,7 +566,7 @@ void ShareInner::paintEvent(QPaintEvent *e) {
if (indexFrom >= _filtered.size()) {
break;
}
paintChat(p, getChat(_filtered[indexFrom]), indexFrom);
paintChat(p, ms, getChat(_filtered[indexFrom]), indexFrom);
++indexFrom;
}
indexFrom -= filteredSize;
@ -585,7 +578,7 @@ void ShareInner::paintEvent(QPaintEvent *e) {
if (indexFrom >= d_byUsernameFiltered.size()) {
break;
}
paintChat(p, d_byUsernameFiltered[indexFrom], filteredSize + indexFrom);
paintChat(p, ms, d_byUsernameFiltered[indexFrom], filteredSize + indexFrom);
++indexFrom;
}
}
@ -593,27 +586,27 @@ void ShareInner::paintEvent(QPaintEvent *e) {
}
}
void ShareInner::enterEvent(QEvent *e) {
void ShareBox::Inner::enterEvent(QEvent *e) {
setMouseTracking(true);
}
void ShareInner::leaveEvent(QEvent *e) {
void ShareBox::Inner::leaveEvent(QEvent *e) {
setMouseTracking(false);
}
void ShareInner::mouseMoveEvent(QMouseEvent *e) {
void ShareBox::Inner::mouseMoveEvent(QMouseEvent *e) {
updateUpon(e->pos());
setCursor((_upon >= 0) ? style::cur_pointer : style::cur_default);
}
void ShareInner::updateUpon(const QPoint &pos) {
void ShareBox::Inner::updateUpon(const QPoint &pos) {
auto x = pos.x(), y = pos.y();
auto row = (y - _rowsTop) / _rowHeight;
auto column = qFloor((x - _rowsLeft) / _rowWidthReal);
auto left = _rowsLeft + qFloor(column * _rowWidthReal) + st::shareColumnSkip / 2;
auto top = _rowsTop + row * _rowHeight + st::sharePhotoTop;
auto xupon = (x >= left) && (x < left + (_rowWidth - st::shareColumnSkip));
auto yupon = (y >= top) && (y < top + st::sharePhotoRadius * 2 + st::shareNameTop + st::shareNameFont->height * 2);
auto yupon = (y >= top) && (y < top + st::sharePhotoCheckbox.imageRadius * 2 + st::shareNameTop + st::shareNameFont->height * 2);
auto upon = (xupon && yupon) ? (row * _columnCount + column) : -1;
if (upon >= displayedChatsCount()) {
upon = -1;
@ -621,41 +614,26 @@ void ShareInner::updateUpon(const QPoint &pos) {
_upon = upon;
}
void ShareInner::mousePressEvent(QMouseEvent *e) {
void ShareBox::Inner::mousePressEvent(QMouseEvent *e) {
if (e->button() == Qt::LeftButton) {
updateUpon(e->pos());
changeCheckState(getChatAtIndex(_upon));
}
}
void ShareInner::onSelectActive() {
void ShareBox::Inner::onSelectActive() {
changeCheckState(getChatAtIndex(_active > 0 ? _active : 0));
}
void ShareInner::resizeEvent(QResizeEvent *e) {
_columnSkip = (width() - _columnCount * st::sharePhotoRadius * 2) / float64(_columnCount + 1);
_rowWidthReal = st::sharePhotoRadius * 2 + _columnSkip;
void ShareBox::Inner::resizeEvent(QResizeEvent *e) {
_columnSkip = (width() - _columnCount * st::sharePhotoCheckbox.imageRadius * 2) / float64(_columnCount + 1);
_rowWidthReal = st::sharePhotoCheckbox.imageRadius * 2 + _columnSkip;
_rowsLeft = qFloor(_columnSkip / 2);
_rowWidth = qFloor(_rowWidthReal);
update();
}
struct AnimBumpy {
AnimBumpy(float64 bump) : bump(bump)
, dt0(bump - sqrt(bump * (bump - 1.)))
, k(1 / (2 * dt0 - 1)) {
}
float64 bump;
float64 dt0;
float64 k;
};
float64 anim_bumpy(const float64 &delta, const float64 &dt) {
static AnimBumpy data = { 1.25 };
return delta * (data.bump - data.k * (dt - data.dt0) * (dt - data.dt0));
}
void ShareInner::changeCheckState(Chat *chat) {
void ShareBox::Inner::changeCheckState(Chat *chat) {
if (!chat) return;
if (!_filter.isEmpty()) {
@ -664,115 +642,44 @@ void ShareInner::changeCheckState(Chat *chat) {
row = _chatsIndexed->addToEnd(App::history(chat->peer)).value(0);
}
chat = getChat(row);
if (!chat->selected) {
if (!chat->checkbox.checked()) {
_chatsIndexed->moveToTop(chat->peer);
}
emit filterCancel();
}
chat->selected = !chat->selected;
if (chat->selected) {
changePeerCheckState(chat, !chat->checkbox.checked());
}
void ShareBox::Inner::peerUnselected(PeerData *peer) {
// If data is nullptr we simply won't do anything.
auto chat = _dataMap.value(peer, nullptr);
changePeerCheckState(chat, false, ChangeStateWay::SkipCallback);
}
void ShareBox::Inner::setPeerSelectedChangedCallback(base::lambda_unique<void(PeerData *peer, bool selected)> callback) {
_peerSelectedChangedCallback = std_::move(callback);
}
void ShareBox::Inner::changePeerCheckState(Chat *chat, bool checked, ChangeStateWay useCallback) {
if (chat) {
chat->checkbox.setChecked(checked);
}
if (checked) {
_selected.insert(chat->peer);
chat->icons.push_back(Chat::Icon());
chat->icons.back().fadeIn.start([this, chat] {
repaintChat(chat->peer);
}, 0, 1, st::shareSelectDuration);
setActive(chatIndex(chat->peer));
} else {
_selected.remove(chat->peer);
prepareWideCheckIconCache(&chat->icons.back());
chat->icons.back().fadeOut.start([this, chat] {
repaintChat(chat->peer);
removeFadeOutedIcons(chat); // this call can destroy current lambda
}, 1, 0, st::shareSelectDuration);
}
prepareWideUserpicCache(chat);
chat->selection.start([this, chat] {
repaintChat(chat->peer);
}, chat->selected ? 0 : 1, chat->selected ? 1 : 0, st::shareSelectDuration, anim_bumpy);
if (chat->selected) {
setActive(chatIndex(chat->peer));
}
emit selectedChanged();
}
void ShareInner::removeFadeOutedIcons(Chat *chat) {
while (!chat->icons.empty() && !chat->icons.front().fadeIn.animating() && !chat->icons.front().fadeOut.animating()) {
if (chat->icons.size() > 1 || !chat->selected) {
chat->icons.erase(chat->icons.begin());
} else {
break;
}
if (useCallback != ChangeStateWay::SkipCallback && _peerSelectedChangedCallback) {
_peerSelectedChangedCallback(chat->peer, checked);
}
}
void ShareInner::prepareWideUserpicCache(Chat *chat) {
if (chat->wideUserpicCache.isNull()) {
auto size = st::sharePhotoRadius * 2;
auto wideSize = size * WideCacheScale;
QImage cache(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
cache.setDevicePixelRatio(cRetinaFactor());
{
Painter p(&cache);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.fillRect(0, 0, wideSize, wideSize, Qt::transparent);
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
chat->peer->paintUserpic(p, size, (wideSize - size) / 2, (wideSize - size) / 2);
}
chat->wideUserpicCache = App::pixmapFromImageInPlace(std_::move(cache));
chat->wideUserpicCache.setDevicePixelRatio(cRetinaFactor());
}
}
void ShareInner::prepareWideCheckIconCache(Chat::Icon *icon) {
QImage wideCache(_wideCheckCache.width(), _wideCheckCache.height(), QImage::Format_ARGB32_Premultiplied);
wideCache.setDevicePixelRatio(cRetinaFactor());
{
Painter p(&wideCache);
p.setCompositionMode(QPainter::CompositionMode_Source);
auto iconRadius = WideCacheScale * st::shareCheckRadius;
auto divider = qRound((WideCacheScale - 2) * st::shareCheckRadius + icon->fadeIn.current(1.) * 3 * st::shareCheckRadius);
p.drawPixmapLeft(QRect(0, 0, divider, iconRadius * 2), width(), _wideCheckIconCache, QRect(0, 0, divider * cIntRetinaFactor(), _wideCheckIconCache.height()));
p.drawPixmapLeft(QRect(divider, 0, iconRadius * 2 - divider, iconRadius * 2), width(), _wideCheckCache, QRect(divider * cIntRetinaFactor(), 0, _wideCheckCache.width() - divider * cIntRetinaFactor(), _wideCheckCache.height()));
}
icon->wideCheckCache = App::pixmapFromImageInPlace(std_::move(wideCache));
icon->wideCheckCache.setDevicePixelRatio(cRetinaFactor());
}
void ShareInner::prepareWideCheckIcons() {
auto size = st::shareCheckRadius * 2;
auto wideSize = size * WideCacheScale;
QImage cache(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
cache.setDevicePixelRatio(cRetinaFactor());
{
Painter p(&cache);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.fillRect(0, 0, wideSize, wideSize, Qt::transparent);
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
p.setRenderHint(QPainter::HighQualityAntialiasing, true);
auto pen = st::shareCheckBorder->p;
pen.setWidth(st::shareSelectWidth);
p.setPen(pen);
p.setBrush(st::shareCheckBg);
auto ellipse = QRect((wideSize - size) / 2, (wideSize - size) / 2, size, size);
p.drawEllipse(ellipse);
}
QImage cacheIcon = cache;
{
Painter p(&cacheIcon);
auto ellipse = QRect((wideSize - size) / 2, (wideSize - size) / 2, size, size);
st::shareCheckIcon.paint(p, ellipse.topLeft(), wideSize);
}
_wideCheckCache = App::pixmapFromImageInPlace(std_::move(cache));
_wideCheckCache.setDevicePixelRatio(cRetinaFactor());
_wideCheckIconCache = App::pixmapFromImageInPlace(std_::move(cacheIcon));
_wideCheckIconCache.setDevicePixelRatio(cRetinaFactor());
}
bool ShareInner::hasSelected() const {
bool ShareBox::Inner::hasSelected() const {
return _selected.size();
}
void ShareInner::updateFilter(QString filter) {
void ShareBox::Inner::updateFilter(QString filter) {
_lastQuery = filter.toLower().trimmed();
filter = textSearchKey(filter);
@ -851,7 +758,7 @@ void ShareInner::updateFilter(QString filter) {
}
}
void ShareInner::peopleReceived(const QString &query, const QVector<MTPPeer> &people) {
void ShareBox::Inner::peopleReceived(const QString &query, const QVector<MTPPeer> &people) {
_lastQuery = query.toLower().trimmed();
if (_lastQuery.at(0) == '@') _lastQuery = _lastQuery.mid(1);
int32 already = _byUsernameFiltered.size();
@ -867,7 +774,7 @@ void ShareInner::peopleReceived(const QString &query, const QVector<MTPPeer> &pe
auto *peer = App::peer(peerId);
if (!peer || !_filterCallback(peer)) continue;
auto chat = new Chat(peer);
auto chat = new Chat(peer, [this, peer] { repaintChat(peer); });
updateChatName(chat, peer);
if (auto row = _chatsIndexed->getRow(peer->id)) {
continue;
@ -881,7 +788,7 @@ void ShareInner::peopleReceived(const QString &query, const QVector<MTPPeer> &pe
refresh();
}
void ShareInner::refresh() {
void ShareBox::Inner::refresh() {
auto count = displayedChatsCount();
if (count) {
auto rows = (count / _columnCount) + (count % _columnCount ? 1 : 0);
@ -892,25 +799,23 @@ void ShareInner::refresh() {
update();
}
ShareInner::~ShareInner() {
ShareBox::Inner::~Inner() {
for_const (auto chat, _dataMap) {
delete chat;
}
}
QVector<PeerData*> ShareInner::selected() const {
QVector<PeerData*> ShareBox::Inner::selected() const {
QVector<PeerData*> result;
result.reserve(_dataMap.size());
for_const (auto chat, _dataMap) {
if (chat->selected) {
if (chat->checkbox.checked()) {
result.push_back(chat->peer);
}
}
return result;
}
} // namespace internal
QString appendShareGameScoreUrl(const QString &url, const FullMsgId &fullId) {
auto shareHashData = QByteArray(0x10, Qt::Uninitialized);
auto shareHashDataInts = reinterpret_cast<int32*>(shareHashData.data());

View file

@ -24,20 +24,21 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "core/lambda_wrap.h"
#include "core/observer.h"
#include "core/vector_of_moveable.h"
#include "ui/effects/round_image_checkbox.h"
namespace Dialogs {
class Row;
class IndexedList;
} // namespace Dialogs
namespace internal {
class ShareInner;
} // namespace internal
namespace Notify {
struct PeerUpdate;
} // namespace Notify
namespace Ui {
class MultiSelect;
} // namespace Ui
QString appendShareGameScoreUrl(const QString &url, const FullMsgId &fullId);
void shareGameScoreByHash(const QString &hash);
@ -51,8 +52,6 @@ public:
ShareBox(CopyCallback &&copyCallback, SubmitCallback &&submitCallback, FilterCallback &&filterCallback);
private slots:
void onFilterUpdate();
void onFilterCancel();
void onScroll();
bool onSearchByUsername(bool searchCache = false);
@ -60,7 +59,6 @@ private slots:
void onSubmit();
void onCopyLink();
void onSelectedChanged();
void onMustScrollTo(int top, int bottom);
@ -72,8 +70,15 @@ protected:
void doSetInnerFocus() override;
private:
void onFilterUpdate(const QString &query);
void onSelectedChanged();
void moveButtons();
void updateButtonsVisibility();
int getTopScrollSkip() const;
void updateScrollSkips();
void addPeerToMultiSelect(PeerData *peer, bool skipAnimation = false);
void onPeerSelectedChanged(PeerData *peer, bool checked);
void peopleReceived(const MTPcontacts_Found &result, mtpRequestId requestId);
bool peopleFailed(const RPCError &error, mtpRequestId requestId);
@ -81,9 +86,9 @@ private:
CopyCallback _copyCallback;
SubmitCallback _submitCallback;
ChildWidget<internal::ShareInner> _inner;
ChildWidget<InputField> _filter;
ChildWidget<IconedButton> _filterCancel;
class Inner;
ChildWidget<Inner> _inner;
ChildWidget<Ui::MultiSelect> _select;
ChildWidget<BoxButton> _copy;
ChildWidget<BoxButton> _share;
@ -107,13 +112,15 @@ private:
};
namespace internal {
class ShareInner : public ScrolledWidget, public RPCSender, private base::Subscriber {
// This class is hold in header because it requires Qt preprocessing.
class ShareBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber {
Q_OBJECT
public:
ShareInner(QWidget *parent, ShareBox::FilterCallback &&filterCallback);
Inner(QWidget *parent, ShareBox::FilterCallback &&filterCallback);
void setPeerSelectedChangedCallback(base::lambda_unique<void(PeerData *peer, bool selected)> callback);
void peerUnselected(PeerData *peer);
QVector<PeerData*> selected() const;
bool hasSelected() const;
@ -126,16 +133,14 @@ public:
void setVisibleTopBottom(int visibleTop, int visibleBottom) override;
void updateFilter(QString filter = QString());
~ShareInner();
~Inner();
public slots:
void onSelectActive();
signals:
void mustScrollTo(int ymin, int ymax);
void filterCancel();
void searchByUsername();
void selectedChanged();
protected:
void paintEvent(QPaintEvent *e) override;
@ -151,36 +156,29 @@ private:
int displayedChatsCount() const;
static constexpr int WideCacheScale = 4;
struct Chat {
Chat(PeerData *peer);
Chat(PeerData *peer, base::lambda_wrap<void()> updateCallback);
PeerData *peer;
Ui::RoundImageCheckbox checkbox;
Text name;
bool selected = false;
QPixmap wideUserpicCache;
ColorAnimation nameFg;
FloatAnimation selection;
struct Icon {
FloatAnimation fadeIn;
FloatAnimation fadeOut;
QPixmap wideCheckCache;
};
std_::vector_of_moveable<Icon> icons;
};
void paintChat(Painter &p, Chat *chat, int index);
void paintChat(Painter &p, uint64 ms, Chat *chat, int index);
void updateChat(PeerData *peer);
void updateChatName(Chat *chat, PeerData *peer);
void repaintChat(PeerData *peer);
void removeFadeOutedIcons(Chat *chat);
void prepareWideUserpicCache(Chat *chat);
void prepareWideCheckIconCache(Chat::Icon *icon);
void prepareWideCheckIcons();
int chatIndex(PeerData *peer) const;
void repaintChatAtIndex(int index);
Chat *getChatAtIndex(int index);
void loadProfilePhotos(int yFrom);
void changeCheckState(Chat *chat);
enum class ChangeStateWay {
Default,
SkipCallback,
};
void changePeerCheckState(Chat *chat, bool checked, ChangeStateWay useCallback = ChangeStateWay::Default);
Chat *getChat(Dialogs::Row *row);
void setActive(int active);
@ -204,13 +202,13 @@ private:
using FilteredDialogs = QVector<Dialogs::Row*>;
FilteredDialogs _filtered;
QPixmap _wideCheckCache, _wideCheckIconCache;
using DataMap = QMap<PeerData*, Chat*>;
DataMap _dataMap;
using SelectedChats = OrderedSet<PeerData*>;
SelectedChats _selected;
base::lambda_unique<void(PeerData *peer, bool selected)> _peerSelectedChangedCallback;
ChatData *data(Dialogs::Row *row);
bool _searching = false;
@ -221,5 +219,3 @@ private:
ByUsernameDatas d_byUsernameFiltered;
};
} // namespace internal

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,239 @@
/*
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-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "abstractbox.h"
class ConfirmBox;
namespace Ui {
class PlainShadow;
} // namespace Ui
class StickersBox : public ItemListBox, public RPCSender {
Q_OBJECT
public:
enum class Section {
Installed,
Featured,
Archived,
ArchivedPart,
};
StickersBox(Section section = Section::Installed);
StickersBox(const Stickers::Order &archivedIds);
~StickersBox();
public slots:
void onStickersUpdated();
void onCheckDraggingScroll(int localY);
void onNoDraggingScroll();
void onScrollTimer();
void onSave();
private slots:
void onScroll();
protected:
void resizeEvent(QResizeEvent *e) override;
void paintEvent(QPaintEvent *e) override;
void closePressed() override;
void showAll() override;
private:
void setup();
int32 countHeight() const;
void rebuildList();
void disenableDone(const MTPmessages_StickerSetInstallResult &result, mtpRequestId req);
bool disenableFail(const RPCError &error, mtpRequestId req);
void reorderDone(const MTPBool &result);
bool reorderFail(const RPCError &result);
void saveOrder();
void updateVisibleTopBottom();
void checkLoadMoreArchived();
void getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedStickers &result);
Section _section;
class Inner;
ChildWidget<Inner> _inner;
ChildWidget<BoxButton> _save = { nullptr };
ChildWidget<BoxButton> _cancel = { nullptr };
OrderedSet<mtpRequestId> _disenableRequests;
mtpRequestId _reorderRequest = 0;
ChildWidget<Ui::PlainShadow> _topShadow = { nullptr };
ChildWidget<ScrollableBoxShadow> _bottomShadow = { nullptr };
QTimer _scrollTimer;
int32 _scrollDelta = 0;
int _aboutWidth = 0;
Text _about;
int _aboutHeight = 0;
mtpRequestId _archivedRequestId = 0;
bool _allArchivedLoaded = false;
};
int32 stickerPacksCount(bool includeDisabledOfficial = false);
// This class is hold in header because it requires Qt preprocessing.
class StickersBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber {
Q_OBJECT
public:
using Section = StickersBox::Section;
Inner(QWidget *parent, Section section);
Inner(QWidget *parent, const Stickers::Order &archivedIds);
void rebuild();
void updateSize();
void updateRows(); // refresh only pack cover stickers
bool appendSet(const Stickers::Set &set);
bool savingStart() {
if (_saving) return false;
_saving = true;
return true;
}
Stickers::Order getOrder() const;
Stickers::Order getDisabledSets() const;
void setVisibleScrollbar(int32 width);
void setVisibleTopBottom(int visibleTop, int visibleBottom) override;
~Inner();
protected:
void paintEvent(QPaintEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void leaveEvent(QEvent *e) override;
signals:
void checkDraggingScroll(int localY);
void noDraggingScroll();
public slots:
void onUpdateSelected();
void onClearRecent();
void onClearBoxDestroyed(QObject *box);
private slots:
void onImageLoaded();
private:
void setup();
void paintButton(Painter &p, int y, bool selected, const QString &text, int badgeCounter) const;
void step_shifting(uint64 ms, bool timer);
void paintRow(Painter &p, int32 index);
void clear();
void setActionSel(int32 actionSel);
float64 aboveShadowOpacity() const;
void readVisibleSets();
void installSet(uint64 setId);
void installDone(const MTPmessages_StickerSetInstallResult &result);
bool installFail(uint64 setId, const RPCError &error);
Section _section;
Stickers::Order _archivedIds;
int32 _rowHeight;
struct StickerSetRow {
StickerSetRow(uint64 id, DocumentData *sticker, int32 count, const QString &title, int titleWidth, bool installed, bool official, bool unread, bool disabled, bool recent, int32 pixw, int32 pixh) : id(id)
, sticker(sticker)
, count(count)
, title(title)
, titleWidth(titleWidth)
, installed(installed)
, official(official)
, unread(unread)
, disabled(disabled)
, recent(recent)
, pixw(pixw)
, pixh(pixh)
, yadd(0, 0) {
}
uint64 id;
DocumentData *sticker;
int32 count;
QString title;
int titleWidth;
bool installed, official, unread, disabled, recent;
int32 pixw, pixh;
anim::ivalue yadd;
};
using StickerSetRows = QList<StickerSetRow*>;
void rebuildAppendSet(const Stickers::Set &set, int maxNameWidth);
void fillSetCover(const Stickers::Set &set, DocumentData **outSticker, int *outWidth, int *outHeight) const;
int fillSetCount(const Stickers::Set &set) const;
QString fillSetTitle(const Stickers::Set &set, int maxNameWidth, int *outTitleWidth) const;
void fillSetFlags(const Stickers::Set &set, bool *outRecent, bool *outInstalled, bool *outOfficial, bool *outUnread, bool *outDisabled);
int countMaxNameWidth() const;
StickerSetRows _rows;
QList<uint64> _animStartTimes;
uint64 _aboveShadowFadeStart = 0;
anim::fvalue _aboveShadowFadeOpacity = { 0., 0. };
Animation _a_shifting;
int _visibleTop = 0;
int _visibleBottom = 0;
int _itemsTop = 0;
bool _saving = false;
int _actionSel = -1;
int _actionDown = -1;
int _clearWidth, _removeWidth, _returnWidth, _restoreWidth;
ConfirmBox *_clearBox = nullptr;
int _buttonHeight = 0;
bool _hasFeaturedButton = false;
bool _hasArchivedButton = false;
QPoint _mouse;
int _selected = -3; // -2 - featured stickers button, -1 - archived stickers button
int _pressed = -2;
QPoint _dragStart;
int _started = -1;
int _dragging = -1;
int _above = -1;
Ui::RectShadow _aboveShadow;
int32 _scrollbar = 0;
};

File diff suppressed because it is too large Load diff

View file

@ -24,15 +24,52 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "core/vector_of_moveable.h"
class ConfirmBox;
namespace Ui {
class PlainShadow;
} // namespace Ui
class StickerSetInner : public ScrolledWidget, public RPCSender, private base::Subscriber {
class StickerSetBox : public ScrollableBox, public RPCSender {
Q_OBJECT
public:
StickerSetInner(const MTPInputStickerSet &set);
StickerSetBox(const MTPInputStickerSet &set);
public slots:
void onStickersUpdated();
void onAddStickers();
void onShareStickers();
void onUpdateButtons();
void onScroll();
private slots:
void onInstalled(uint64 id);
signals:
void installed(uint64 id);
protected:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void showAll() override;
private:
class Inner;
ChildWidget<Inner> _inner;
ScrollableBoxShadow _shadow;
BoxButton _add, _share, _cancel, _done;
QString _title;
};
// This class is hold in header because it requires Qt preprocessing.
class StickerSetBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber {
Q_OBJECT
public:
Inner(QWidget *parent, const MTPInputStickerSet &set);
bool loaded() const;
int32 notInstalled() const;
@ -43,7 +80,7 @@ public:
void setVisibleTopBottom(int visibleTop, int visibleBottom) override;
void install();
~StickerSetInner();
~Inner();
protected:
void mousePressEvent(QMouseEvent *e) override;
@ -96,253 +133,3 @@ private:
int _previewShown = -1;
};
class StickerSetBox : public ScrollableBox, public RPCSender {
Q_OBJECT
public:
StickerSetBox(const MTPInputStickerSet &set);
public slots:
void onStickersUpdated();
void onAddStickers();
void onShareStickers();
void onUpdateButtons();
void onScroll();
private slots:
void onInstalled(uint64 id);
signals:
void installed(uint64 id);
protected:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void showAll() override;
private:
StickerSetInner _inner;
ScrollableBoxShadow _shadow;
BoxButton _add, _share, _cancel, _done;
QString _title;
};
namespace internal {
class StickersInner;
} // namespace internal
class StickersBox : public ItemListBox, public RPCSender {
Q_OBJECT
public:
enum class Section {
Installed,
Featured,
Archived,
ArchivedPart,
};
StickersBox(Section section = Section::Installed);
StickersBox(const Stickers::Order &archivedIds);
~StickersBox();
public slots:
void onStickersUpdated();
void onCheckDraggingScroll(int localY);
void onNoDraggingScroll();
void onScrollTimer();
void onSave();
private slots:
void onScroll();
protected:
void resizeEvent(QResizeEvent *e) override;
void paintEvent(QPaintEvent *e) override;
void closePressed() override;
void showAll() override;
private:
void setup();
int32 countHeight() const;
void rebuildList();
void disenableDone(const MTPmessages_StickerSetInstallResult &result, mtpRequestId req);
bool disenableFail(const RPCError &error, mtpRequestId req);
void reorderDone(const MTPBool &result);
bool reorderFail(const RPCError &result);
void saveOrder();
void updateVisibleTopBottom();
void checkLoadMoreArchived();
void getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedStickers &result);
Section _section;
ChildWidget<internal::StickersInner> _inner;
ChildWidget<BoxButton> _save = { nullptr };
ChildWidget<BoxButton> _cancel = { nullptr };
OrderedSet<mtpRequestId> _disenableRequests;
mtpRequestId _reorderRequest = 0;
ChildWidget<Ui::PlainShadow> _topShadow = { nullptr };
ChildWidget<ScrollableBoxShadow> _bottomShadow = { nullptr };
QTimer _scrollTimer;
int32 _scrollDelta = 0;
int _aboutWidth = 0;
Text _about;
int _aboutHeight = 0;
mtpRequestId _archivedRequestId = 0;
bool _allArchivedLoaded = false;
};
int32 stickerPacksCount(bool includeDisabledOfficial = false);
namespace internal {
class StickersInner : public ScrolledWidget, public RPCSender, private base::Subscriber {
Q_OBJECT
public:
using Section = StickersBox::Section;
StickersInner(Section section);
StickersInner(const Stickers::Order &archivedIds);
void rebuild();
void updateSize();
void updateRows(); // refresh only pack cover stickers
bool appendSet(const Stickers::Set &set);
bool savingStart() {
if (_saving) return false;
_saving = true;
return true;
}
Stickers::Order getOrder() const;
Stickers::Order getDisabledSets() const;
void setVisibleScrollbar(int32 width);
void setVisibleTopBottom(int visibleTop, int visibleBottom) override;
~StickersInner();
protected:
void paintEvent(QPaintEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void leaveEvent(QEvent *e) override;
signals:
void checkDraggingScroll(int localY);
void noDraggingScroll();
public slots:
void onUpdateSelected();
void onClearRecent();
void onClearBoxDestroyed(QObject *box);
private slots:
void onImageLoaded();
private:
void setup();
void paintButton(Painter &p, int y, bool selected, const QString &text, int badgeCounter) const;
void step_shifting(uint64 ms, bool timer);
void paintRow(Painter &p, int32 index);
void clear();
void setActionSel(int32 actionSel);
float64 aboveShadowOpacity() const;
void readVisibleSets();
void installSet(uint64 setId);
void installDone(const MTPmessages_StickerSetInstallResult &result);
bool installFail(uint64 setId, const RPCError &error);
Section _section;
Stickers::Order _archivedIds;
int32 _rowHeight;
struct StickerSetRow {
StickerSetRow(uint64 id, DocumentData *sticker, int32 count, const QString &title, int titleWidth, bool installed, bool official, bool unread, bool disabled, bool recent, int32 pixw, int32 pixh) : id(id)
, sticker(sticker)
, count(count)
, title(title)
, titleWidth(titleWidth)
, installed(installed)
, official(official)
, unread(unread)
, disabled(disabled)
, recent(recent)
, pixw(pixw)
, pixh(pixh)
, yadd(0, 0) {
}
uint64 id;
DocumentData *sticker;
int32 count;
QString title;
int titleWidth;
bool installed, official, unread, disabled, recent;
int32 pixw, pixh;
anim::ivalue yadd;
};
using StickerSetRows = QList<StickerSetRow*>;
void rebuildAppendSet(const Stickers::Set &set, int maxNameWidth);
void fillSetCover(const Stickers::Set &set, DocumentData **outSticker, int *outWidth, int *outHeight) const;
int fillSetCount(const Stickers::Set &set) const;
QString fillSetTitle(const Stickers::Set &set, int maxNameWidth, int *outTitleWidth) const;
void fillSetFlags(const Stickers::Set &set, bool *outRecent, bool *outInstalled, bool *outOfficial, bool *outUnread, bool *outDisabled);
int countMaxNameWidth() const;
StickerSetRows _rows;
QList<uint64> _animStartTimes;
uint64 _aboveShadowFadeStart = 0;
anim::fvalue _aboveShadowFadeOpacity = { 0., 0. };
Animation _a_shifting;
int _visibleTop = 0;
int _visibleBottom = 0;
int _itemsTop = 0;
bool _saving = false;
int _actionSel = -1;
int _actionDown = -1;
int _clearWidth, _removeWidth, _returnWidth, _restoreWidth;
ConfirmBox *_clearBox = nullptr;
int _buttonHeight = 0;
bool _hasFeaturedButton = false;
bool _hasArchivedButton = false;
QPoint _mouse;
int _selected = -3; // -2 - featured stickers button, -1 - archived stickers button
int _pressed = -2;
QPoint _dragStart;
int _started = -1;
int _dragging = -1;
int _above = -1;
Ui::RectShadow _aboveShadow;
int32 _scrollbar = 0;
};
} // namespace internal

View file

@ -243,7 +243,7 @@ structure::Variable ParsedFile::readVariable(const QString &name) {
structure::Variable result = { composeFullName(name) };
if (auto value = readValue()) {
result.value = value;
if (value.type().tag != structure::TypeTag::Struct) {
if (value.type().tag != structure::TypeTag::Struct || !value.copyOf().empty()) {
assertNextToken(BasicType::Semicolon);
}
}

View file

@ -60,7 +60,7 @@ struct lambda_wrap_helper_base {
protected:
static void bad_construct_copy(void *lambda, const void *source) {
throw std::exception();
t_assert(!"base::lambda bad_construct_copy() called!");
}
};
@ -72,7 +72,8 @@ struct lambda_wrap_empty : public lambda_wrap_helper_base<Return, Args...> {
static void construct_move_other_method(void *lambda, void *source) {
}
static Return call_method(const void *lambda, Args... args) {
throw std::exception();
t_assert(!"base::lambda empty call_method() called!");
return Return();
}
static void destruct_method(const void *lambda) {
}
@ -359,6 +360,10 @@ public:
}
}
lambda_wrap clone() const {
return *this;
}
template <typename Lambda, typename = IsOther<Lambda>>
lambda_wrap(const Lambda &other) : Parent(&internal::lambda_wrap_helper_copy<Lambda, Return, Args...>::instance, typename Parent::Private()) {
internal::lambda_wrap_helper_copy<Lambda, Return, Args...>::construct_copy_lambda_method(this->storage_, &other);

View file

@ -35,6 +35,9 @@ historyToDownPaddingTop: 10px;
historyToDownBadgeFont: semiboldFont;
historyToDownBadgeSize: 22px;
historyEmptyDog: icon {{ "history_empty_dog", #ffffff }};
historyEmptySize: 128px;
membersInnerScroll: flatScroll(solidScroll) {
deltat: 3px;
deltab: 3px;

View file

@ -334,6 +334,16 @@ QVector<int> ServiceMessagePainter::countLineWidths(const Text &text, const QRec
return lineWidths;
}
void paintEmpty(Painter &p, int width, int height) {
auto position = QPoint((width - st::historyEmptySize) / 2, ((height - st::historyEmptySize) * 4) / 9);
p.setPen(Qt::NoPen);
p.setBrush(App::msgServiceBg());
p.setRenderHint(QPainter::HighQualityAntialiasing);
p.drawEllipse(rtlrect(position.x(), position.y(), st::historyEmptySize, st::historyEmptySize, width));
p.setRenderHint(QPainter::HighQualityAntialiasing, false);
st::historyEmptyDog.paint(p, position.x() + (st::historyEmptySize - st::historyEmptyDog.width()) / 2, position.y() + (st::historyEmptySize - st::historyEmptyDog.height()) / 2, width);
}
void serviceColorsUpdated() {
if (serviceMessageStyle) {
for (auto &corner : serviceMessageStyle->corners) {

View file

@ -48,6 +48,8 @@ private:
};
void paintEmpty(Painter &p, int width, int height);
void serviceColorsUpdated();
} // namespace HistoryLayout

View file

@ -376,8 +376,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
textstyleRestore();
}
} else if (noHistoryDisplayed) {
QPoint dogPos((width() - st::msgDogImg.pxWidth()) / 2, ((height() - st::msgDogImg.pxHeight()) * 4) / 9);
p.drawPixmap(dogPos, Window::chatBackground()->dog());
HistoryLayout::paintEmpty(p, width(), height());
}
if (!noHistoryDisplayed) {
adjustCurrent(r.top());
@ -8776,8 +8775,7 @@ void HistoryWidget::paintEvent(QPaintEvent *e) {
}
if (_scroll.isHidden()) {
p.setClipRect(_scroll.geometry());
QPoint dogPos((width() - st::msgDogImg.pxWidth()) / 2, ((height() - _field.height() - 2 * st::sendPadding - st::msgDogImg.pxHeight()) * 4) / 9);
p.drawPixmap(dogPos, Window::chatBackground()->dog());
HistoryLayout::paintEmpty(p, width(), height() - _field.height() - 2 * st::sendPadding);
}
} else {
style::font font(st::msgServiceFont);

View file

@ -230,11 +230,6 @@ style::color documentSelectedColor(int32 colorIndex) {
return colors[colorIndex & 3];
}
style::sprite documentCorner(int32 colorIndex) {
static style::sprite corners[] = { st::msgFileBlue, st::msgFileGreen, st::msgFileRed, st::msgFileYellow };
return corners[colorIndex & 3];
}
RoundCorners documentCorners(int32 colorIndex) {
return RoundCorners(DocBlueCorners + (colorIndex & 3));
}

View file

@ -86,7 +86,6 @@ style::color documentColor(int32 colorIndex);
style::color documentDarkColor(int32 colorIndex);
style::color documentOverColor(int32 colorIndex);
style::color documentSelectedColor(int32 colorIndex);
style::sprite documentCorner(int32 colorIndex);
RoundCorners documentCorners(int32 colorIndex);
bool documentIsValidMediaFile(const QString &filepath);

View file

@ -715,7 +715,7 @@ HitTestType MainWindow::hitTest(const QPoint &p) const {
}
QRect MainWindow::iconRect() const {
return QRect(st::titleIconPos + title->geometry().topLeft(), st::titleIconImg.pxSize());
return title->iconRect();
}
bool MainWindow::eventFilter(QObject *obj, QEvent *e) {

View file

@ -85,19 +85,28 @@ mediaviewClose: icon {{ "mediaview_close", #ffffff }};
mediaviewSave: icon {{ "mediaview_download", #ffffff }};
mediaviewMore: icon {{ "mediaview_more", #ffffff }};
mediaviewFileRedCornerFg: #d55959;
mediaviewFileYellowCornerFg: #e8a659;
mediaviewFileGreenCornerFg: #49a957;
mediaviewFileBlueCornerFg: #599dcf;
mediaviewFileRed: icon {
{ size(25px, 25px), #ffffff },
{ "mediaview_file_corner", #d55959 },
{ "mediaview_file_corner", mediaviewFileRedCornerFg },
};
mediaviewFileYellow: icon {
{ size(25px, 25px), #ffffff },
{ "mediaview_file_corner", #e8a659 },
{ "mediaview_file_corner", mediaviewFileYellowCornerFg },
};
mediaviewFileGreen: icon {
{ size(25px, 25px), #ffffff },
{ "mediaview_file_corner", #49a957 },
{ "mediaview_file_corner", mediaviewFileGreenCornerFg },
};
mediaviewFileBlue: icon {
{ size(25px, 25px), #ffffff },
{ "mediaview_file_corner", #599dcf },
{ "mediaview_file_corner", mediaviewFileBlueCornerFg },
};
mediaviewTransparentBg: #ffffff;
mediaviewTransparentFg: #cccccc;
mediaviewTransparentSize: 4px;

View file

@ -103,7 +103,7 @@ MediaView::MediaView() : TWidget(App::wnd())
}
});
_transparentBrush = QBrush(App::sprite().copy(st::mvTransparentBrush.rect()));
generateTransparentBrush();
setWindowFlags(Qt::FramelessWindowHint | Qt::BypassWindowManagerHint | Qt::Tool | Qt::NoDropShadowWindowHint);
moveToScreen();
@ -2628,6 +2628,19 @@ void MediaView::loadBack() {
}
}
void MediaView::generateTransparentBrush() {
auto size = st::mediaviewTransparentSize * cIntRetinaFactor();
auto transparent = QImage(2 * size, 2 * size, QImage::Format_ARGB32_Premultiplied);
transparent.fill(st::mediaviewTransparentBg->c);
{
Painter p(&transparent);
p.fillRect(rtlrect(0, size, size, size, 2 * size), st::mediaviewTransparentFg);
p.fillRect(rtlrect(size, 0, size, size, 2 * size), st::mediaviewTransparentFg);
}
transparent.setDevicePixelRatio(cRetinaFactor());
_transparentBrush = QBrush(transparent);
}
MediaView::LastChatPhoto MediaView::computeLastOverviewChatPhoto() {
LastChatPhoto emptyResult = { nullptr, nullptr };
auto lastPhotoInOverview = [&emptyResult](auto history, auto list) -> LastChatPhoto {

View file

@ -136,6 +136,8 @@ private:
void findCurrent();
void loadBack();
void generateTransparentBrush();
void updateCursor();
void setZoomLevel(int newZoom);

View file

@ -20,6 +20,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
using "basic.style";
using "history/history.style";
using "media/view/mediaview.style";
OverviewFileLayout {
maxWidth: pixels;

View file

@ -34,6 +34,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "application.h"
#include "overview/overview_layout.h"
#include "history/history_media_types.h"
#include "history/history_service_layout.h"
#include "media/media_audio.h"
// flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html
@ -778,8 +779,7 @@ void OverviewInner::paintEvent(QPaintEvent *e) {
Overview::Layout::PaintContext context(ms, _selMode);
if (_history->overview[_type].isEmpty() && (!_migrated || !_history->overviewLoaded(_type) || _migrated->overview[_type].isEmpty())) {
QPoint dogPos((_width - st::msgDogImg.pxWidth()) / 2, ((height() - st::msgDogImg.pxHeight()) * 4) / 9);
p.drawPixmap(dogPos, Window::chatBackground()->dog());
HistoryLayout::paintEmpty(p, _width, height());
return;
} else if (_inSearch && _searchResults.isEmpty() && _searchFull && (!_migrated || _searchFullMigrated) && !_searchTimer.isActive()) {
p.setFont(st::noContactsFont->f);

View file

@ -529,14 +529,14 @@ void CoverWidget::onAddMember() {
if (_peerChat->count >= Global::ChatSizeMax() && _peerChat->amCreator()) {
Ui::showLayer(new ConvertToSupergroupBox(_peerChat));
} else {
Ui::showLayer(new ContactsBox(_peerChat, MembersFilterRecent));
Ui::showLayer(new ContactsBox(_peerChat, MembersFilter::Recent));
}
} else if (_peerChannel && _peerChannel->mgInfo) {
MembersAlreadyIn already;
for_const (auto user, _peerChannel->mgInfo->lastParticipants) {
already.insert(user);
}
Ui::showLayer(new ContactsBox(_peerChannel, MembersFilterRecent, already));
Ui::showLayer(new ContactsBox(_peerChannel, MembersFilter::Recent, already));
}
}

View file

@ -700,13 +700,13 @@ int ChannelMembersWidget::resizeGetHeight(int newWidth) {
void ChannelMembersWidget::onAdmins() {
if (auto channel = peer()->asChannel()) {
Ui::showLayer(new MembersBox(channel, MembersFilterAdmins));
Ui::showLayer(new MembersBox(channel, MembersFilter::Admins));
}
}
void ChannelMembersWidget::onMembers() {
if (auto channel = peer()->asChannel()) {
Ui::showLayer(new MembersBox(channel, MembersFilterRecent));
Ui::showLayer(new MembersBox(channel, MembersFilter::Recent));
}
}

View file

@ -164,9 +164,9 @@ void SettingsWidget::onNotificationsChange() {
void SettingsWidget::onManageAdmins() {
if (auto chat = peer()->asChat()) {
Ui::showLayer(new ContactsBox(chat, MembersFilterAdmins));
Ui::showLayer(new ContactsBox(chat, MembersFilter::Admins));
} else if (auto channel = peer()->asChannel()) {
Ui::showLayer(new MembersBox(channel, MembersFilterAdmins));
Ui::showLayer(new MembersBox(channel, MembersFilter::Admins));
}
}

View file

@ -29,7 +29,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "mainwidget.h"
#include "mainwindow.h"
#include "boxes/emojibox.h"
#include "boxes/stickersetbox.h"
#include "boxes/stickers_box.h"
#include "boxes/downloadpathbox.h"
#include "boxes/connectionbox.h"
#include "boxes/confirmbox.h"

View file

@ -24,6 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "styles/style_stickers.h"
#include "boxes/confirmbox.h"
#include "boxes/stickersetbox.h"
#include "boxes/stickers_box.h"
#include "inline_bots/inline_bot_result.h"
#include "inline_bots/inline_bot_layout_item.h"
#include "dialogs/dialogs_layout.h"

View file

@ -21,7 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "stdafx.h"
#include "stickers.h"
#include "boxes/stickersetbox.h"
#include "boxes/stickers_box.h"
#include "boxes/confirmbox.h"
#include "lang.h"
#include "apiwrap.h"

View file

@ -20,6 +20,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
using "basic.style";
using "boxes/boxes.style";
featuredStickersHeader: 45px;
featuredStickersSkip: 15px;

View file

@ -31,6 +31,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "media/player/media_player_title_button.h"
#include "media/player/media_player_panel.h"
#include "media/player/media_player_instance.h"
#include "styles/style_window.h"
class TitleWidget::Hider : public TWidget {
public:
@ -126,9 +127,9 @@ void TitleWidget::paintEvent(QPaintEvent *e) {
auto chooseText = lang(inlineSwitchChoose ? lng_inline_switch_choose : lng_forward_choose);
p.drawText(st::titleMenuOffset - st::titleTextButton.width / 2, st::titleTextButton.textTop + st::titleTextButton.font->ascent, chooseText);
}
p.drawSprite(st::titleIconPos, st::titleIconImg);
st::titleIcon.paint(p, st::titleIconPosition, width());
if (Adaptive::OneColumn() && !_counter.isNull() && App::main()) {
p.drawPixmap(st::titleIconPos.x() + st::titleIconImg.pxWidth() - (_counter.width() / cIntRetinaFactor()), st::titleIconPos.y() + st::titleIconImg.pxHeight() - (_counter.height() / cIntRetinaFactor()), _counter);
p.drawPixmap(st::titleCounterPosition, _counter);
}
}
@ -335,11 +336,11 @@ void TitleWidget::updateCounter() {
}
_counter = App::pixmapFromImageInPlace(App::wnd()->iconWithCounter(size, counter, bg, false));
_counter.setDevicePixelRatio(cRetinaFactor());
update(QRect(st::titleIconPos, st::titleIconImg.pxSize()));
update(QRect(st::titleCounterPosition, _counter.size() / cIntRetinaFactor()));
} else {
if (!_counter.isNull()) {
update(QRect(st::titleCounterPosition, _counter.size() / cIntRetinaFactor()));
_counter = QPixmap();
update(QRect(st::titleIconPos, st::titleIconImg.pxSize()));
}
}
}
@ -395,7 +396,7 @@ HitTestType TitleWidget::hitTest(const QPoint &p) {
int x(p.x()), y(p.y()), w(width()), h(height());
if (!Adaptive::OneColumn() && _hider && x >= App::main()->dlgsWidth()) return HitTestType::None;
if (x >= st::titleIconPos.x() && y >= st::titleIconPos.y() && x < st::titleIconPos.x() + st::titleIconImg.pxWidth() && y < st::titleIconPos.y() + st::titleIconImg.pxHeight()) {
if (x >= st::titleIconPosition.x() && y >= st::titleIconPosition.y() && x < st::titleIconPosition.x() + st::titleIcon.width() && y < st::titleIconPosition.y() + st::titleIcon.height()) {
return HitTestType::Icon;
} else if (false
|| (_player && _player->geometry().contains(p))
@ -420,3 +421,7 @@ HitTestType TitleWidget::hitTest(const QPoint &p) {
}
return HitTestType::None;
}
QRect TitleWidget::iconRect() const {
return myrtlrect(QRect(st::titleIconPosition, st::titleIcon.size()));
}

View file

@ -42,6 +42,7 @@ public:
void maximizedChanged(bool maximized, bool force = false);
HitTestType hitTest(const QPoint &p);
QRect iconRect() const;
void setHideLevel(float64 level);

View file

@ -102,6 +102,22 @@ namespace anim {
float64 easeInQuint(const float64 &delta, const float64 &dt);
float64 easeOutQuint(const float64 &delta, const float64 &dt);
template <int BumpRatioNumerator, int BumpRatioDenominator>
float64 bumpy(const float64 &delta, const float64 &dt) {
struct Bumpy {
Bumpy()
: bump(BumpRatioNumerator / float64(BumpRatioDenominator))
, dt0(bump - sqrt(bump * (bump - 1.)))
, k(1 / (2 * dt0 - 1)) {
}
float64 bump;
float64 dt0;
float64 k;
};
static Bumpy data;
return delta * (data.bump - data.k * (dt - data.dt0) * (dt - data.dt0));
}
class fvalue { // float animated value
public:
using ValueType = float64;
@ -468,6 +484,15 @@ public:
using ValueType = typename AnimType::ValueType;
using Callback = base::lambda_unique<void()>;
void step(uint64 ms) {
if (_data) {
_data->a_animation.step(ms);
if (_data && !_data->a_animation.animating()) {
_data.reset();
}
}
}
bool animating() const {
if (_data) {
if (_data->a_animation.animating()) {
@ -478,11 +503,8 @@ public:
return false;
}
bool animating(uint64 ms) {
if (animating()) {
_data->a_animation.step(ms);
return animating();
}
return false;
step(ms);
return animating();
}
ValueType current() const {
@ -499,7 +521,7 @@ public:
template <typename Lambda>
void start(Lambda &&updateCallback, const ValueType &from, const ValueType &to, float64 duration, anim::transition transition = anim::linear) {
if (!_data) {
_data = std_::make_unique<Data>(from, std_::move(updateCallback));
_data = std_::make_unique<Data>(from, std_::forward<Lambda>(updateCallback));
}
_data->value.start(to);
_data->duration = duration;
@ -517,11 +539,17 @@ public:
private:
struct Data {
Data(const ValueType &from, Callback &&updateCallback)
template <typename Lambda, typename = std_::enable_if_t<std_::is_rvalue_reference<Lambda&&>::value>>
Data(const ValueType &from, Lambda &&updateCallback)
: value(from, from)
, a_animation(animation(this, &Data::step))
, updateCallback(std_::move(updateCallback)) {
}
Data(const ValueType &from, const base::lambda_wrap<void()> &updateCallback)
: value(from, from)
, a_animation(animation(this, &Data::step))
, updateCallback(base::lambda_wrap<void()>(updateCallback)) {
}
void step(float64 ms, bool timer) {
auto dt = (ms >= duration) ? 1. : (ms / duration);
if (dt >= 1) {

View file

@ -24,8 +24,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "lang.h"
#include "application.h"
#include "ui/scrollarea.h"
#include "ui/widgets/multi_select.h"
#include "boxes/contactsbox.h"
#include "countries.h"
#include "styles/style_boxes.h"
namespace {
@ -63,7 +65,8 @@ namespace {
countriesFiltered.reserve(countriesCount);
countriesNames.resize(countriesCount);
}
}
} // namespace
const CountriesByCode &countriesByCode() {
initCountries();
@ -192,7 +195,73 @@ void CountryInput::setText(const QString &newText) {
_text = _st.font->elided(newText, width() - _st.textMrg.left() - _st.textMrg.right());
}
CountrySelectInner::CountrySelectInner() : TWidget()
CountrySelectBox::CountrySelectBox() : ItemListBox(st::countriesScroll, st::boxWidth)
, _inner(this)
, _select(this, st::contactsMultiSelect, lang(lng_country_ph))
, _topShadow(this) {
_select->resizeToWidth(st::boxWidth);
ItemListBox::init(_inner, st::boxScrollSkip, st::boxTitleHeight + _select->height());
_select->setQueryChangedCallback([this](const QString &query) { onFilterUpdate(query); });
_select->setSubmittedCallback([this](bool) { onSubmit(); });
connect(_inner, SIGNAL(mustScrollTo(int, int)), scrollArea(), SLOT(scrollToY(int, int)));
connect(_inner, SIGNAL(countryChosen(const QString&)), this, SIGNAL(countryChosen(const QString&)));
prepare();
}
void CountrySelectBox::onSubmit() {
_inner->chooseCountry();
}
void CountrySelectBox::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Down) {
_inner->selectSkip(1);
} else if (e->key() == Qt::Key_Up) {
_inner->selectSkip(-1);
} else if (e->key() == Qt::Key_PageDown) {
_inner->selectSkipPage(scrollArea()->height(), 1);
} else if (e->key() == Qt::Key_PageUp) {
_inner->selectSkipPage(scrollArea()->height(), -1);
} else {
ItemListBox::keyPressEvent(e);
}
}
void CountrySelectBox::paintEvent(QPaintEvent *e) {
Painter p(this);
if (paint(p)) return;
paintTitle(p, lang(lng_country_select));
}
void CountrySelectBox::resizeEvent(QResizeEvent *e) {
ItemListBox::resizeEvent(e);
_select->resizeToWidth(width());
_select->moveToLeft(0, st::boxTitleHeight);
_inner->resizeToWidth(width());
_topShadow.setGeometry(0, st::boxTitleHeight + _select->height(), width(), st::lineWidth);
}
void CountrySelectBox::showAll() {
_select->show();
_topShadow.show();
ItemListBox::showAll();
}
void CountrySelectBox::onFilterUpdate(const QString &query) {
scrollArea()->scrollToY(0);
_inner->updateFilter(query);
}
void CountrySelectBox::doSetInnerFocus() {
_select->setInnerFocus();
}
CountrySelectBox::Inner::Inner(QWidget *parent) : ScrolledWidget(parent)
, _rowHeight(st::countryRowHeight)
, _sel(0)
, _mouseSel(false) {
@ -239,7 +308,7 @@ CountrySelectInner::CountrySelectInner() : TWidget()
updateFilter();
}
void CountrySelectInner::paintEvent(QPaintEvent *e) {
void CountrySelectBox::Inner::paintEvent(QPaintEvent *e) {
Painter p(this);
QRect r(e->rect());
p.setClipRect(r);
@ -283,11 +352,11 @@ void CountrySelectInner::paintEvent(QPaintEvent *e) {
}
}
void CountrySelectInner::enterEvent(QEvent *e) {
void CountrySelectBox::Inner::enterEvent(QEvent *e) {
setMouseTracking(true);
}
void CountrySelectInner::leaveEvent(QEvent *e) {
void CountrySelectBox::Inner::leaveEvent(QEvent *e) {
_mouseSel = false;
setMouseTracking(false);
if (_sel >= 0) {
@ -296,13 +365,13 @@ void CountrySelectInner::leaveEvent(QEvent *e) {
}
}
void CountrySelectInner::mouseMoveEvent(QMouseEvent *e) {
void CountrySelectBox::Inner::mouseMoveEvent(QMouseEvent *e) {
_mouseSel = true;
_lastMousePos = e->globalPos();
updateSel();
}
void CountrySelectInner::mousePressEvent(QMouseEvent *e) {
void CountrySelectBox::Inner::mousePressEvent(QMouseEvent *e) {
_mouseSel = true;
_lastMousePos = e->globalPos();
updateSel();
@ -311,7 +380,7 @@ void CountrySelectInner::mousePressEvent(QMouseEvent *e) {
}
}
void CountrySelectInner::updateFilter(QString filter) {
void CountrySelectBox::Inner::updateFilter(QString filter) {
filter = textSearchKey(filter);
QStringList f;
@ -366,7 +435,7 @@ void CountrySelectInner::updateFilter(QString filter) {
}
}
void CountrySelectInner::selectSkip(int32 dir) {
void CountrySelectBox::Inner::selectSkip(int32 dir) {
_mouseSel = false;
int cur = (_sel >= 0) ? _sel : -1;
@ -384,13 +453,13 @@ void CountrySelectInner::selectSkip(int32 dir) {
update();
}
void CountrySelectInner::selectSkipPage(int32 h, int32 dir) {
void CountrySelectBox::Inner::selectSkipPage(int32 h, int32 dir) {
int32 points = h / _rowHeight;
if (!points) return;
selectSkip(points * dir);
}
void CountrySelectInner::chooseCountry() {
void CountrySelectBox::Inner::chooseCountry() {
QString result;
if (_filter.isEmpty()) {
if (_sel >= 0 && _sel < countriesAll.size()) {
@ -404,11 +473,11 @@ void CountrySelectInner::chooseCountry() {
emit countryChosen(result);
}
void CountrySelectInner::refresh() {
void CountrySelectBox::Inner::refresh() {
resize(width(), countriesNow->length() ? (countriesNow->length() * _rowHeight + st::countriesSkip) : st::noContactsHeight);
}
void CountrySelectInner::updateSel() {
void CountrySelectBox::Inner::updateSel() {
if (!_mouseSel) return;
QPoint p(mapFromGlobal(_lastMousePos));
@ -422,89 +491,8 @@ void CountrySelectInner::updateSel() {
}
}
void CountrySelectInner::updateSelectedRow() {
void CountrySelectBox::Inner::updateSelectedRow() {
if (_sel >= 0) {
update(0, st::countriesSkip + _sel * _rowHeight, width(), _rowHeight);
}
}
CountrySelectBox::CountrySelectBox() : ItemListBox(st::countriesScroll, st::boxWidth)
, _inner()
, _filter(this, st::boxSearchField, lang(lng_country_ph))
, _filterCancel(this, st::boxSearchCancel)
, _topShadow(this) {
ItemListBox::init(&_inner, st::boxScrollSkip, st::boxTitleHeight + _filter.height());
connect(&_filter, SIGNAL(changed()), this, SLOT(onFilterUpdate()));
connect(&_filter, SIGNAL(submitted(bool)), this, SLOT(onSubmit()));
connect(&_filterCancel, SIGNAL(clicked()), this, SLOT(onFilterCancel()));
connect(&_inner, SIGNAL(mustScrollTo(int, int)), scrollArea(), SLOT(scrollToY(int, int)));
connect(&_inner, SIGNAL(countryChosen(const QString&)), this, SIGNAL(countryChosen(const QString&)));
_filterCancel.setAttribute(Qt::WA_OpaquePaintEvent);
prepare();
}
void CountrySelectBox::onSubmit() {
_inner.chooseCountry();
}
void CountrySelectBox::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Down) {
_inner.selectSkip(1);
} else if (e->key() == Qt::Key_Up) {
_inner.selectSkip(-1);
} else if (e->key() == Qt::Key_PageDown) {
_inner.selectSkipPage(scrollArea()->height(), 1);
} else if (e->key() == Qt::Key_PageUp) {
_inner.selectSkipPage(scrollArea()->height(), -1);
} else {
ItemListBox::keyPressEvent(e);
}
}
void CountrySelectBox::paintEvent(QPaintEvent *e) {
Painter p(this);
if (paint(p)) return;
paintTitle(p, lang(lng_country_select));
}
void CountrySelectBox::resizeEvent(QResizeEvent *e) {
ItemListBox::resizeEvent(e);
_filter.resize(width(), _filter.height());
_filter.moveToLeft(0, st::boxTitleHeight);
_filterCancel.moveToRight(0, st::boxTitleHeight);
_inner.resize(width(), _inner.height());
_topShadow.setGeometry(0, st::boxTitleHeight + _filter.height(), width(), st::lineWidth);
}
void CountrySelectBox::showAll() {
_filter.show();
if (_filter.getLastText().isEmpty()) {
_filterCancel.hide();
} else {
_filterCancel.show();
}
_topShadow.show();
ItemListBox::showAll();
}
void CountrySelectBox::onFilterCancel() {
_filter.setText(QString());
}
void CountrySelectBox::onFilterUpdate() {
scrollArea()->scrollToY(0);
if (_filter.getLastText().isEmpty()) {
_filterCancel.hide();
} else {
_filterCancel.show();
}
_inner.updateFilter(_filter.getLastText());
}
void CountrySelectBox::doSetInnerFocus() {
_filter.setFocus();
}

View file

@ -30,6 +30,10 @@ QString findValidCode(QString fullCode);
class CountrySelect;
namespace Ui {
class MultiSelect;
} // namespace Ui
class CountryInput : public QWidget {
Q_OBJECT
@ -63,11 +67,47 @@ private:
};
class CountrySelectInner : public TWidget {
namespace internal {
class CountrySelectInner;
} // namespace internal
class CountrySelectBox : public ItemListBox {
Q_OBJECT
public:
CountrySelectInner();
CountrySelectBox();
signals:
void countryChosen(const QString &iso);
public slots:
void onSubmit();
protected:
void keyPressEvent(QKeyEvent *e) override;
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void doSetInnerFocus() override;
void showAll() override;
private:
void onFilterUpdate(const QString &query);
class Inner;
ChildWidget<Inner> _inner;
ChildWidget<Ui::MultiSelect> _select;
ScrollableBoxShadow _topShadow;
};
// This class is hold in header because it requires Qt preprocessing.
class CountrySelectBox::Inner : public ScrolledWidget {
Q_OBJECT
public:
Inner(QWidget *parent);
void updateFilter(QString filter = QString());
@ -104,34 +144,3 @@ private:
QPoint _lastMousePos;
};
class CountrySelectBox : public ItemListBox {
Q_OBJECT
public:
CountrySelectBox();
signals:
void countryChosen(const QString &iso);
public slots:
void onFilterUpdate();
void onFilterCancel();
void onSubmit();
protected:
void keyPressEvent(QKeyEvent *e) override;
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void doSetInnerFocus() override;
void showAll() override;
private:
CountrySelectInner _inner;
InputField _filter;
IconedButton _filterCancel;
ScrollableBoxShadow _topShadow;
};

View file

@ -0,0 +1,217 @@
/*
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-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "ui/effects/round_image_checkbox.h"
namespace Ui {
namespace {
static constexpr int kWideScale = 4;
void prepareCheckCaches(const style::RoundImageCheckbox *st, QPixmap &checkBgCache, QPixmap &checkFullCache) {
auto size = st->checkRadius * 2;
auto wideSize = size * kWideScale;
auto cache = QImage(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
cache.setDevicePixelRatio(cRetinaFactor());
{
Painter p(&cache);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.fillRect(0, 0, wideSize, wideSize, Qt::transparent);
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
p.setRenderHint(QPainter::HighQualityAntialiasing, true);
auto pen = st->checkBorder->p;
pen.setWidth(st->selectWidth);
p.setPen(pen);
p.setBrush(st->checkBg);
auto ellipse = QRect((wideSize - size) / 2, (wideSize - size) / 2, size, size);
p.drawEllipse(ellipse);
}
auto cacheIcon = cache;
{
Painter p(&cacheIcon);
auto ellipse = QRect((wideSize - size) / 2, (wideSize - size) / 2, size, size);
st->checkIcon.paint(p, ellipse.topLeft(), wideSize);
}
checkBgCache = App::pixmapFromImageInPlace(std_::move(cache));
checkBgCache.setDevicePixelRatio(cRetinaFactor());
checkFullCache = App::pixmapFromImageInPlace(std_::move(cacheIcon));
checkFullCache.setDevicePixelRatio(cRetinaFactor());
}
} // namespace
RoundImageCheckbox::RoundImageCheckbox(const style::RoundImageCheckbox &st, base::lambda_wrap<void()> updateCallback, PaintRoundImage paintRoundImage)
: _st(st)
, _updateCallback(std_::move(updateCallback))
, _paintRoundImage(std_::move(paintRoundImage)) {
prepareCheckCaches(&_st, _wideCheckBgCache, _wideCheckFullCache);
}
void RoundImageCheckbox::paint(Painter &p, uint64 ms, int x, int y, int outerWidth) {
_selection.step(ms);
for (auto &icon : _icons) {
icon.fadeIn.step(ms);
icon.fadeOut.step(ms);
}
removeFadeOutedIcons();
auto selectionLevel = _selection.current(_checked ? 1. : 0.);
if (_selection.animating()) {
auto userpicRadius = qRound(kWideScale * (_st.imageRadius + (_st.imageSmallRadius - _st.imageRadius) * selectionLevel));
auto userpicShift = kWideScale * _st.imageRadius - userpicRadius;
auto userpicLeft = x - (kWideScale - 1) * _st.imageRadius + userpicShift;
auto userpicTop = y - (kWideScale - 1) * _st.imageRadius + userpicShift;
auto to = QRect(userpicLeft, userpicTop, userpicRadius * 2, userpicRadius * 2);
auto from = QRect(QPoint(0, 0), _wideCache.size());
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
p.drawPixmapLeft(to, outerWidth, _wideCache, from);
p.setRenderHint(QPainter::SmoothPixmapTransform, false);
} else {
if (!_wideCache.isNull()) {
_wideCache = QPixmap();
}
auto userpicRadius = _checked ? _st.imageSmallRadius : _st.imageRadius;
auto userpicShift = _st.imageRadius - userpicRadius;
auto userpicLeft = x + userpicShift;
auto userpicTop = y + userpicShift;
_paintRoundImage(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
}
if (selectionLevel > 0) {
p.setRenderHint(QPainter::HighQualityAntialiasing, true);
p.setOpacity(snap(selectionLevel, 0., 1.));
p.setBrush(Qt::NoBrush);
auto pen = _st.selectFg->p;
pen.setWidth(_st.selectWidth);
p.setPen(pen);
p.drawEllipse(rtlrect(x, y, _st.imageRadius * 2, _st.imageRadius * 2, outerWidth));
p.setOpacity(1.);
p.setRenderHint(QPainter::HighQualityAntialiasing, false);
}
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
for (auto &icon : _icons) {
auto fadeIn = icon.fadeIn.current(1.);
auto fadeOut = icon.fadeOut.current(1.);
auto iconRadius = qRound(kWideScale * (_st.checkSmallRadius + fadeOut * (_st.checkRadius - _st.checkSmallRadius)));
auto iconShift = kWideScale * _st.checkRadius - iconRadius;
auto iconLeft = x + 2 * _st.imageRadius + _st.selectWidth - 2 * _st.checkRadius - (kWideScale - 1) * _st.checkRadius + iconShift;
auto iconTop = y + 2 * _st.imageRadius + _st.selectWidth - 2 * _st.checkRadius - (kWideScale - 1) * _st.checkRadius + iconShift;
auto to = QRect(iconLeft, iconTop, iconRadius * 2, iconRadius * 2);
auto from = QRect(QPoint(0, 0), _wideCheckFullCache.size());
auto opacity = fadeIn * fadeOut;
p.setOpacity(opacity);
if (fadeOut < 1.) {
p.drawPixmapLeft(to, outerWidth, icon.wideCheckCache, from);
} else {
auto divider = qRound((kWideScale - 2) * _st.checkRadius + fadeIn * 3 * _st.checkRadius);
p.drawPixmapLeft(QRect(iconLeft, iconTop, divider, iconRadius * 2), outerWidth, _wideCheckFullCache, QRect(0, 0, divider * cIntRetinaFactor(), _wideCheckFullCache.height()));
p.drawPixmapLeft(QRect(iconLeft + divider, iconTop, iconRadius * 2 - divider, iconRadius * 2), outerWidth, _wideCheckBgCache, QRect(divider * cIntRetinaFactor(), 0, _wideCheckBgCache.width() - divider * cIntRetinaFactor(), _wideCheckBgCache.height()));
}
}
p.setRenderHint(QPainter::SmoothPixmapTransform, false);
p.setOpacity(1.);
}
float64 RoundImageCheckbox::checkedAnimationRatio() const {
return snap(_selection.current(_checked ? 1. : 0.), 0., 1.);
}
void RoundImageCheckbox::setChecked(bool checked, SetStyle speed) {
if (_checked == checked) {
if (speed != SetStyle::Animated) {
if (!_icons.isEmpty()) {
_icons.back().fadeIn.finish();
_icons.back().fadeOut.finish();
}
_selection.finish();
}
return;
}
_checked = checked;
if (_checked) {
_icons.push_back(Icon());
_icons.back().fadeIn.start(_updateCallback, 0, 1, _st.selectDuration);
if (speed != SetStyle::Animated) {
_icons.back().fadeIn.finish();
}
} else {
_icons.back().fadeOut.start(_updateCallback, 1, 0, _st.selectDuration);
if (speed == SetStyle::Animated) {
prepareWideCheckIconCache(&_icons.back());
} else {
_icons.back().fadeOut.finish();
}
}
if (speed == SetStyle::Animated) {
prepareWideCache();
_selection.start(_updateCallback, _checked ? 0 : 1, _checked ? 1 : 0, _st.selectDuration, anim::bumpy<125, 100>);
} else {
_selection.finish();
}
}
void RoundImageCheckbox::removeFadeOutedIcons() {
while (!_icons.empty() && !_icons.front().fadeIn.animating() && !_icons.front().fadeOut.animating()) {
if (_icons.size() > 1 || !_checked) {
_icons.erase(_icons.begin());
} else {
break;
}
}
}
void RoundImageCheckbox::prepareWideCache() {
if (_wideCache.isNull()) {
auto size = _st.imageRadius * 2;
auto wideSize = size * kWideScale;
QImage cache(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
cache.setDevicePixelRatio(cRetinaFactor());
{
Painter p(&cache);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.fillRect(0, 0, wideSize, wideSize, Qt::transparent);
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
_paintRoundImage(p, (wideSize - size) / 2, (wideSize - size) / 2, wideSize, size);
}
_wideCache = App::pixmapFromImageInPlace(std_::move(cache));
}
}
void RoundImageCheckbox::prepareWideCheckIconCache(Icon *icon) {
auto cacheWidth = _wideCheckBgCache.width() / _wideCheckBgCache.devicePixelRatio();
auto cacheHeight = _wideCheckBgCache.height() / _wideCheckBgCache.devicePixelRatio();
auto wideCache = QImage(cacheWidth * cIntRetinaFactor(), cacheHeight * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
wideCache.setDevicePixelRatio(cRetinaFactor());
{
Painter p(&wideCache);
p.setCompositionMode(QPainter::CompositionMode_Source);
auto iconRadius = kWideScale * _st.checkRadius;
auto divider = qRound((kWideScale - 2) * _st.checkRadius + icon->fadeIn.current(1.) * (kWideScale - 1) * _st.checkRadius);
p.drawPixmapLeft(QRect(0, 0, divider, iconRadius * 2), cacheWidth, _wideCheckFullCache, QRect(0, 0, divider * cIntRetinaFactor(), _wideCheckFullCache.height()));
p.drawPixmapLeft(QRect(divider, 0, iconRadius * 2 - divider, iconRadius * 2), cacheWidth, _wideCheckBgCache, QRect(divider * cIntRetinaFactor(), 0, _wideCheckBgCache.width() - divider * cIntRetinaFactor(), _wideCheckBgCache.height()));
}
icon->wideCheckCache = App::pixmapFromImageInPlace(std_::move(wideCache));
icon->wideCheckCache.setDevicePixelRatio(cRetinaFactor());
}
} // namespace Ui

View file

@ -0,0 +1,68 @@
/*
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-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "styles/style_widgets.h"
namespace Ui {
class RoundImageCheckbox {
public:
using PaintRoundImage = base::lambda_unique<void(Painter &p, int x, int y, int outerWidth, int size)>;
RoundImageCheckbox(const style::RoundImageCheckbox &st, base::lambda_wrap<void()> updateCallback, PaintRoundImage paintRoundImage);
void paint(Painter &p, uint64 ms, int x, int y, int outerWidth);
float64 checkedAnimationRatio() const;
bool checked() const {
return _checked;
}
enum class SetStyle {
Animated,
Fast,
};
void setChecked(bool checked, SetStyle speed = SetStyle::Animated);
private:
struct Icon {
FloatAnimation fadeIn;
FloatAnimation fadeOut;
QPixmap wideCheckCache;
};
void removeFadeOutedIcons();
void prepareWideCache();
void prepareWideCheckIconCache(Icon *icon);
const style::RoundImageCheckbox &_st;
base::lambda_wrap<void()> _updateCallback;
PaintRoundImage _paintRoundImage;
bool _checked = false;
QPixmap _wideCache;
FloatAnimation _selection;
std_::vector_of_moveable<Icon> _icons;
// Those pixmaps are shared among all checkboxes that have the same style.
QPixmap _wideCheckBgCache, _wideCheckFullCache;
};
} // namespace Ui

View file

@ -184,8 +184,8 @@ void FlatInput::paintEvent(QPaintEvent *e) {
p.drawRoundedRect(QRectF(0, 0, width(), height()).marginsRemoved(QMarginsF(_st.borderWidth / 2., _st.borderWidth / 2., _st.borderWidth / 2., _st.borderWidth / 2.)), st::buttonRadius - (_st.borderWidth / 2.), st::buttonRadius - (_st.borderWidth / 2.));
p.setRenderHint(QPainter::HighQualityAntialiasing, false);
if (_st.imgRect.pxWidth()) {
p.drawSprite(_st.imgPos, _st.imgRect);
if (!_st.icon.empty()) {
_st.icon.paint(p, 0, 0, width());
}
bool phDraw = _phVisible;
@ -683,10 +683,10 @@ void InputArea::checkContentHeight() {
}
}
InputArea::InputAreaInner::InputAreaInner(InputArea *parent) : QTextEdit(parent) {
InputArea::Inner::Inner(InputArea *parent) : QTextEdit(parent) {
}
bool InputArea::InputAreaInner::viewportEvent(QEvent *e) {
bool InputArea::Inner::viewportEvent(QEvent *e) {
if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) {
QTouchEvent *ev = static_cast<QTouchEvent*>(e);
if (ev->device()->type() == QTouchDevice::TouchScreen) {
@ -790,7 +790,7 @@ void InputArea::contextMenuEvent(QContextMenuEvent *e) {
_inner.contextMenuEvent(e);
}
void InputArea::InputAreaInner::focusInEvent(QFocusEvent *e) {
void InputArea::Inner::focusInEvent(QFocusEvent *e) {
f()->focusInInner();
QTextEdit::focusInEvent(e);
emit f()->focused();
@ -807,7 +807,7 @@ void InputArea::focusInInner() {
}
}
void InputArea::InputAreaInner::focusOutEvent(QFocusEvent *e) {
void InputArea::Inner::focusOutEvent(QFocusEvent *e) {
f()->focusOutInner();
QTextEdit::focusOutEvent(e);
emit f()->blurred();
@ -943,7 +943,7 @@ void InputArea::insertEmoji(EmojiPtr emoji, QTextCursor c) {
c.insertText(objectReplacement, imageFormat);
}
QVariant InputArea::InputAreaInner::loadResource(int type, const QUrl &name) {
QVariant InputArea::Inner::loadResource(int type, const QUrl &name) {
QString imageName = name.toDisplayString();
if (imageName.startsWith(qstr("emoji://e."))) {
if (EmojiPtr emoji = emojiFromUrl(imageName)) {
@ -1193,7 +1193,7 @@ void InputArea::updatePlaceholder() {
}
}
QMimeData *InputArea::InputAreaInner::createMimeDataFromSelection() const {
QMimeData *InputArea::Inner::createMimeDataFromSelection() const {
QMimeData *result = new QMimeData();
QTextCursor c(textCursor());
int32 start = c.selectionStart(), end = c.selectionEnd();
@ -1211,7 +1211,7 @@ void InputArea::setCtrlEnterSubmit(CtrlEnterSubmit ctrlEnterSubmit) {
_ctrlEnterSubmit = ctrlEnterSubmit;
}
void InputArea::InputAreaInner::keyPressEvent(QKeyEvent *e) {
void InputArea::Inner::keyPressEvent(QKeyEvent *e) {
bool shift = e->modifiers().testFlag(Qt::ShiftModifier), alt = e->modifiers().testFlag(Qt::AltModifier);
bool macmeta = (cPlatform() == dbipMac || cPlatform() == dbipMacOld) && e->modifiers().testFlag(Qt::ControlModifier) && !e->modifiers().testFlag(Qt::MetaModifier) && !e->modifiers().testFlag(Qt::AltModifier);
bool ctrl = e->modifiers().testFlag(Qt::ControlModifier) || e->modifiers().testFlag(Qt::MetaModifier);
@ -1276,11 +1276,11 @@ void InputArea::InputAreaInner::keyPressEvent(QKeyEvent *e) {
}
}
void InputArea::InputAreaInner::paintEvent(QPaintEvent *e) {
void InputArea::Inner::paintEvent(QPaintEvent *e) {
return QTextEdit::paintEvent(e);
}
void InputArea::InputAreaInner::contextMenuEvent(QContextMenuEvent *e) {
void InputArea::Inner::contextMenuEvent(QContextMenuEvent *e) {
if (QMenu *menu = createStandardContextMenu()) {
(new PopupMenu(menu))->popup(e->globalPos());
}
@ -1338,7 +1338,9 @@ InputField::InputField(QWidget *parent, const style::InputField &st, const QStri
_inner.setWordWrapMode(QTextOption::NoWrap);
setAttribute(Qt::WA_OpaquePaintEvent);
if (_st.textBg->c.alphaF() >= 1.) {
setAttribute(Qt::WA_OpaquePaintEvent);
}
_inner.setFont(_st.font->f);
_inner.setAlignment(_st.textAlign);
@ -1380,10 +1382,10 @@ void InputField::onTouchTimer() {
_touchRightButton = true;
}
InputField::InputFieldInner::InputFieldInner(InputField *parent) : QTextEdit(parent) {
InputField::Inner::Inner(InputField *parent) : QTextEdit(parent) {
}
bool InputField::InputFieldInner::viewportEvent(QEvent *e) {
bool InputField::Inner::viewportEvent(QEvent *e) {
if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) {
QTouchEvent *ev = static_cast<QTouchEvent*>(e);
if (ev->device()->type() == QTouchDevice::TouchScreen) {
@ -1436,8 +1438,18 @@ void InputField::touchEvent(QTouchEvent *e) {
void InputField::paintEvent(QPaintEvent *e) {
Painter p(this);
auto ms = getms();
if (_a_placeholderShift.animating()) {
_a_placeholderShift.step(ms);
}
if (_a_placeholderFg.animating()) {
_a_placeholderFg.step(ms);
}
QRect r(rect().intersected(e->rect()));
p.fillRect(r, st::white->b);
if (_st.textBg->c.alphaF() > 0.) {
p.fillRect(r, _st.textBg);
}
if (_st.border) {
p.fillRect(0, height() - _st.border, width(), _st.border, _st.borderFg->b);
}
@ -1446,9 +1458,6 @@ void InputField::paintEvent(QPaintEvent *e) {
p.fillRect(0, height() - _st.borderActive, width(), _st.borderActive, a_borderFg.current());
p.setOpacity(1);
}
if (_st.iconSprite.pxWidth()) {
p.drawSpriteLeft(_st.iconPosition, width(), _st.iconSprite);
}
bool drawPlaceholder = _placeholderVisible;
if (_a_placeholderShift.animating()) {
@ -1490,7 +1499,7 @@ void InputField::contextMenuEvent(QContextMenuEvent *e) {
_inner.contextMenuEvent(e);
}
void InputField::InputFieldInner::focusInEvent(QFocusEvent *e) {
void InputField::Inner::focusInEvent(QFocusEvent *e) {
f()->focusInInner();
QTextEdit::focusInEvent(e);
emit f()->focused();
@ -1507,7 +1516,7 @@ void InputField::focusInInner() {
}
}
void InputField::InputFieldInner::focusOutEvent(QFocusEvent *e) {
void InputField::Inner::focusOutEvent(QFocusEvent *e) {
f()->focusOutInner();
QTextEdit::focusOutEvent(e);
emit f()->blurred();
@ -1643,7 +1652,7 @@ void InputField::insertEmoji(EmojiPtr emoji, QTextCursor c) {
c.insertText(objectReplacement, imageFormat);
}
QVariant InputField::InputFieldInner::loadResource(int type, const QUrl &name) {
QVariant InputField::Inner::loadResource(int type, const QUrl &name) {
QString imageName = name.toDisplayString();
if (imageName.startsWith(qstr("emoji://e."))) {
if (EmojiPtr emoji = emojiFromUrl(imageName)) {
@ -1889,9 +1898,7 @@ void InputField::step_placeholderFg(float64 ms, bool timer) {
void InputField::step_placeholderShift(float64 ms, bool timer) {
float64 dt = ms / _st.duration;
if (dt >= 1) {
_a_placeholderShift.stop();
a_placeholderLeft.finish();
a_placeholderOpacity.finish();
finishPlaceholderAnimation();
} else {
a_placeholderLeft.update(dt, anim::linear);
a_placeholderOpacity.update(dt, anim::linear);
@ -1899,6 +1906,13 @@ void InputField::step_placeholderShift(float64 ms, bool timer) {
if (timer) update();
}
void InputField::finishPlaceholderAnimation() {
_a_placeholderShift.stop();
a_placeholderLeft.finish();
a_placeholderOpacity.finish();
update();
}
void InputField::step_border(float64 ms, bool timer) {
float64 dt = ms / _st.duration;
if (dt >= 1) {
@ -1928,7 +1942,7 @@ void InputField::setPlaceholderHidden(bool forcePlaceholderHidden) {
updatePlaceholder();
}
QMimeData *InputField::InputFieldInner::createMimeDataFromSelection() const {
QMimeData *InputField::Inner::createMimeDataFromSelection() const {
QMimeData *result = new QMimeData();
QTextCursor c(textCursor());
int32 start = c.selectionStart(), end = c.selectionEnd();
@ -1942,7 +1956,7 @@ void InputField::customUpDown(bool custom) {
_customUpDown = custom;
}
void InputField::InputFieldInner::keyPressEvent(QKeyEvent *e) {
void InputField::Inner::keyPressEvent(QKeyEvent *e) {
bool shift = e->modifiers().testFlag(Qt::ShiftModifier), alt = e->modifiers().testFlag(Qt::AltModifier);
bool macmeta = (cPlatform() == dbipMac || cPlatform() == dbipMacOld) && e->modifiers().testFlag(Qt::ControlModifier) && !e->modifiers().testFlag(Qt::MetaModifier) && !e->modifiers().testFlag(Qt::AltModifier);
bool ctrl = e->modifiers().testFlag(Qt::ControlModifier) || e->modifiers().testFlag(Qt::MetaModifier), ctrlGood = true;
@ -1979,36 +1993,41 @@ void InputField::InputFieldInner::keyPressEvent(QKeyEvent *e) {
}
#endif // Q_OS_MAC
} else {
QTextCursor tc(textCursor());
auto oldCursorPosition = textCursor().position();
if (enter && ctrl) {
e->setModifiers(e->modifiers() & ~Qt::ControlModifier);
}
QTextEdit::keyPressEvent(e);
if (tc == textCursor()) {
auto currentCursor = textCursor();
if (textCursor().position() == oldCursorPosition) {
bool check = false;
if (e->key() == Qt::Key_PageUp || e->key() == Qt::Key_Up) {
tc.movePosition(QTextCursor::Start, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
oldCursorPosition = currentCursor.position();
currentCursor.movePosition(QTextCursor::Start, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
check = true;
} else if (e->key() == Qt::Key_PageDown || e->key() == Qt::Key_Down) {
tc.movePosition(QTextCursor::End, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
oldCursorPosition = currentCursor.position();
currentCursor.movePosition(QTextCursor::End, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
check = true;
} else if (e->key() == Qt::Key_Left || e->key() == Qt::Key_Right || e->key() == Qt::Key_Backspace) {
e->ignore();
}
if (check) {
if (tc == textCursor()) {
if (oldCursorPosition == currentCursor.position()) {
e->ignore();
} else {
setTextCursor(tc);
setTextCursor(currentCursor);
}
}
}
}
}
void InputField::InputFieldInner::paintEvent(QPaintEvent *e) {
void InputField::Inner::paintEvent(QPaintEvent *e) {
return QTextEdit::paintEvent(e);
}
void InputField::InputFieldInner::contextMenuEvent(QContextMenuEvent *e) {
void InputField::Inner::contextMenuEvent(QContextMenuEvent *e) {
if (QMenu *menu = createStandardContextMenu()) {
(new PopupMenu(menu))->popup(e->globalPos());
}
@ -2167,9 +2186,6 @@ void MaskedInputField::paintEvent(QPaintEvent *e) {
p.fillRect(0, height() - _st.borderActive, width(), _st.borderActive, a_borderFg.current());
p.setOpacity(1);
}
if (_st.iconSprite.pxWidth()) {
p.drawSpriteLeft(_st.iconPosition, width(), _st.iconSprite);
}
p.setClipRect(r);
paintPlaceholder(p);

View file

@ -262,10 +262,9 @@ private:
bool heightAutoupdated();
void checkContentHeight();
friend class InputAreaInner;
class InputAreaInner : public QTextEdit {
class Inner : public QTextEdit {
public:
InputAreaInner(InputArea *parent);
Inner(InputArea *parent);
QVariant loadResource(int type, const QUrl &name) override;
@ -286,6 +285,7 @@ private:
friend class InputArea;
};
friend class Inner;
void focusInInner();
void focusOutInner();
@ -294,7 +294,7 @@ private:
void startBorderAnimation();
InputAreaInner _inner;
Inner _inner;
QString _oldtext;
@ -343,6 +343,7 @@ public:
}
void updatePlaceholder();
void setPlaceholderHidden(bool forcePlaceholderHidden);
void finishPlaceholderAnimation();
void step_placeholderFg(float64 ms, bool timer);
void step_placeholderShift(float64 ms, bool timer);
@ -431,10 +432,9 @@ private:
int32 _maxLength;
bool _forcePlaceholderHidden = false;
friend class InputFieldInner;
class InputFieldInner : public QTextEdit {
class Inner : public QTextEdit {
public:
InputFieldInner(InputField *parent);
Inner(InputField *parent);
QVariant loadResource(int type, const QUrl &name) override;
@ -455,6 +455,7 @@ private:
friend class InputField;
};
friend class Inner;
void focusInInner();
void focusOutInner();
@ -463,7 +464,7 @@ private:
void startBorderAnimation();
InputFieldInner _inner;
Inner _inner;
QString _oldtext;

View file

@ -134,7 +134,7 @@ void MonoIcon::ensureLoaded() const {
if (size > sizeTag.size() && !memcmp(data, sizeTag.data(), sizeTag.size())) {
size -= sizeTag.size();
data += sizeTag.size();
QByteArray baForStream(reinterpret_cast<const char*>(data), size);
auto baForStream = QByteArray::fromRawData(reinterpret_cast<const char*>(data), size);
QBuffer buffer(&baForStream);
buffer.open(QIODevice::ReadOnly);
@ -207,7 +207,7 @@ int Icon::width() const {
int Icon::height() const {
if (_height < 0) {
_height = 0;
for_const (const auto &part, _parts) {
for_const (auto &part, _parts) {
accumulate_max(_height, part.offset().x() + part.height());
}
}

View file

@ -105,6 +105,9 @@ public:
return std_::make_unique<Icon>(ColoredCopy { *this, colors });
}
bool empty() const {
return _parts.empty();
}
void paint(QPainter &p, const QPoint &pos, int outerw) const;
void paint(QPainter &p, int x, int y, int outerw) const {
paint(p, QPoint(x, y), outerw);
@ -115,6 +118,9 @@ public:
void fill(QPainter &p, const QRect &rect) const;
int width() const;
int height() const;
QSize size() const {
return QSize(width(), height());
}
private:
struct ColoredCopy {

View file

@ -21,10 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#pragma once
#include "ui/widgets/continuous_slider.h"
namespace style {
struct FilledSlider;
} // namespace style
#include "styles/style_widgets.h"
namespace Ui {

View file

@ -21,10 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#pragma once
#include "ui/widgets/continuous_slider.h"
namespace style {
struct MediaSlider;
} // namespace style
#include "styles/style_widgets.h"
namespace Ui {

View file

@ -0,0 +1,856 @@
/*
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-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "ui/widgets/multi_select.h"
#include "styles/style_widgets.h"
#include "ui/buttons/icon_button.h"
#include "lang.h"
namespace Ui {
namespace {
constexpr int kWideScale = 3;
} // namespace
class MultiSelect::Inner::Item {
public:
Item(const style::MultiSelectItem &st, uint64 id, const QString &text, const style::color &color, PaintRoundImage paintRoundImage);
uint64 id() const {
return _id;
}
int getWidth() const {
return _width;
}
QRect rect() const {
return QRect(_x, _y, _width, _st.height);
}
bool isOverDelete() const {
return _overDelete;
}
void setActive(bool active) {
_active = active;
}
void setPosition(int x, int y, int outerWidth, int maxVisiblePadding);
QRect paintArea(int outerWidth) const;
void setUpdateCallback(base::lambda_wrap<void()> updateCallback) {
_updateCallback = std_::move(updateCallback);
}
void setText(const QString &text);
void paint(Painter &p, int outerWidth, uint64 ms);
void mouseMoveEvent(QPoint point);
void leaveEvent();
void showAnimated() {
setVisibleAnimated(true);
}
void hideAnimated() {
setVisibleAnimated(false);
}
bool hideFinished() const {
return (_hiding && !_visibility.animating());
}
private:
void setOver(bool over);
void paintOnce(Painter &p, int x, int y, int outerWidth, uint64 ms);
void paintDeleteButton(Painter &p, int x, int y, int outerWidth, float64 overOpacity);
bool paintCached(Painter &p, int x, int y, int outerWidth);
void prepareCache();
void setVisibleAnimated(bool visible);
const style::MultiSelectItem &_st;
uint64 _id;
struct SlideAnimation {
SlideAnimation(base::lambda_wrap<void()> updateCallback, int fromX, int toX, int y, float64 duration)
: fromX(fromX)
, toX(toX)
, y(y) {
x.start(std_::move(updateCallback), fromX, toX, duration);
}
IntAnimation x;
int fromX, toX;
int y;
};
std_::vector_of_moveable<SlideAnimation> _copies;
int _x = -1;
int _y = -1;
int _width = 0;
Text _text;
const style::color &_color;
bool _over = false;
QPixmap _cache;
FloatAnimation _visibility;
FloatAnimation _overOpacity;
bool _overDelete = false;
bool _active = false;
PaintRoundImage _paintRoundImage;
base::lambda_wrap<void()> _updateCallback;
bool _hiding = false;
};
MultiSelect::Inner::Item::Item(const style::MultiSelectItem &st, uint64 id, const QString &text, const style::color &color, PaintRoundImage paintRoundImage)
: _st(st)
, _id(id)
, _color(color)
, _paintRoundImage(std_::move(paintRoundImage)) {
setText(text);
}
void MultiSelect::Inner::Item::setText(const QString &text) {
_text.setText(_st.font, text, _textNameOptions);
_width = _st.height + _st.padding.left() + _text.maxWidth() + _st.padding.right();
accumulate_min(_width, _st.maxWidth);
}
void MultiSelect::Inner::Item::paint(Painter &p, int outerWidth, uint64 ms) {
if (!_cache.isNull() && !_visibility.animating(ms)) {
if (_hiding) {
return;
} else {
_cache = QPixmap();
}
}
if (_copies.empty()) {
paintOnce(p, _x, _y, outerWidth, ms);
} else {
for (auto i = _copies.begin(), e = _copies.end(); i != e;) {
auto x = i->x.current(getms(), _x);
auto y = i->y;
auto animating = i->x.animating();
if (animating || (y == _y)) {
paintOnce(p, x, y, outerWidth, ms);
}
if (animating) {
++i;
} else {
i = _copies.erase(i);
e = _copies.end();
}
}
}
}
void MultiSelect::Inner::Item::paintOnce(Painter &p, int x, int y, int outerWidth, uint64 ms) {
if (!_cache.isNull()) {
paintCached(p, x, y, outerWidth);
return;
}
auto radius = _st.height / 2;
auto inner = rtlrect(x + radius, y, _width - radius, _st.height, outerWidth);
auto clipEnabled = p.hasClipping();
auto clip = clipEnabled ? p.clipRegion() : QRegion();
p.setRenderHint(QPainter::HighQualityAntialiasing);
p.setClipRect(inner);
p.setPen(Qt::NoPen);
p.setBrush(_active ? _st.textActiveBg : _st.textBg);
p.drawRoundedRect(rtlrect(x, y, _width, _st.height, outerWidth), radius, radius);
if (clipEnabled) {
p.setClipRegion(clip);
} else {
p.setClipping(false);
}
p.setRenderHint(QPainter::HighQualityAntialiasing, false);
auto overOpacity = _overOpacity.current(ms, _over ? 1. : 0.);
if (overOpacity < 1.) {
_paintRoundImage(p, x, y, outerWidth, _st.height);
}
if (overOpacity > 0.) {
paintDeleteButton(p, x, y, outerWidth, overOpacity);
}
auto textLeft = _st.height + _st.padding.left();
auto textWidth = _width - textLeft - _st.padding.right();
p.setPen(_active ? _st.textActiveFg : _st.textFg);
_text.drawLeftElided(p, x + textLeft, y + _st.padding.top(), textWidth, outerWidth);
}
void MultiSelect::Inner::Item::paintDeleteButton(Painter &p, int x, int y, int outerWidth, float64 overOpacity) {
p.setOpacity(overOpacity);
p.setRenderHint(QPainter::HighQualityAntialiasing);
p.setPen(Qt::NoPen);
p.setBrush(_color);
p.drawEllipse(rtlrect(x, y, _st.height, _st.height, outerWidth));
auto deleteScale = overOpacity + _st.minScale * (1. - overOpacity);
auto deleteSkip = deleteScale * _st.deleteLeft + (1. - deleteScale) * (_st.height / 2);
auto sqrt2 = sqrt(2.);
auto deleteLeft = rtlpoint(x + deleteSkip, 0, outerWidth).x() + 0.;
auto deleteTop = y + deleteSkip + 0.;
auto deleteWidth = _st.height - 2 * deleteSkip;
auto deleteHeight = _st.height - 2 * deleteSkip;
auto deleteStroke = _st.deleteStroke / sqrt2;
QPointF pathDelete[] = {
{ deleteLeft, deleteTop + deleteStroke },
{ deleteLeft + deleteStroke, deleteTop },
{ deleteLeft + (deleteWidth / 2.), deleteTop + (deleteHeight / 2.) - deleteStroke },
{ deleteLeft + deleteWidth - deleteStroke, deleteTop },
{ deleteLeft + deleteWidth, deleteTop + deleteStroke },
{ deleteLeft + (deleteWidth / 2.) + deleteStroke, deleteTop + (deleteHeight / 2.) },
{ deleteLeft + deleteWidth, deleteTop + deleteHeight - deleteStroke },
{ deleteLeft + deleteWidth - deleteStroke, deleteTop + deleteHeight },
{ deleteLeft + (deleteWidth / 2.), deleteTop + (deleteHeight / 2.) + deleteStroke },
{ deleteLeft + deleteStroke, deleteTop + deleteHeight },
{ deleteLeft, deleteTop + deleteHeight - deleteStroke },
{ deleteLeft + (deleteWidth / 2.) - deleteStroke, deleteTop + (deleteHeight / 2.) },
};
if (overOpacity < 1.) {
auto alpha = -(overOpacity - 1.) * M_PI_2;
auto cosalpha = cos(alpha);
auto sinalpha = sin(alpha);
auto shiftx = deleteLeft + (deleteWidth / 2.);
auto shifty = deleteTop + (deleteHeight / 2.);
for (auto &point : pathDelete) {
auto x = point.x() - shiftx;
auto y = point.y() - shifty;
point.setX(shiftx + x * cosalpha - y * sinalpha);
point.setY(shifty + y * cosalpha + x * sinalpha);
}
}
QPainterPath path;
path.moveTo(pathDelete[0]);
for (int i = 1; i != base::array_size(pathDelete); ++i) {
path.lineTo(pathDelete[i]);
}
p.fillPath(path, _st.deleteFg);
p.setRenderHint(QPainter::HighQualityAntialiasing, false);
p.setOpacity(1.);
}
bool MultiSelect::Inner::Item::paintCached(Painter &p, int x, int y, int outerWidth) {
auto opacity = _visibility.current(_hiding ? 0. : 1.);
auto scale = opacity + _st.minScale * (1. - opacity);
auto height = opacity * _cache.height() / _cache.devicePixelRatio();
auto width = opacity * _cache.width() / _cache.devicePixelRatio();
p.setOpacity(opacity);
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
p.drawPixmap(rtlrect(x + (_width - width) / 2., y + (_st.height - height) / 2., width, height, outerWidth), _cache);
p.setRenderHint(QPainter::SmoothPixmapTransform, false);
p.setOpacity(1.);
return true;
}
void MultiSelect::Inner::Item::mouseMoveEvent(QPoint point) {
if (!_cache.isNull()) return;
_overDelete = QRect(0, 0, _st.height, _st.height).contains(point);
setOver(true);
}
void MultiSelect::Inner::Item::leaveEvent() {
_overDelete = false;
setOver(false);
}
void MultiSelect::Inner::Item::setPosition(int x, int y, int outerWidth, int maxVisiblePadding) {
if (_x >= 0 && _y >= 0 && (_x != x || _y != y)) {
// Make an animation if it is not the first setPosition().
auto found = false;
auto leftHidden = -_width - maxVisiblePadding;
auto rightHidden = outerWidth + maxVisiblePadding;
for (auto i = _copies.begin(), e = _copies.end(); i != e;) {
if (i->x.animating()) {
if (i->y == y) {
i->x.start(_updateCallback, i->toX, x, _st.duration);
found = true;
} else {
i->x.start(_updateCallback, i->fromX, (i->toX > i->fromX) ? rightHidden : leftHidden, _st.duration);
}
++i;
} else {
i = _copies.erase(i);
e = _copies.end();
}
}
if (_copies.empty()) {
if (_y == y) {
auto copy = SlideAnimation(_updateCallback, _x, x, _y, _st.duration);
_copies.push_back(std_::move(copy));
} else {
auto copyHiding = SlideAnimation(_updateCallback, _x, (y > _y) ? rightHidden : leftHidden, _y, _st.duration);
_copies.push_back(std_::move(copyHiding));
auto copyShowing = SlideAnimation(_updateCallback, (y > _y) ? leftHidden : rightHidden, x, y, _st.duration);
_copies.push_back(std_::move(copyShowing));
}
} else if (!found) {
auto copy = SlideAnimation(_updateCallback, (y > _y) ? leftHidden : rightHidden, x, y, _st.duration);
_copies.push_back(std_::move(copy));
}
}
_x = x;
_y = y;
}
QRect MultiSelect::Inner::Item::paintArea(int outerWidth) const {
if (_copies.empty()) {
return rect();
}
auto yMin = 0, yMax = 0;
for_const (auto &copy, _copies) {
accumulate_max(yMax, copy.y);
if (yMin) {
accumulate_min(yMin, copy.y);
} else {
yMin = copy.y;
}
}
return QRect(0, yMin, outerWidth, yMax - yMin + _st.height);
}
void MultiSelect::Inner::Item::prepareCache() {
if (!_cache.isNull()) return;
t_assert(!_visibility.animating());
auto cacheWidth = _width * kWideScale * cIntRetinaFactor();
auto cacheHeight = _st.height * kWideScale * cIntRetinaFactor();
auto data = QImage(cacheWidth, cacheHeight, QImage::Format_ARGB32_Premultiplied);
data.fill(Qt::transparent);
data.setDevicePixelRatio(cRetinaFactor());
{
Painter p(&data);
paintOnce(p, _width * (kWideScale - 1) / 2, _st.height * (kWideScale - 1) / 2, cacheWidth, getms());
}
_cache = App::pixmapFromImageInPlace(std_::move(data));
}
void MultiSelect::Inner::Item::setVisibleAnimated(bool visible) {
_hiding = !visible;
prepareCache();
auto from = visible ? 0. : 1.;
auto to = visible ? 1. : 0.;
auto transition = visible ? anim::bumpy<1125, 1000> : anim::linear;
_visibility.start(_updateCallback, from, to, _st.duration, transition);
}
void MultiSelect::Inner::Item::setOver(bool over) {
if (over != _over) {
_over = over;
_overOpacity.start(_updateCallback, _over ? 0. : 1., _over ? 1. : 0., _st.duration);
}
}
MultiSelect::MultiSelect(QWidget *parent, const style::MultiSelect &st, const QString &placeholder) : TWidget(parent)
, _st(st)
, _scroll(this, _st.scroll)
, _inner(this, st, placeholder, [this](int activeTop, int activeBottom) { scrollTo(activeTop, activeBottom); }) {
_scroll->setOwnedWidget(_inner);
_scroll->installEventFilter(this);
_inner->setResizedCallback([this](int innerHeightDelta) {
auto newHeight = resizeGetHeight(width());
if (innerHeightDelta > 0) {
_scroll->scrollToY(_scroll->scrollTop() + innerHeightDelta);
}
if (newHeight != height()) {
resize(width(), newHeight);
if (_resizedCallback) {
_resizedCallback();
}
}
});
_inner->setQueryChangedCallback([this](const QString &query) {
_scroll->scrollToY(_scroll->scrollTopMax());
if (_queryChangedCallback) {
_queryChangedCallback(query);
}
});
setAttribute(Qt::WA_OpaquePaintEvent);
}
bool MultiSelect::eventFilter(QObject *o, QEvent *e) {
if (o == _scroll && e->type() == QEvent::KeyPress) {
e->ignore();
return true;
}
return false;
}
void MultiSelect::scrollTo(int activeTop, int activeBottom) {
auto scrollTop = _scroll->scrollTop();
auto scrollHeight = _scroll->height();
auto scrollBottom = scrollTop + scrollHeight;
if (scrollTop > activeTop) {
_scroll->scrollToY(activeTop);
} else if (scrollBottom < activeBottom) {
_scroll->scrollToY(activeBottom - scrollHeight);
}
}
void MultiSelect::setQueryChangedCallback(base::lambda_unique<void(const QString &query)> callback) {
_queryChangedCallback = std_::move(callback);
}
void MultiSelect::setSubmittedCallback(base::lambda_unique<void(bool ctrlShiftEnter)> callback) {
_inner->setSubmittedCallback(std_::move(callback));
}
void MultiSelect::setResizedCallback(base::lambda_unique<void()> callback) {
_resizedCallback = std_::move(callback);
}
void MultiSelect::setInnerFocus() {
if (_inner->setInnerFocus()) {
_scroll->scrollToY(_scroll->scrollTopMax());
}
}
void MultiSelect::clearQuery() {
_inner->clearQuery();
}
QString MultiSelect::getQuery() const {
return _inner->getQuery();
}
void MultiSelect::addItem(uint64 itemId, const QString &text, const style::color &color, PaintRoundImage paintRoundImage, AddItemWay way) {
_inner->addItem(std_::make_unique<Inner::Item>(_st.item, itemId, text, color, std_::move(paintRoundImage)), way);
}
void MultiSelect::setItemRemovedCallback(base::lambda_unique<void(uint64 itemId)> callback) {
_inner->setItemRemovedCallback(std_::move(callback));
}
void MultiSelect::removeItem(uint64 itemId) {
_inner->removeItem(itemId);
}
int MultiSelect::resizeGetHeight(int newWidth) {
if (newWidth != _inner->width()) {
_inner->resizeToWidth(newWidth);
}
auto newHeight = qMin(_inner->height(), _st.maxHeight);
_scroll->setGeometryToLeft(0, 0, newWidth, newHeight);
return newHeight;
}
MultiSelect::Inner::Inner(QWidget *parent, const style::MultiSelect &st, const QString &placeholder, ScrollCallback callback) : ScrolledWidget(parent)
, _st(st)
, _scrollCallback(std_::move(callback))
, _field(this, _st.field, placeholder)
, _cancel(this, _st.fieldCancel) {
_field->customUpDown(true);
connect(_field, SIGNAL(focused()), this, SLOT(onFieldFocused()));
connect(_field, SIGNAL(changed()), this, SLOT(onQueryChanged()));
connect(_field, SIGNAL(submitted(bool)), this, SLOT(onSubmitted(bool)));
_cancel->hide();
_cancel->setClickedCallback([this] {
clearQuery();
_field->setFocus();
});
setMouseTracking(true);
}
void MultiSelect::Inner::onQueryChanged() {
auto query = getQuery();
_cancel->setVisible(!query.isEmpty());
updateFieldGeometry();
if (_queryChangedCallback) {
_queryChangedCallback(query);
}
}
QString MultiSelect::Inner::getQuery() const {
return _field->getLastText().trimmed();
}
bool MultiSelect::Inner::setInnerFocus() {
if (_active >= 0) {
setFocus();
} else if (!_field->hasFocus()) {
_field->setFocus();
return true;
}
return false;
}
void MultiSelect::Inner::clearQuery() {
_field->setText(QString());
}
void MultiSelect::Inner::setQueryChangedCallback(base::lambda_unique<void(const QString &query)> callback) {
_queryChangedCallback = std_::move(callback);
}
void MultiSelect::Inner::setSubmittedCallback(base::lambda_unique<void(bool ctrlShiftEnter)> callback) {
_submittedCallback = std_::move(callback);
}
void MultiSelect::Inner::updateFieldGeometry() {
auto fieldFinalWidth = _fieldWidth;
if (!_cancel->isHidden()) {
fieldFinalWidth -= _st.fieldCancelSkip;
}
_field->resizeToWidth(fieldFinalWidth);
_field->moveToLeft(_st.padding.left() + _fieldLeft, _st.padding.top() + _fieldTop);
}
void MultiSelect::Inner::updateHasAnyItems(bool hasAnyItems) {
_field->setPlaceholderHidden(hasAnyItems);
updateCursor();
_iconOpacity.start([this] {
rtlupdate(_st.padding.left(), _st.padding.top(), _st.fieldIcon.width(), _st.fieldIcon.height());
}, hasAnyItems ? 1. : 0., hasAnyItems ? 0. : 1., _st.item.duration);
}
void MultiSelect::Inner::updateCursor() {
setCursor(_items.empty() ? style::cur_text : (_overDelete ? style::cur_pointer : style::cur_default));
}
void MultiSelect::Inner::setActiveItem(int active, ChangeActiveWay skipSetFocus) {
if (_active == active) return;
if (_active >= 0) {
t_assert(_active < _items.size());
_items[_active]->setActive(false);
}
_active = active;
if (_active >= 0) {
t_assert(_active < _items.size());
_items[_active]->setActive(true);
}
if (skipSetFocus != ChangeActiveWay::SkipSetFocus) {
setInnerFocus();
}
if (_scrollCallback) {
auto rect = (_active >= 0) ? _items[_active]->rect() : _field->geometry().translated(-_st.padding.left(), -_st.padding.top());
_scrollCallback(rect.y(), rect.y() + rect.height() + _st.padding.top() + _st.padding.bottom());
}
update();
}
void MultiSelect::Inner::setActiveItemPrevious() {
if (_active > 0) {
setActiveItem(_active - 1);
} else if (_active < 0 && !_items.empty()) {
setActiveItem(_items.size() - 1);
}
}
void MultiSelect::Inner::setActiveItemNext() {
if (_active >= 0 && _active + 1 < _items.size()) {
setActiveItem(_active + 1);
} else {
setActiveItem(-1);
}
}
int MultiSelect::Inner::resizeGetHeight(int newWidth) {
computeItemsGeometry(newWidth);
updateFieldGeometry();
auto cancelLeft = _fieldLeft + _fieldWidth + _st.padding.right() - _cancel->width();
auto cancelTop = _fieldTop - _st.padding.top();
_cancel->moveToLeft(_st.padding.left() + cancelLeft, _st.padding.top() + cancelTop);
return _field->y() + _field->height() + _st.padding.bottom();
}
void MultiSelect::Inner::paintEvent(QPaintEvent *e) {
Painter p(this);
auto ms = getms();
_height.step(ms);
_iconOpacity.step(ms);
auto paintRect = e->rect();
p.fillRect(paintRect, st::windowBg);
auto offset = QPoint(rtl() ? _st.padding.right() : _st.padding.left(), _st.padding.top());
p.translate(offset);
paintRect.translate(-offset);
auto outerWidth = width() - _st.padding.left() - _st.padding.right();
auto iconOpacity = _iconOpacity.current(_items.empty() ? 1. : 0.);
if (iconOpacity > 0.) {
p.setOpacity(iconOpacity);
_st.fieldIcon.paint(p, 0, 0, outerWidth);
p.setOpacity(1.);
}
auto checkRect = myrtlrect(paintRect);
auto paintMargins = itemPaintMargins();
for (auto i = _removingItems.begin(), e = _removingItems.end(); i != e;) {
auto item = *i;
auto itemRect = item->paintArea(outerWidth);
itemRect = itemRect.marginsAdded(paintMargins);
if (checkRect.intersects(itemRect)) {
item->paint(p, outerWidth, ms);
}
if (item->hideFinished()) {
i = _removingItems.erase(i);
e = _removingItems.end();
} else {
++i;
}
}
for_const (auto item, _items) {
auto itemRect = item->paintArea(outerWidth);
itemRect = itemRect.marginsAdded(paintMargins);
if (checkRect.y() + checkRect.height() <= itemRect.y()) {
break;
} else if (checkRect.intersects(itemRect)) {
item->paint(p, outerWidth, ms);
}
}
}
QMargins MultiSelect::Inner::itemPaintMargins() const {
return {
qMax(_st.itemSkip, _st.padding.left()),
_st.itemSkip,
qMax(_st.itemSkip, _st.padding.right()),
_st.itemSkip,
};
}
void MultiSelect::Inner::leaveEvent(QEvent *e) {
clearSelection();
}
void MultiSelect::Inner::mouseMoveEvent(QMouseEvent *e) {
updateSelection(e->pos());
}
void MultiSelect::Inner::keyPressEvent(QKeyEvent *e) {
if (_active >= 0) {
t_assert(_active < _items.size());
if (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace) {
auto itemId = _items[_active]->id();
setActiveItemNext();
removeItem(itemId);
} else if (e->key() == Qt::Key_Left) {
setActiveItemPrevious();
} else if (e->key() == Qt::Key_Right) {
setActiveItemNext();
} else if (e->key() == Qt::Key_Escape) {
setActiveItem(-1);
} else {
e->ignore();
}
} else if (e->key() == Qt::Key_Left || e->key() == Qt::Key_Backspace) {
setActiveItemPrevious();
} else {
e->ignore();
}
}
void MultiSelect::Inner::onFieldFocused() {
setActiveItem(-1, ChangeActiveWay::SkipSetFocus);
}
void MultiSelect::Inner::updateSelection(QPoint mousePosition) {
auto point = myrtlpoint(mousePosition) - QPoint(_st.padding.left(), _st.padding.right());
auto selected = -1;
for (auto i = 0, size = _items.size(); i != size; ++i) {
auto itemRect = _items[i]->rect();
if (itemRect.y() > point.y()) {
break;
} else if (itemRect.contains(point)) {
point -= itemRect.topLeft();
selected = i;
break;
}
}
if (_selected != selected) {
if (_selected >= 0) {
t_assert(_selected < _items.size());
_items[_selected]->leaveEvent();
}
_selected = selected;
update();
}
auto overDelete = false;
if (_selected >= 0) {
_items[_selected]->mouseMoveEvent(point);
overDelete = _items[_selected]->isOverDelete();
}
if (_overDelete != overDelete) {
_overDelete = overDelete;
updateCursor();
}
}
void MultiSelect::Inner::mousePressEvent(QMouseEvent *e) {
if (_overDelete) {
t_assert(_selected >= 0);
t_assert(_selected < _items.size());
removeItem(_items[_selected]->id());
} else if (_selected >= 0) {
setActiveItem(_selected);
} else {
setInnerFocus();
}
}
void MultiSelect::Inner::addItem(std_::unique_ptr<Item> item, AddItemWay way) {
auto wasEmpty = _items.empty();
item->setUpdateCallback([this, item = item.get()] {
auto itemRect = item->paintArea(width() - _st.padding.left() - _st.padding.top());
itemRect = itemRect.translated(_st.padding.left(), _st.padding.top());
itemRect = itemRect.marginsAdded(itemPaintMargins());
rtlupdate(itemRect);
});
_items.push_back(item.release());
updateItemsGeometry();
if (wasEmpty) {
updateHasAnyItems(true);
}
if (way != AddItemWay::SkipAnimation) {
_items.back()->showAnimated();
} else {
_field->finishPlaceholderAnimation();
finishHeightAnimation();
}
}
void MultiSelect::Inner::computeItemsGeometry(int newWidth) {
newWidth -= _st.padding.left() + _st.padding.right();
auto itemLeft = 0;
auto itemTop = 0;
auto widthLeft = newWidth;
auto maxVisiblePadding = qMax(_st.padding.left(), _st.padding.right());
for_const (auto item, _items) {
auto itemWidth = item->getWidth();
t_assert(itemWidth <= newWidth);
if (itemWidth > widthLeft) {
itemLeft = 0;
itemTop += _st.item.height + _st.itemSkip;
widthLeft = newWidth;
}
item->setPosition(itemLeft, itemTop, newWidth, maxVisiblePadding);
itemLeft += itemWidth + _st.itemSkip;
widthLeft -= itemWidth + _st.itemSkip;
}
auto fieldMinWidth = _st.fieldMinWidth + _st.fieldCancelSkip;
t_assert(fieldMinWidth <= newWidth);
if (fieldMinWidth > widthLeft) {
_fieldLeft = 0;
_fieldTop = itemTop + _st.item.height + _st.itemSkip;
} else {
_fieldLeft = itemLeft + (_items.empty() ? _st.fieldIconSkip : 0);
_fieldTop = itemTop;
}
_fieldWidth = newWidth - _fieldLeft;
}
void MultiSelect::Inner::updateItemsGeometry() {
computeItemsGeometry(width());
updateFieldGeometry();
auto newHeight = resizeGetHeight(width());
if (newHeight == _newHeight) return;
_newHeight = newHeight;
_height.start([this] { updateHeightStep(); }, height(), _newHeight, _st.item.duration);
}
void MultiSelect::Inner::updateHeightStep() {
auto newHeight = _height.current(_newHeight);
if (auto heightDelta = newHeight - height()) {
resize(width(), newHeight);
if (_resizedCallback) {
_resizedCallback(heightDelta);
}
update();
}
}
void MultiSelect::Inner::finishHeightAnimation() {
_height.finish();
updateHeightStep();
}
void MultiSelect::Inner::setItemText(uint64 itemId, const QString &text) {
for (int i = 0, count = _items.size(); i != count; ++i) {
auto item = _items[i];
if (item->id() == itemId) {
item->setText(text);
updateItemsGeometry();
return;
}
}
}
void MultiSelect::Inner::setItemRemovedCallback(base::lambda_unique<void(uint64 itemId)> callback) {
_itemRemovedCallback = std_::move(callback);
}
void MultiSelect::Inner::setResizedCallback(base::lambda_unique<void(int heightDelta)> callback) {
_resizedCallback = std_::move(callback);
}
void MultiSelect::Inner::removeItem(uint64 itemId) {
for (int i = 0, count = _items.size(); i != count; ++i) {
auto item = _items[i];
if (item->id() == itemId) {
clearSelection();
_items.removeAt(i);
if (_active == i) {
_active = -1;
} else if (_active > i) {
--_active;
}
_removingItems.insert(item);
item->hideAnimated();
updateItemsGeometry();
if (_items.empty()) {
updateHasAnyItems(false);
}
auto point = QCursor::pos();
if (auto parent = parentWidget()) {
if (parent->rect().contains(parent->mapFromGlobal(point))) {
updateSelection(mapFromGlobal(point));
}
}
break;
}
}
if (_itemRemovedCallback) {
_itemRemovedCallback(itemId);
}
setInnerFocus();
}
MultiSelect::Inner::~Inner() {
for (auto item : base::take(_items)) {
delete item;
}
for (auto item : base::take(_removingItems)) {
delete item;
}
}
} // namespace Ui

View file

@ -0,0 +1,169 @@
/*
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-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "styles/style_widgets.h"
class InputField;
namespace Ui {
class IconButton;
class MultiSelect : public TWidget {
public:
MultiSelect(QWidget *parent, const style::MultiSelect &st, const QString &placeholder = QString());
QString getQuery() const;
void setInnerFocus();
void clearQuery();
void setQueryChangedCallback(base::lambda_unique<void(const QString &query)> callback);
void setSubmittedCallback(base::lambda_unique<void(bool ctrlShiftEnter)> callback);
void setResizedCallback(base::lambda_unique<void()> callback);
enum class AddItemWay {
Default,
SkipAnimation,
};
using PaintRoundImage = base::lambda_unique<void(Painter &p, int x, int y, int outerWidth, int size)>;
void addItem(uint64 itemId, const QString &text, const style::color &color, PaintRoundImage paintRoundImage, AddItemWay way = AddItemWay::Default);
void setItemText(uint64 itemId, const QString &text);
void setItemRemovedCallback(base::lambda_unique<void(uint64 itemId)> callback);
void removeItem(uint64 itemId);
protected:
int resizeGetHeight(int newWidth) override;
bool eventFilter(QObject *o, QEvent *e) override;
private:
void scrollTo(int activeTop, int activeBottom);
const style::MultiSelect &_st;
ChildWidget<ScrollArea> _scroll;
class Inner;
ChildWidget<Inner> _inner;
base::lambda_unique<void()> _resizedCallback;
base::lambda_unique<void(const QString &query)> _queryChangedCallback;
};
// This class is hold in header because it requires Qt preprocessing.
class MultiSelect::Inner : public ScrolledWidget {
Q_OBJECT
public:
using ScrollCallback = base::lambda_unique<void(int activeTop, int activeBottom)>;
Inner(QWidget *parent, const style::MultiSelect &st, const QString &placeholder, ScrollCallback callback);
QString getQuery() const;
bool setInnerFocus();
void clearQuery();
void setQueryChangedCallback(base::lambda_unique<void(const QString &query)> callback);
void setSubmittedCallback(base::lambda_unique<void(bool ctrlShiftEnter)> callback);
class Item;
void addItem(std_::unique_ptr<Item> item, AddItemWay way);
void setItemText(uint64 itemId, const QString &text);
void setItemRemovedCallback(base::lambda_unique<void(uint64 itemId)> callback);
void removeItem(uint64 itemId);
void setResizedCallback(base::lambda_unique<void(int heightDelta)> callback);
~Inner();
protected:
int resizeGetHeight(int newWidth) override;
void paintEvent(QPaintEvent *e) override;
void leaveEvent(QEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
private slots:
void onQueryChanged();
void onSubmitted(bool ctrlShiftEnter) {
if (_submittedCallback) {
_submittedCallback(ctrlShiftEnter);
}
}
void onFieldFocused();
private:
void computeItemsGeometry(int newWidth);
void updateItemsGeometry();
void updateFieldGeometry();
void updateHasAnyItems(bool hasAnyItems);
void updateSelection(QPoint mousePosition);
void clearSelection() {
updateSelection(QPoint(-1, -1));
}
void updateCursor();
void updateHeightStep();
void finishHeightAnimation();
enum class ChangeActiveWay {
Default,
SkipSetFocus,
};
void setActiveItem(int active, ChangeActiveWay skipSetFocus = ChangeActiveWay::Default);
void setActiveItemPrevious();
void setActiveItemNext();
QMargins itemPaintMargins() const;
const style::MultiSelect &_st;
FloatAnimation _iconOpacity;
ScrollCallback _scrollCallback;
using Items = QList<Item*>;
Items _items;
using RemovingItems = OrderedSet<Item*>;
RemovingItems _removingItems;
int _selected = -1;
int _active = -1;
bool _overDelete = false;
int _fieldLeft = 0;
int _fieldTop = 0;
int _fieldWidth = 0;
ChildWidget<InputField> _field;
ChildWidget<Ui::IconButton> _cancel;
int _newHeight = 0;
IntAnimation _height;
base::lambda_unique<void(const QString &query)> _queryChangedCallback;
base::lambda_unique<void(bool ctrlShiftEnter)> _submittedCallback;
base::lambda_unique<void(uint64 itemId)> _itemRemovedCallback;
base::lambda_unique<void(int heightDelta)> _resizedCallback;
};
} // namespace Ui

View file

@ -30,7 +30,7 @@ public:
using UpdateCallback = base::lambda_unique<void()>;
WidgetSlideWrap(QWidget *parent, Widget *entity
, style::margins entityPadding
, UpdateCallback &&updateCallback
, UpdateCallback updateCallback
, int duration = st::widgetSlideDuration) : TWidget(parent)
, _entity(entity)
, _padding(entityPadding)

View file

@ -54,6 +54,51 @@ FilledSlider {
duration: int;
}
RoundImageCheckbox {
imageRadius: pixels;
imageSmallRadius: pixels;
selectWidth: pixels;
selectFg: color;
selectDuration: int;
checkBorder: color;
checkBg: color;
checkRadius: pixels;
checkSmallRadius: pixels;
checkIcon: icon;
}
MultiSelectItem {
padding: margins;
maxWidth: pixels;
height: pixels;
font: font;
textBg: color;
textFg: color;
textActiveBg: color;
textActiveFg: color;
deleteFg: color;
deleteLeft: pixels;
deleteStroke: pixels;
duration: int;
minScale: double;
}
MultiSelect {
padding: margins;
maxHeight: pixels;
scroll: flatScroll;
item: MultiSelectItem;
itemSkip: pixels;
field: InputField;
fieldMinWidth: pixels;
fieldIcon: icon;
fieldIconSkip: pixels;
fieldCancel: IconButton;
fieldCancelSkip: pixels;
}
widgetSlideDuration: 200;
discreteSliderHeight: 39px;

View file

@ -41,10 +41,9 @@ void ChatBackground::initIfEmpty() {
}
}
void ChatBackground::init(int32 id, QPixmap &&image, QPixmap &&dog) {
void ChatBackground::init(int32 id, QPixmap &&image) {
_id = id;
_image = std_::move(image);
_dog = std_::move(dog);
notify(ChatBackgroundUpdate(ChatBackgroundUpdate::Type::New, _tile));
}
@ -52,7 +51,6 @@ void ChatBackground::init(int32 id, QPixmap &&image, QPixmap &&dog) {
void ChatBackground::reset() {
_id = 0;
_image = QPixmap();
_dog = QPixmap();
_tile = false;
notify(ChatBackgroundUpdate(ChatBackgroundUpdate::Type::New, _tile));
@ -66,10 +64,6 @@ const QPixmap &ChatBackground::image() const {
return _image;
}
const QPixmap &ChatBackground::dog() const {
return _dog;
}
bool ChatBackground::tile() const {
return _tile;
}

View file

@ -39,19 +39,17 @@ class ChatBackground : public base::Observable<ChatBackgroundUpdate> {
public:
bool empty() const;
void initIfEmpty();
void init(int32 id, QPixmap &&image, QPixmap &&dog);
void init(int32 id, QPixmap &&image);
void reset();
int32 id() const;
const QPixmap &image() const;
const QPixmap &dog() const;
bool tile() const;
void setTile(bool tile);
private:
int32 _id = 0;
QPixmap _image;
QPixmap _dog;
bool _tile = false;
};

View file

@ -21,6 +21,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
using "basic.style";
titleIconPosition: point(9px, 9px);
titleIcon: icon {
{ "title_icon_bg", #49708f },
{ "title_icon", #ffffff, point(4px, 4px) }
};
titleCounterPosition: point(17px, 17px);
notifyBg: white;
notifyBorder: #f1f1f1;
notifyBorderWidth: 1px;

View file

@ -183,6 +183,8 @@
'<(src_loc)/boxes/languagebox.h',
'<(src_loc)/boxes/localstoragebox.cpp',
'<(src_loc)/boxes/localstoragebox.h',
'<(src_loc)/boxes/members_box.cpp',
'<(src_loc)/boxes/members_box.h',
'<(src_loc)/boxes/notifications_box.cpp',
'<(src_loc)/boxes/notifications_box.h',
'<(src_loc)/boxes/passcodebox.cpp',
@ -199,6 +201,8 @@
'<(src_loc)/boxes/sharebox.h',
'<(src_loc)/boxes/stickersetbox.cpp',
'<(src_loc)/boxes/stickersetbox.h',
'<(src_loc)/boxes/stickers_box.cpp',
'<(src_loc)/boxes/stickers_box.h',
'<(src_loc)/boxes/usernamebox.cpp',
'<(src_loc)/boxes/usernamebox.h',
'<(src_loc)/core/basic_types.h',
@ -455,6 +459,8 @@
'<(src_loc)/ui/effects/radial_animation.h',
'<(src_loc)/ui/effects/rect_shadow.cpp',
'<(src_loc)/ui/effects/rect_shadow.h',
'<(src_loc)/ui/effects/round_image_checkbox.cpp',
'<(src_loc)/ui/effects/round_image_checkbox.h',
'<(src_loc)/ui/style/style_core.cpp',
'<(src_loc)/ui/style/style_core.h',
'<(src_loc)/ui/style/style_core_color.cpp',
@ -487,6 +493,8 @@
'<(src_loc)/ui/widgets/label_simple.h',
'<(src_loc)/ui/widgets/media_slider.cpp',
'<(src_loc)/ui/widgets/media_slider.h',
'<(src_loc)/ui/widgets/multi_select.cpp',
'<(src_loc)/ui/widgets/multi_select.h',
'<(src_loc)/ui/widgets/shadow.cpp',
'<(src_loc)/ui/widgets/shadow.h',
'<(src_loc)/ui/widgets/widget_slide_wrap.h',