diff --git a/Telegram/SourceFiles/boxes/abstractbox.cpp b/Telegram/SourceFiles/boxes/abstractbox.cpp
index 9300efb8e..ef0e69c5e 100644
--- a/Telegram/SourceFiles/boxes/abstractbox.cpp
+++ b/Telegram/SourceFiles/boxes/abstractbox.cpp
@@ -90,6 +90,7 @@ void AbstractBox::animStep(float64 ms) {
 	if (ms >= 1) {
 		a_opacity.finish();
 		_cache = QPixmap();
+		setAttribute(Qt::WA_OpaquePaintEvent);
 		if (!_hiding) {
 			showAll();
 			showDone();
@@ -129,6 +130,7 @@ void AbstractBox::startHide() {
 		hideAll();
 	}
 	a_opacity.start(0);
+	setAttribute(Qt::WA_OpaquePaintEvent, false);
 }
 
 ScrollableBox::ScrollableBox(const style::flatScroll &scroll) : AbstractBox(),
diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp
index c4b9c9843..7978ebd01 100644
--- a/Telegram/SourceFiles/boxes/contactsbox.cpp
+++ b/Telegram/SourceFiles/boxes/contactsbox.cpp
@@ -150,8 +150,12 @@ bool ContactsInner::addAdminFail(const RPCError &error, mtpRequestId req) {
 	if (req != _addAdminRequestId) return true;
 
 	_addAdminRequestId = 0;
-	if (_addAdminBox) _addAdminBox->onClose();
-	emit adminAdded();
+	if (error.type() == "USERS_TOO_MUCH") {
+		App::wnd()->replaceLayer(new MaxInviteBox(_channel->invitationUrl));
+	} else {
+		if (_addAdminBox) _addAdminBox->onClose();
+		emit adminAdded();
+	}
 	return true;
 }
 
@@ -173,23 +177,29 @@ void ContactsInner::peerUpdated(PeerData *peer) {
 				}
 			}
 		}
+		update();
 	} else {
+		int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2;
 		ContactsData::iterator i = _contactsData.find(peer);
 		if (i != _contactsData.cend()) {
 			for (DialogRow *row = _contacts->list.begin; row->next; row = row->next) {
-				if (row->attached == i.value()) row->attached = 0;
+				if (row->attached == i.value()) {
+					row->attached = 0;
+					update(0, rh * row->pos, width(), rh);
+				}
 			}
 			if (!_filter.isEmpty()) {
 				for (int32 j = 0, s = _filtered.size(); j < s; ++j) {
-					if (_filtered[j]->attached == i.value()) _filtered[j]->attached = 0;
+					if (_filtered[j]->attached == i.value()) {
+						_filtered[j]->attached = 0;
+						update(0, rh * j, width(), rh);
+					}
 				}
 			}
 			delete i.value();
 			_contactsData.erase(i);
 		}
 	}
-
-	parentWidget()->update();
 }
 
 void ContactsInner::loadProfilePhotos(int32 yFrom) {
@@ -338,10 +348,11 @@ void ContactsInner::paintEvent(QPaintEvent *e) {
 	QRect r(e->rect());
 	Painter p(this);
 
+	p.setClipRect(r);
 	_time = unixtime();
 	p.fillRect(r, st::white->b);
 
-	int32 yFrom = r.top(), yTo = r.bottom();
+	int32 yFrom = r.y(), yTo = r.y() + r.height();
 	int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2;
 	if (_filter.isEmpty()) {
 		if (_contacts->list.count || !_byUsername.isEmpty()) {
@@ -365,16 +376,12 @@ void ContactsInner::paintEvent(QPaintEvent *e) {
 
 				yFrom -= _contacts->list.count * rh + st::searchedBarHeight;
 				yTo -= _contacts->list.count * rh + st::searchedBarHeight;
-				int32 from = (yFrom >= 0) ? (yFrom / rh) : 0;
-				if (from < _byUsername.size()) {
-					int32 to = (yTo / rh) + 1;
-					if (to > _byUsername.size()) to = _byUsername.size();
-
-					p.translate(0, from * rh);
-					for (; from < to; ++from) {
-						paintDialog(p, _byUsername[from], d_byUsername[from], (_byUsernameSel == from));
-						p.translate(0, rh);
-					}
+				int32 from = floorclamp(yFrom, rh, 0, _byUsername.size());
+				int32 to = ceilclamp(yTo, rh, 0, _byUsername.size());
+				p.translate(0, from * rh);
+				for (; from < to; ++from) {
+					paintDialog(p, _byUsername[from], d_byUsername[from], (_byUsernameSel == from));
+					p.translate(0, rh);
 				}
 			}
 		} else {
@@ -405,16 +412,12 @@ void ContactsInner::paintEvent(QPaintEvent *e) {
 			p.drawText(QRect(0, 0, width(), st::noContactsHeight), text, style::al_center);
 		} else {
 			if (!_filtered.isEmpty()) {
-				int32 from = (yFrom >= 0) ? (yFrom / rh) : 0;
-				if (from < _filtered.size()) {
-					int32 to = (yTo / rh) + 1;
-					if (to > _filtered.size()) to = _filtered.size();
-
-					p.translate(0, from * rh);
-					for (; from < to; ++from) {
-						paintDialog(p, _filtered[from]->history->peer, contactData(_filtered[from]), (_filteredSel == from));
-						p.translate(0, rh);
-					}
+				int32 from = floorclamp(yFrom, rh, 0, _filtered.size());
+				int32 to = ceilclamp(yTo, rh, 0, _filtered.size());
+				p.translate(0, from * rh);
+				for (; from < to; ++from) {
+					paintDialog(p, _filtered[from]->history->peer, contactData(_filtered[from]), (_filteredSel == from));
+					p.translate(0, rh);
 				}
 			}
 			if (!_byUsernameFiltered.isEmpty()) {
@@ -426,16 +429,12 @@ void ContactsInner::paintEvent(QPaintEvent *e) {
 
 				yFrom -= _filtered.size() * rh + st::searchedBarHeight;
 				yTo -= _filtered.size() * rh + st::searchedBarHeight;
-				int32 from = (yFrom >= 0) ? (yFrom / rh) : 0;
-				if (from < _byUsernameFiltered.size()) {
-					int32 to = (yTo / rh) + 1;
-					if (to > _byUsernameFiltered.size()) to = _byUsernameFiltered.size();
-
-					p.translate(0, from * rh);
-					for (; from < to; ++from) {
-						paintDialog(p, _byUsernameFiltered[from], d_byUsernameFiltered[from], (_byUsernameSel == from));
-						p.translate(0, rh);
-					}
+				int32 from = floorclamp(yFrom, rh, 0, _byUsernameFiltered.size());
+				int32 to = ceilclamp(yTo, rh, 0, _byUsernameFiltered.size());
+				p.translate(0, from * rh);
+				for (; from < to; ++from) {
+					paintDialog(p, _byUsernameFiltered[from], d_byUsernameFiltered[from], (_byUsernameSel == from));
+					p.translate(0, rh);
 				}
 			}
 		}
@@ -446,12 +445,31 @@ void ContactsInner::enterEvent(QEvent *e) {
 	setMouseTracking(true);
 }
 
+void ContactsInner::updateSelectedRow() {
+	int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2;
+	if (_filter.isEmpty()) {
+		if (_sel) {
+			update(0, _sel->pos * rh, width(), rh);
+		}
+		if (_byUsernameSel >= 0) {
+			update(0, _contacts->list.count * rh + st::searchedBarHeight + _byUsernameSel * rh, width(), rh);
+		}
+	} else {
+		if (_filteredSel >= 0) {
+			update(0, _filteredSel * rh, width(), rh);
+		}
+		if (_byUsernameSel >= 0) {
+			update(0, _filtered.size() * rh + st::searchedBarHeight + _byUsernameSel * rh, width(), rh);
+		}
+	}
+}
+
 void ContactsInner::leaveEvent(QEvent *e) {
 	setMouseTracking(false);
 	if (_sel || _filteredSel >= 0 || _byUsernameSel >= 0) {
+		updateSelectedRow();
 		_sel = 0;
 		_filteredSel = _byUsernameSel = -1;
-		parentWidget()->update();
 	}
 }
 
@@ -554,7 +572,7 @@ void ContactsInner::chooseParticipant() {
 			}
 		}
 	}
-	parentWidget()->update();
+	update();
 }
 
 void ContactsInner::changeCheckState(DialogRow *row) {
@@ -596,18 +614,20 @@ void ContactsInner::updateSel() {
 		int32 byUsernameSel = (in && p.y() >= _contacts->list.count * rh + st::searchedBarHeight) ? ((p.y() - _contacts->list.count * rh - st::searchedBarHeight) / rh) : -1;
 		if (byUsernameSel >= _byUsername.size()) byUsernameSel = -1;
 		if (newSel != _sel || byUsernameSel != _byUsernameSel) {
+			updateSelectedRow();
 			_sel = newSel;
 			_byUsernameSel = byUsernameSel;
-			parentWidget()->update();
+			updateSelectedRow();
 		}
 	} else {
 		int32 newFilteredSel = (in && p.y() >= 0 && p.y() < _filtered.size() * rh) ? (p.y() / rh) : -1;
 		int32 byUsernameSel = (in && p.y() >= _filtered.size() * rh + st::searchedBarHeight) ? ((p.y() - _filtered.size() * rh - st::searchedBarHeight) / rh) : -1;
 		if (byUsernameSel >= _byUsernameFiltered.size()) byUsernameSel = -1;
 		if (newFilteredSel != _filteredSel || byUsernameSel != _byUsernameSel) {
+			updateSelectedRow();
 			_filteredSel = newFilteredSel;
 			_byUsernameSel = byUsernameSel;
-			parentWidget()->update();
+			updateSelectedRow();
 		}
 	}
 }
@@ -745,7 +765,7 @@ void ContactsInner::updateFilter(QString filter) {
 				emit searchByUsername();
 			}
 		}
-		if (parentWidget()) parentWidget()->update();
+		update();
 		loadProfilePhotos(0);
 	}
 }
@@ -984,7 +1004,7 @@ void ContactsInner::selectSkip(int32 dir) {
 			emit mustScrollTo(skip + _byUsernameSel * rh, skip + (_byUsernameSel + 1) * rh);
 		}
 	}
-	parentWidget()->update();
+	update();
 }
 
 void ContactsInner::selectSkipPage(int32 h, int32 dir) {
@@ -1441,7 +1461,7 @@ void MembersInner::paintEvent(QPaintEvent *e) {
 	_time = unixtime();
 	p.fillRect(r, st::white->b);
 
-	int32 yFrom = r.top(), yTo = r.bottom();
+	int32 yFrom = r.y() - st::membersPadding.top(), yTo = r.y() + r.height() - st::membersPadding.top();
 	int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2;
 
 	p.translate(0, st::membersPadding.top());
@@ -1450,19 +1470,15 @@ void MembersInner::paintEvent(QPaintEvent *e) {
 		p.setPen(st::noContactsColor->p);
 		p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center);
 	} else {
-		int32 from = (yFrom >= 0) ? (yFrom / rh) : 0;
-		if (from < _rows.size()) {
-			int32 to = (yTo / rh) + 1;
-			if (to > _rows.size()) to = _rows.size();
-
-			p.translate(0, from * rh);
-			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, rh);
-			}
+		int32 from = floorclamp(yFrom, rh, 0, _rows.size());
+		int32 to = ceilclamp(yTo, rh, 0, _rows.size());
+		p.translate(0, from * rh);
+		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, rh);
 		}
 	}
 }
@@ -1474,8 +1490,8 @@ void MembersInner::enterEvent(QEvent *e) {
 void MembersInner::leaveEvent(QEvent *e) {
 	setMouseTracking(false);
 	if (_sel >= 0) {
+		updateSelectedRow();
 		_sel = -1;
-		parentWidget()->update();
 	}
 }
 
@@ -1581,7 +1597,7 @@ void MembersInner::selectSkip(int32 dir) {
 		emit mustScrollTo(_sel * rh, (_sel + 1) * rh);
 	}
 
-	parentWidget()->update();
+	update();
 }
 
 void MembersInner::selectSkipPage(int32 h, int32 dir) {
@@ -1700,23 +1716,32 @@ void MembersInner::updateSel() {
 		newKickSel = -1;
 	}
 	if (newSel != _sel || newKickSel != _kickSel) {
+		updateSelectedRow();
 		_sel = newSel;
 		_kickSel = newKickSel;
-		parentWidget()->update();
+		updateSelectedRow();
 		setCursor(_kickSel >= 0 ? style::cur_pointer : style::cur_default);
 	}
 }
 
 void MembersInner::peerUpdated(PeerData *peer) {
-	parentWidget()->update();
+	update();
+}
+
+void MembersInner::updateSelectedRow() {
+	if (_sel >= 0) {
+		int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2;
+		update(0, st::membersPadding.top() + _sel * rh, width(), rh);
+	}
 }
 
 void MembersInner::onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars) {
+	int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2;
 	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::profileListNameFont, peer->name, _textNameOptions);
-				parentWidget()->update();
+				update(0, st::membersPadding.top() + i * rh, width(), rh);
 			} else {
 				break;
 			}
diff --git a/Telegram/SourceFiles/boxes/contactsbox.h b/Telegram/SourceFiles/boxes/contactsbox.h
index add3c8303..45e872bb8 100644
--- a/Telegram/SourceFiles/boxes/contactsbox.h
+++ b/Telegram/SourceFiles/boxes/contactsbox.h
@@ -108,6 +108,7 @@ public slots:
 
 private:
 
+	void updateSelectedRow();
 	void addAdminDone(const MTPBool &result, mtpRequestId req);
 	bool addAdminFail(const RPCError &error, mtpRequestId req);
 
@@ -296,6 +297,7 @@ public slots:
 
 private:
 
+	void updateSelectedRow();
 	void clearSel();
 	MemberData *data(int32 index);
 
diff --git a/Telegram/SourceFiles/boxes/sessionsbox.cpp b/Telegram/SourceFiles/boxes/sessionsbox.cpp
index c5250bdf2..2cf7fe6b1 100644
--- a/Telegram/SourceFiles/boxes/sessionsbox.cpp
+++ b/Telegram/SourceFiles/boxes/sessionsbox.cpp
@@ -38,30 +38,28 @@ void SessionsInner::paintEvent(QPaintEvent *e) {
 	p.setFont(st::linkFont->f);
 	int32 x = st::sessionPadding.left(), xact = st::sessionTerminateSkip + st::sessionTerminate.iconPos.x();// st::sessionTerminateSkip + st::sessionTerminate.width + st::sessionTerminateSkip;
 	int32 w = width();
-	int32 from = (r.top() >= 0) ? qFloor(r.top() / st::sessionHeight) : 0, count = _list->size();
-	if (from < count) {
-		int32 to = (r.bottom() >= 0 ? qFloor(r.bottom() / st::sessionHeight) : 0) + 1;
-		if (to > count) to = count;
-		p.translate(0, from * st::sessionHeight);
-		for (int32 i = from; i < to; ++i) {
-			const SessionData &auth(_list->at(i));
+	int32 count = _list->size();
+	int32 from = floorclamp(r.y(), st::sessionHeight, 0, count);
+	int32 to = ceilclamp(r.y() + r.height(), 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::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::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.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);
-		}
+		p.translate(0, st::sessionHeight);
 	}
 }
 
diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp
index 5a8af1db0..5c05b7471 100644
--- a/Telegram/SourceFiles/boxes/stickersetbox.cpp
+++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp
@@ -26,9 +26,14 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
 
 #include "localstorage.h"
 
+void StickerSetPanel::paintEvent(QPaintEvent *e) {
+	Painter p(this);
+	p.fillRect(e->rect(), st::emojiPanHeaderBg->b);
+}
+
 StickerSetInner::StickerSetInner(const MTPInputStickerSet &set) :
 _loaded(false), _setId(0), _setAccess(0), _setCount(0), _setHash(0), _setFlags(0), _bottom(0),
-_input(set), _installRequest(0) {
+_input(set), _installRequest(0), _panel(0) {
 	switch (set.type()) {
 	case mtpc_inputStickerSetID: _setId = set.c_inputStickerSetID().vid.v; _setAccess = set.c_inputStickerSetID().vaccess_hash.v; break;
 	case mtpc_inputStickerSetShortName: _setShortName = qs(set.c_inputStickerSetShortName().vshort_name); break;
@@ -68,6 +73,9 @@ void StickerSetInner::gotSet(const MTPmessages_StickerSet &set) {
 	} else {
 		int32 rows = _pack.size() / StickerPanPerRow + ((_pack.size() % StickerPanPerRow) ? 1 : 0);
 		resize(st::stickersPadding + StickerPanPerRow * st::stickersSize.width(), rows * st::stickersSize.height() + st::stickersAddOrShare);
+		_panel = new StickerSetPanel(parentWidget());
+		_panel->setGeometry(0, parentWidget()->height() - st::stickersAddOrShare, width(), st::stickersAddOrShare);
+		_panel->show();
 	}
 	_loaded = true;
 
@@ -178,16 +186,12 @@ void StickerSetInner::paintEvent(QPaintEvent *e) {
 			}
 		}
 	}
-	p.fillRect(0, _bottom - st::stickersAddOrShare, width(), st::stickersAddOrShare, st::emojiPanHeaderBg->b);
 }
 
 void StickerSetInner::setScrollBottom(int32 bottom) {
 	if (bottom == _bottom) return;
 
-	QRegion upd = QRect(0, _bottom - st::stickersAddOrShare, width(), st::stickersAddOrShare);
 	_bottom = bottom;
-	upd += QRect(0, _bottom - st::stickersAddOrShare, width(), st::stickersAddOrShare);
-	repaint(upd);
 }
 
 bool StickerSetInner::loaded() const {
diff --git a/Telegram/SourceFiles/boxes/stickersetbox.h b/Telegram/SourceFiles/boxes/stickersetbox.h
index 8ac876990..4adcf4550 100644
--- a/Telegram/SourceFiles/boxes/stickersetbox.h
+++ b/Telegram/SourceFiles/boxes/stickersetbox.h
@@ -19,7 +19,16 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
 
 #include "abstractbox.h"
 
-class StickerSetInner : public QWidget, public RPCSender {
+class StickerSetPanel : public TWidget {
+public:
+
+	StickerSetPanel(QWidget *parent) : TWidget(parent) {
+	}
+	void paintEvent(QPaintEvent *e);
+
+};
+
+class StickerSetInner : public TWidget, public RPCSender {
 	Q_OBJECT
 
 public:
@@ -64,6 +73,8 @@ private:
 	MTPInputStickerSet _input;
 
 	mtpRequestId _installRequest;
+
+	StickerSetPanel *_panel;
 };
 
 class StickerSetBox : public ScrollableBox, public RPCSender {
diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp
index 830a3a39b..7bfe73c9b 100644
--- a/Telegram/SourceFiles/dialogswidget.cpp
+++ b/Telegram/SourceFiles/dialogswidget.cpp
@@ -69,11 +69,14 @@ int32 DialogsInner::searchedOffset() const {
 }
 
 void DialogsInner::paintRegion(Painter &p, const QRegion &region, bool paintingOther) {
+	QRegion original(rtl() ? region.translated(-otherWidth(), 0) : region);
+	if (App::wnd() && App::wnd()->contentOverlapped(this, original)) return;
+
 	if (!App::main()) return;
 
 	QRect r(region.boundingRect());
-	if (!paintingOther && !r.contains(rect())) {
-		p.setClipRect(rect().intersected(r));
+	if (!paintingOther) {
+		p.setClipRect(r);
 	}
 
 	if (_state == DefaultState) {
@@ -94,16 +97,12 @@ void DialogsInner::paintRegion(Painter &p, const QRegion &region, bool paintingO
 		}
 	} else if (_state == FilteredState || _state == SearchedState) {
 		if (!hashtagResults.isEmpty()) {
-			int32 from = r.top() / int32(st::mentionHeight);
-			if (from < 0) {
-				from = 0;
-			} else if (from > hashtagResults.size()) {
-				from = hashtagResults.size();
-			}
+			int32 from = floorclamp(r.y(), st::mentionHeight, 0, hashtagResults.size());
+			int32 to = ceilclamp(r.y() + r.height(), st::mentionHeight, 0, hashtagResults.size());
 			p.translate(0, from * st::mentionHeight);
 			if (from < hashtagResults.size()) {
-				int32 to = (r.bottom() / int32(st::mentionHeight)) + 1, w = fullWidth(), htagwidth = w - st::dlgPaddingHor * 2;
-				if (to > hashtagResults.size()) to = hashtagResults.size();
+				int32 w = fullWidth(), htagwidth = w - st::dlgPaddingHor * 2;
+
 				p.setFont(st::mentionFont->f);
 				p.setPen(st::black->p);
 				for (; from < to; ++from) {
@@ -142,16 +141,11 @@ void DialogsInner::paintRegion(Painter &p, const QRegion &region, bool paintingO
 		}
 		if (!filterResults.isEmpty()) {
 			int32 skip = filteredOffset();
-			int32 from = (r.top() - skip) / int32(st::dlgHeight);
-			if (from < 0) {
-				from = 0;
-			} else if (from > filterResults.size()) {
-				from = filterResults.size();
-			}
+			int32 from = floorclamp(r.y() - skip, st::dlgHeight, 0, filterResults.size());
+			int32 to = ceilclamp(r.y() + r.height() - skip, st::dlgHeight, 0, filterResults.size());
 			p.translate(0, from * st::dlgHeight);
 			if (from < filterResults.size()) {
-				int32 to = (r.bottom() / int32(st::dlgHeight)) + 1, w = fullWidth();
-				if (to > filterResults.size()) to = filterResults.size();
+				int32 w = fullWidth();
 				for (; from < to; ++from) {
 					bool active = (filterResults[from]->history->peer == App::main()->activePeer() && !App::main()->activeMsgId());
 					bool selected = (from == filteredSel);
@@ -171,16 +165,11 @@ void DialogsInner::paintRegion(Painter &p, const QRegion &region, bool paintingO
 			p.translate(0, st::searchedBarHeight);
 
 			int32 skip = peopleOffset();
-			int32 from = (r.top() - skip) / int32(st::dlgHeight);
-			if (from < 0) {
-				from = 0;
-			} else if (from > peopleResults.size()) {
-				from = peopleResults.size();
-			}
+			int32 from = floorclamp(r.y() - skip, st::dlgHeight, 0, peopleResults.size());
+			int32 to = ceilclamp(r.y() + r.height() - skip, st::dlgHeight, 0, peopleResults.size());
 			p.translate(0, from * st::dlgHeight);
 			if (from < peopleResults.size()) {
-				int32 to = ((r.bottom() - skip) / int32(st::dlgHeight)) + 1, w = fullWidth();
-				if (to > peopleResults.size()) to = peopleResults.size();
+				int32 w = fullWidth();
 				for (; from < to; ++from) {
 					bool active = (peopleResults[from] == App::main()->activePeer() && !App::main()->activeMsgId());
 					bool selected = (from == peopleSel);
@@ -215,16 +204,11 @@ void DialogsInner::paintRegion(Painter &p, const QRegion &region, bool paintingO
 			p.translate(0, st::searchedBarHeight);
 
 			int32 skip = searchedOffset();
-			int32 from = (r.top() - skip) / int32(st::dlgHeight);
-			if (from < 0) {
-				from = 0;
-			} else if (from > searchResults.size()) {
-				from = searchResults.size();
-			}
+			int32 from = floorclamp(r.y() - skip, st::dlgHeight, 0, searchResults.size());
+			int32 to = ceilclamp(r.y() + r.height() - skip, st::dlgHeight, 0, searchResults.size());
 			p.translate(0, from * st::dlgHeight);
 			if (from < searchResults.size()) {
-				int32 to = ((r.bottom() - skip) / int32(st::dlgHeight)) + 1, w = fullWidth();
-				if (to > searchResults.size()) to = searchResults.size();
+				int32 w = fullWidth();
 				for (; from < to; ++from) {
 					bool active = (searchResults[from]->_item->history()->peer == App::main()->activePeer() && searchResults[from]->_item->id == App::main()->activeMsgId());
 					bool selected = (from == searchedSel);
@@ -941,7 +925,7 @@ void DialogsInner::refresh(bool toTop) {
 			h = searchedOffset() + (searchResults.count() * st::dlgHeight);
 		}
 	}
-	setHeight(h	);
+	setHeight(h);
 	if (toTop) {
 		emit mustScrollTo(0, 0);
 		loadPeerPhotos(0);
@@ -2192,6 +2176,8 @@ void DialogsWidget::keyPressEvent(QKeyEvent *e) {
 }
 
 void DialogsWidget::paintEvent(QPaintEvent *e) {
+	if (App::wnd() && App::wnd()->contentOverlapped(this, e)) return;
+
 	QPainter p(this);
 	QRect r(e->rect());
 	if (r != rect()) {
diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp
index ba9e07789..de572f584 100644
--- a/Telegram/SourceFiles/dropdown.cpp
+++ b/Telegram/SourceFiles/dropdown.cpp
@@ -444,7 +444,7 @@ bool DragArea::animStep(float64 ms) {
 	return res;
 }
 
-EmojiColorPicker::EmojiColorPicker(QWidget *parent) : TWidget(parent),
+EmojiColorPicker::EmojiColorPicker() :
 _ignoreShow(false), _selected(-1), _pressedSel(-1), _hiding(false), a_opacity(0), _shadow(st::dropdownDef.shadow) {
 	memset(_variants, 0, sizeof(_variants));
 	memset(_hovers, 0, sizeof(_hovers));
@@ -484,13 +484,16 @@ void EmojiColorPicker::paintEvent(QPaintEvent *e) {
 	if (!_cache.isNull()) {
 		p.setOpacity(a_opacity.current());
 	}
+	if (e->rect() != rect()) {
+		p.setClipRect(e->rect());
+	}
 
 	int32 w = st::dropdownDef.shadow.pxWidth(), h = st::dropdownDef.shadow.pxHeight();
 	QRect r = QRect(w, h, width() - 2 * w, height() - 2 * h);
 	_shadow.paint(p, r, st::dropdownDef.shadowShift);
 
 	if (_cache.isNull()) {
-		p.fillRect(r, st::white->b);
+		p.fillRect(e->rect().intersected(r), st::white->b);
 
 		int32 x = w + 2 * st::emojiColorsPadding + st::emojiPanSize.width();
 		if (rtl()) x = width() - x - st::emojiColorsSep;
@@ -541,7 +544,7 @@ void EmojiColorPicker::mouseMoveEvent(QMouseEvent *e) {
 }
 
 bool EmojiColorPicker::animStep(float64 ms) {
-	bool res1 = true, res2 = true;
+	bool res1 = false, res2 = false;
 	if (!_cache.isNull()) {
 		float64 dt = ms / st::dropdownDef.duration;
 		if (dt >= 1) {
@@ -554,13 +557,15 @@ bool EmojiColorPicker::animStep(float64 ms) {
 				_lastMousePos = QCursor::pos();
 				updateSelected();
 			}
-			res1 = false;
 		} else {
 			a_opacity.update(dt, anim::linear);
+			res1 = true;
 		}
+		update();
 	}
 	if (!_emojiAnimations.isEmpty()) {
 		uint64 now = getms();
+		QRegion toUpdate;
 		for (EmojiAnimations::iterator i = _emojiAnimations.begin(); i != _emojiAnimations.end();) {
 			int index = qAbs(i.key()) - 1;
 			float64 dt = float64(now - i.value()) / st::emojiPanDuration;
@@ -571,10 +576,11 @@ bool EmojiColorPicker::animStep(float64 ms) {
 				_hovers[index] = (i.key() > 0) ? dt : (1 - dt);
 				++i;
 			}
+			toUpdate += QRect(st::dropdownDef.shadow.pxWidth() + st::emojiColorsPadding + index * st::emojiPanSize.width() + (index ? 2 * st::emojiColorsPadding + st::emojiColorsSep : 0), st::dropdownDef.shadow.pxHeight() + st::emojiColorsPadding, st::emojiPanSize.width(), st::emojiPanSize.height());
 		}
 		res2 = !_emojiAnimations.isEmpty();
+		rtlupdate(toUpdate.boundingRect());
 	}
-	update();
 	return res1 || res2;
 }
 
@@ -684,13 +690,13 @@ void EmojiColorPicker::drawVariant(Painter &p, int variant) {
 	p.drawPixmapLeft(w.x() + (st::emojiPanSize.width() - (esize / cIntRetinaFactor())) / 2, w.y() + (st::emojiPanSize.height() - (esize / cIntRetinaFactor())) / 2, width(), App::emojisLarge(), QRect(_variants[variant]->x * esize, _variants[variant]->y * esize, esize, esize));
 }
 
-EmojiPanInner::EmojiPanInner(QWidget *parent) : TWidget(parent), _maxHeight(int(st::emojiPanMaxHeight)),
-_top(0), _selected(-1), _pressedSel(-1), _pickerSel(-1), _picker(this),
-_switcherHover(0), _stickersWidth(st::emojiPanHeaderFont->m.width(lang(lng_switch_stickers))) {
-	resize(st::emojiPanWidth, countHeight());
+EmojiPanInner::EmojiPanInner() : _maxHeight(int(st::emojiPanMaxHeight)),
+_top(0), _selected(-1), _pressedSel(-1), _pickerSel(-1) {
+	resize(st::emojiPanWidth - st::emojiScroll.width, countHeight());
 
 	setMouseTracking(true);
 	setFocusPolicy(Qt::NoFocus);
+	setAttribute(Qt::WA_OpaquePaintEvent);
 
 	_picker.hide();
 
@@ -712,16 +718,13 @@ _switcherHover(0), _stickersWidth(st::emojiPanHeaderFont->m.width(lang(lng_switc
 
 void EmojiPanInner::setMaxHeight(int32 h) {
 	_maxHeight = h;
-	resize(st::emojiPanWidth, countHeight());
+	resize(st::emojiPanWidth - st::emojiScroll.width, countHeight());
 }
 
 void EmojiPanInner::setScrollTop(int top) {
 	if (top == _top) return;
 
-	QRegion upd = QRect(0, _top, width(), st::emojiPanHeader);
 	_top = top;
-	upd += QRect(0, _top, width(), st::emojiPanHeader);
-	repaint(upd);
 	updateSelected();
 }
 
@@ -743,6 +746,14 @@ void EmojiPanInner::paintEvent(QPaintEvent *e) {
 	}
 	p.fillRect(r, st::white->b);
 
+	int32 fromcol = floorclamp(r.x() - st::emojiPanPadding, st::emojiPanSize.width(), 0, EmojiPanPerRow);
+	int32 tocol = ceilclamp(r.x() + r.width() - st::emojiPanPadding, st::emojiPanSize.width(), 0, EmojiPanPerRow);
+	if (rtl()) {
+		qSwap(fromcol, tocol);
+		fromcol = EmojiPanPerRow - fromcol;
+		tocol = EmojiPanPerRow - tocol;
+	}
+
 	int32 y, tilly = 0;
 	for (int c = 0; c < emojiTabCount; ++c) {
 		y = tilly;
@@ -752,14 +763,6 @@ void EmojiPanInner::paintEvent(QPaintEvent *e) {
 		if (r.top() >= tilly) continue;
 
 		y += st::emojiPanHeader;
-
-		if (r.bottom() <= y) {
-			p.setFont(st::emojiPanHeaderFont->f);
-			p.setPen(st::emojiPanHeaderColor->p);
-			p.drawTextLeft(st::emojiPanHeaderLeft, qMax(y - int(st::emojiPanHeader), _top) + st::emojiPanHeaderTop, width(), lang(LangKey(lng_emoji_category0 + c)));
-			break;
-		}
-
 		if (_emojis[c].isEmpty()) {
 			_emojis[c] = emojiPack(emojiTabAtIndex(c));
 			if (emojiTabAtIndex(c) != dbietRecent) {
@@ -779,9 +782,10 @@ void EmojiPanInner::paintEvent(QPaintEvent *e) {
 			}
 		}
 
-		int32 fromrow = (r.top() <= y) ? 0 : qMax(qFloor((r.top() - y) / st::emojiPanSize.height()), 0), torow = qMin(qCeil((r.bottom() - y) / st::emojiPanSize.height()) + 1, rows);
+		int32 fromrow = floorclamp(r.y() - y, st::emojiPanSize.height(), 0, rows);
+		int32 torow = ceilclamp(r.y() + r.height() - y, st::emojiPanSize.height(), 0, rows);
 		for (int32 i = fromrow; i < torow; ++i) {
-			for (int32 j = 0; j < EmojiPanPerRow; ++j) {
+			for (int32 j = fromcol; j < tocol; ++j) {
 				int32 index = i * EmojiPanPerRow + j;
 				if (index >= size) break;
 
@@ -798,34 +802,32 @@ void EmojiPanInner::paintEvent(QPaintEvent *e) {
 				p.drawPixmapLeft(w.x() + (st::emojiPanSize.width() - (_esize / cIntRetinaFactor())) / 2, w.y() + (st::emojiPanSize.height() - (_esize / cIntRetinaFactor())) / 2, width(), App::emojisLarge(), QRect(_emojis[c][index]->x * _esize, _emojis[c][index]->y * _esize, _esize, _esize));
 			}
 		}
-
-		if (y - int(st::emojiPanHeader) < _top) {
-			p.fillRect(QRect(0, qMin(_top, tilly - int(st::emojiPanHeader)), width(), st::emojiPanHeader), st::emojiPanHeaderBg->b);
-		}
-		p.setFont(st::emojiPanHeaderFont->f);
-		p.setPen(st::emojiPanHeaderColor->p);
-		p.drawTextLeft(st::emojiPanHeaderLeft, qMin(qMax(y - int(st::emojiPanHeader), _top), tilly - int(st::emojiPanHeader)) + st::emojiPanHeaderTop, width(), lang(LangKey(lng_emoji_category0 + c)));
 	}
+}
 
-	p.setFont(st::emojiPanHeaderFont->f);
-	p.setPen(st::emojiSwitchColor->p);
-	p.drawTextRight(st::emojiSwitchSkip, _top + st::emojiPanHeaderTop, width(), lang(lng_switch_stickers), _stickersWidth);
-	p.drawSpriteRight(QPoint(st::emojiSwitchImgSkip - st::emojiSwitchStickers.pxWidth(), _top + (st::emojiPanHeader - st::emojiSwitchStickers.pxHeight()) / 2), width(), st::emojiSwitchStickers);
+bool EmojiPanInner::checkPickerHide() {
+	if (!_picker.isHidden() && _selected == _pickerSel) {
+		_picker.hideStart();
+		_pickerSel = -1;
+		updateSelected();
+		return true;
+	}
+	return false;
 }
 
 void EmojiPanInner::mousePressEvent(QMouseEvent *e) {
 	_lastMousePos = e->globalPos();
 	updateSelected();
-	if (!_picker.isHidden() && _selected == _pickerSel) {
-		_picker.hideStart();
+	if (checkPickerHide()) {
 		return;
 	}
 	_pressedSel = _selected;
 
-	if (_selected >= 0 && _selected != SwitcherSelected) {
+	if (_selected >= 0) {
 		int tab = (_selected / MatrixRowShift), sel = _selected % MatrixRowShift;
 		if (tab < emojiTabCount && sel < _emojis[tab].size() && _emojis[tab][sel]->color) {
 			_pickerSel = _selected;
+			setCursor(style::cur_default);
 			if (cEmojiVariants().constFind(_emojis[tab][sel]->code) == cEmojiVariants().cend()) {
 				onShowPicker();
 			} else {
@@ -848,6 +850,7 @@ void EmojiPanInner::mouseReleaseEvent(QMouseEvent *e) {
 			if (tab < emojiTabCount && sel < _emojis[tab].size() && _emojis[tab][sel]->color) {
 				if (cEmojiVariants().constFind(_emojis[tab][sel]->code) != cEmojiVariants().cend()) {
 					_picker.hideStart();
+					_pickerSel = -1;
 				}
 			}
 		}
@@ -861,10 +864,6 @@ void EmojiPanInner::mouseReleaseEvent(QMouseEvent *e) {
 	}
 
 	if (_selected < 0 || _selected != pressed) return;
-	if (_selected == SwitcherSelected) {
-		emit switchToStickers();
-		return;
-	}
 
 	if (_selected >= emojiTabCount * MatrixRowShift) {
 		return;
@@ -930,8 +929,8 @@ void EmojiPanInner::onShowPicker() {
 			int32 size = (c == tab) ? (sel - (sel % EmojiPanPerRow)) : _counts[c], rows = (size / EmojiPanPerRow) + ((size % EmojiPanPerRow) ? 1 : 0);
 			y += st::emojiPanHeader + (rows * st::emojiPanSize.height());
 		}
-		y -= _picker.height() - st::msgRadius;
-		if (y < _top) {
+		y -= _picker.height() - st::msgRadius + _top;
+		if (y < 0) {
 			y += _picker.height() - st::msgRadius + st::emojiPanSize.height() - st::msgRadius;
 		}
 		int xmax = width() - _picker.width();
@@ -953,6 +952,23 @@ void EmojiPanInner::onPickerHidden() {
 	updateSelected();
 }
 
+QRect EmojiPanInner::emojiRect(int tab, int sel) {
+	int x = 0, y = 0;
+	for (int i = 0; i < emojiTabCount; ++i) {
+		if (i == tab) {
+			int rows = (sel / EmojiPanPerRow);
+			y += st::emojiPanHeader + rows * st::emojiPanSize.height();
+			x = st::emojiPanPadding + ((sel % EmojiPanPerRow) * st::emojiPanSize.width());
+			break;
+		} else {
+			int cnt = emojiPackCount(emojiTabAtIndex(i));
+			int rows = (cnt / EmojiPanPerRow) + ((cnt % EmojiPanPerRow) ? 1 : 0);
+			y += st::emojiPanHeader + rows * st::emojiPanSize.height();
+		}
+	}
+	return QRect(x, y, st::emojiPanSize.width(), st::emojiPanSize.height());
+}
+
 void EmojiPanInner::onColorSelected(EmojiPtr emoji) {
 	if (emoji->color) {
 		cRefEmojiVariants().insert(emoji->code, emojiKey(emoji));
@@ -961,7 +977,7 @@ void EmojiPanInner::onColorSelected(EmojiPtr emoji) {
 		int tab = (_pickerSel / MatrixRowShift), sel = _pickerSel % MatrixRowShift;
 		if (tab >= 0 && tab < emojiTabCount) {
 			_emojis[tab][sel] = emoji;
-			update();
+			rtlupdate(emojiRect(tab, sel));
 		}
 	}
 	selectEmoji(emoji);
@@ -998,9 +1014,17 @@ void EmojiPanInner::clearSelection(bool fast) {
 	if (fast) {
 		for (Animations::const_iterator i = _animations.cbegin(); i != _animations.cend(); ++i) {
 			int index = qAbs(i.key()) - 1, tab = (index / MatrixRowShift), sel = index % MatrixRowShift;
-			(index == SwitcherSelected ? _switcherHover : _hovers[tab][sel]) = 0;
+			_hovers[tab][sel] = 0;
 		}
 		_animations.clear();
+		if (_selected >= 0) {
+			int index = qAbs(_selected), tab = (index / MatrixRowShift), sel = index % MatrixRowShift;
+			_hovers[tab][sel] = 0;
+		}
+		if (_pressedSel >= 0) {
+			int index = qAbs(_pressedSel), tab = (index / MatrixRowShift), sel = index % MatrixRowShift;
+			_hovers[tab][sel] = 0;
+		}
 		_selected = _pressedSel = -1;
 		anim::stop(this);
 	} else {
@@ -1025,6 +1049,7 @@ void EmojiPanInner::hideFinish() {
 	if (!_picker.isHidden()) {
 		_picker.hideStart(true);
 		_pickerSel = -1;
+		clearSelection(true);
 	}
 }
 
@@ -1034,7 +1059,42 @@ void EmojiPanInner::refreshRecent() {
 	if (_hovers[0].size() != _counts[0]) _hovers[0] = QVector<float64>(_counts[0], 0);
 	_emojis[0] = emojiPack(dbietRecent);
 	int32 h = countHeight();
-	if (h != height()) resize(width(), h);
+	if (h != height()) {
+		resize(width(), h);
+		emit needRefreshPanels();
+	}
+}
+
+void EmojiPanInner::fillPanels(QVector<EmojiPanel*> &panels) {
+	if (_picker.parentWidget() != parentWidget()) {
+		_picker.setParent(parentWidget());
+	}
+	for (int32 i = 0; i < panels.size(); ++i) {
+		panels.at(i)->hide();
+		panels.at(i)->deleteLater();
+	}
+	panels.clear();
+
+	int y = 0;
+	panels.reserve(emojiTabCount);
+	for (int c = 0; c < emojiTabCount; ++c) {
+		panels.push_back(new EmojiPanel(parentWidget(), lang(LangKey(lng_emoji_category0 + c)), NoneStickerSetId, true, y));
+		connect(panels.back(), SIGNAL(mousePressed()), this, SLOT(checkPickerHide()));
+		int cnt = emojiPackCount(emojiTabAtIndex(c)), rows = (cnt / EmojiPanPerRow) + ((cnt % EmojiPanPerRow) ? 1 : 0);
+		panels.back()->show();
+		y += st::emojiPanHeader + rows * st::emojiPanSize.height();
+	}
+}
+
+void EmojiPanInner::refreshPanels(QVector<EmojiPanel*> &panels) {
+	if (panels.size() != emojiTabCount) return fillPanels(panels);
+
+	int32 y = 0;
+	for (int c = 0; c < emojiTabCount; ++c) {
+		panels.at(c)->setWantedY(y);
+		int cnt = emojiPackCount(emojiTabAtIndex(c)), rows = (cnt / EmojiPanPerRow) + ((cnt % EmojiPanPerRow) ? 1 : 0);
+		y += st::emojiPanHeader + rows * st::emojiPanSize.height();
+	}
 }
 
 void EmojiPanInner::updateSelected() {
@@ -1042,33 +1102,25 @@ void EmojiPanInner::updateSelected() {
 
 	int32 selIndex = -1;
 	QPoint p(mapFromGlobal(_lastMousePos));
-	if (p.y() < _top + st::emojiPanHeader) {
-		bool upon1 = rtl() && p.x() >= 0 && p.x() < st::emojiSwitchSkip + _stickersWidth + (st::emojiSwitchSkip - st::emojiSwitchImgSkip);
-		bool upon2 = !rtl() && p.x() < width() && p.x() >= width() - st::emojiSwitchSkip - _stickersWidth - (st::emojiSwitchSkip - st::emojiSwitchImgSkip);
-		if (upon1 || upon2) {
-			selIndex = SwitcherSelected;
-		}
-	} else {
-		int y, ytill = 0, sx = (rtl() ? width() - p.x() : p.x()) - st::emojiPanPadding;
-		for (int c = 0; c < emojiTabCount; ++c) {
-			int cnt = _counts[c];
-			y = ytill;
-			ytill = y + st::emojiPanHeader + ((cnt / EmojiPanPerRow) + ((cnt % EmojiPanPerRow) ? 1 : 0)) * st::emojiPanSize.height();
-			if (p.y() >= y && p.y() < ytill) {
-				y += st::emojiPanHeader;
-				if (p.y() >= y && sx >= 0 && sx < EmojiPanPerRow * st::emojiPanSize.width()) {
-					selIndex = qFloor((p.y() - y) / st::emojiPanSize.height()) * EmojiPanPerRow + qFloor(sx / st::emojiPanSize.width());
-					if (selIndex >= _emojis[c].size()) {
-						selIndex = -1;
-					} else {
-						selIndex += c * MatrixRowShift;
-					}
+	int y, ytill = 0, sx = (rtl() ? width() - p.x() : p.x()) - st::emojiPanPadding;
+	for (int c = 0; c < emojiTabCount; ++c) {
+		int cnt = _counts[c];
+		y = ytill;
+		ytill = y + st::emojiPanHeader + ((cnt / EmojiPanPerRow) + ((cnt % EmojiPanPerRow) ? 1 : 0)) * st::emojiPanSize.height();
+		if (p.y() >= y && p.y() < ytill) {
+			y += st::emojiPanHeader;
+			if (p.y() >= y && sx >= 0 && sx < EmojiPanPerRow * st::emojiPanSize.width()) {
+				selIndex = qFloor((p.y() - y) / st::emojiPanSize.height()) * EmojiPanPerRow + qFloor(sx / st::emojiPanSize.width());
+				if (selIndex >= _emojis[c].size()) {
+					selIndex = -1;
+				} else {
+					selIndex += c * MatrixRowShift;
 				}
-				break;
 			}
+			break;
 		}
 	}
-	
+
 	bool startanim = false;
 	int oldSel = _selected, newSel = selIndex;
 	
@@ -1103,18 +1155,20 @@ void EmojiPanInner::updateSelected() {
 
 bool EmojiPanInner::animStep(float64 ms) {
 	uint64 now = getms();
+	QRegion toUpdate;
 	for (Animations::iterator i = _animations.begin(); i != _animations.end();) {
 		int index = qAbs(i.key()) - 1, tab = (index / MatrixRowShift), sel = index % MatrixRowShift;
 		float64 dt = float64(now - i.value()) / st::emojiPanDuration;
 		if (dt >= 1) {
-			(index == SwitcherSelected ? _switcherHover : _hovers[tab][sel]) = (i.key() > 0) ? 1 : 0;
+			_hovers[tab][sel] = (i.key() > 0) ? 1 : 0;
 			i = _animations.erase(i);
 		} else {
-			(index == SwitcherSelected ? _switcherHover : _hovers[tab][sel]) = (i.key() > 0) ? dt : (1 - dt);
+			_hovers[tab][sel] = (i.key() > 0) ? dt : (1 - dt);
 			++i;
 		}
+		toUpdate += emojiRect(tab, sel);
 	}
-	update();
+	rtlupdate(toUpdate.boundingRect());
 	return !_animations.isEmpty();
 }
 
@@ -1137,13 +1191,13 @@ void EmojiPanInner::showEmojiPack(DBIEmojiTab packIndex) {
 	update();
 }
 
-StickerPanInner::StickerPanInner(QWidget *parent) : TWidget(parent), _maxHeight(st::emojiPanMaxHeight),
-_top(0), _selected(-1), _pressedSel(-1),
-_switcherHover(0), _emojiWidth(st::emojiPanHeaderFont->m.width(lang(lng_switch_emoji))) {
-	resize(st::emojiPanWidth, countHeight());
+StickerPanInner::StickerPanInner() : _maxHeight(st::emojiPanMaxHeight),
+_top(0), _selected(-1), _pressedSel(-1) {
+	resize(st::emojiPanWidth - st::emojiScroll.width, countHeight());
 
 	setMouseTracking(true);
 	setFocusPolicy(Qt::NoFocus);
+	setAttribute(Qt::WA_OpaquePaintEvent);
 
 	connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(update()));
 	
@@ -1152,16 +1206,13 @@ _switcherHover(0), _emojiWidth(st::emojiPanHeaderFont->m.width(lang(lng_switch_e
 
 void StickerPanInner::setMaxHeight(int32 h) {
 	_maxHeight = h;
-	resize(st::emojiPanWidth, countHeight());
+	resize(st::emojiPanWidth - st::emojiScroll.width, countHeight());
 }
 
 void StickerPanInner::setScrollTop(int top) {
 	if (top == _top) return;
 
-	QRegion upd = QRect(0, _top, width(), st::emojiPanHeader);
 	_top = top;
-	upd += QRect(0, _top, width(), st::emojiPanHeader);
-	repaint(upd);
 	updateSelected();
 }
 
@@ -1176,6 +1227,23 @@ int StickerPanInner::countHeight() {
 	return result + st::stickerPanPadding;
 }
 
+QRect StickerPanInner::stickerRect(int tab, int sel) {
+	int x = 0, y = 0;
+	for (int i = 0; i < _sets.size(); ++i) {
+		if (i == tab) {
+			int rows = (((sel >= _sets.at(i).pack.size()) ? (sel - _sets.at(i).pack.size()) : sel) / StickerPanPerRow);
+			y += st::emojiPanHeader + rows * st::stickerPanSize.height();
+			x = st::stickerPanPadding + ((sel % StickerPanPerRow) * st::stickerPanSize.width());
+			break;
+		} else {
+			int cnt = _sets.at(i).pack.size();
+			int rows = (cnt / StickerPanPerRow) + ((cnt % StickerPanPerRow) ? 1 : 0);
+			y += st::emojiPanHeader + rows * st::stickerPanSize.height();
+		}
+	}
+	return QRect(x, y, st::stickerPanSize.width(), st::stickerPanSize.height());
+}
+
 void StickerPanInner::paintEvent(QPaintEvent *e) {
 	Painter p(this);
 	QRect r = e ? e->rect() : rect();
@@ -1183,6 +1251,15 @@ void StickerPanInner::paintEvent(QPaintEvent *e) {
 		p.setClipRect(r);
 	}
 	p.fillRect(r, st::white->b);
+
+	int32 fromcol = floorclamp(r.x() - st::stickerPanPadding, st::stickerPanSize.width(), 0, StickerPanPerRow);
+	int32 tocol = ceilclamp(r.x() + r.width() - st::stickerPanPadding, st::stickerPanSize.width(), 0, StickerPanPerRow);
+	if (rtl()) {
+		qSwap(fromcol, tocol);
+		fromcol = StickerPanPerRow - fromcol;
+		tocol = StickerPanPerRow - tocol;
+	}
+
 	int32 y, tilly = 0;
 	for (int c = 0, l = _sets.size(); c < l; ++c) {
 		y = tilly;
@@ -1194,22 +1271,10 @@ void StickerPanInner::paintEvent(QPaintEvent *e) {
 		bool special = (_sets[c].flags & MTPDstickerSet_flag_official);
 		y += st::emojiPanHeader;
 
-		QString title = _sets[c].title;
-		if (r.bottom() <= y) {
-			p.setFont(st::emojiPanHeaderFont->f);
-			p.setPen(st::emojiPanHeaderColor->p);
-			p.drawTextLeft(st::emojiPanHeaderLeft, qMax(y - int(st::emojiPanHeader), _top) + st::emojiPanHeaderTop, width(), title);
-			if (!special && y >= _top + 2 * st::emojiPanHeader) {
-				p.setOpacity(st::stickerPanDeleteOpacity + (1 - st::stickerPanDeleteOpacity) * _sets[c].hovers[size]);
-				p.drawSpriteRight(QPoint(st::emojiPanHeaderLeft, y - (st::emojiPanHeader + st::notifyClose.icon.pxHeight()) / 2), width(), st::notifyClose.icon);
-				p.setOpacity(1);
-			}
-			break;
-		}
-
-		int32 fromrow = (r.top() <= y) ? 0 : qMax(qFloor((r.top() - y) / st::stickerPanSize.height()), 0), torow = qMin(qCeil((r.bottom() - y) / st::stickerPanSize.height()) + 1, rows);
+		int32 fromrow = floorclamp(r.y() - y, st::stickerPanSize.height(), 0, rows);
+		int32 torow = ceilclamp(r.y() + r.height() - y, st::stickerPanSize.height(), 0, rows);
 		for (int32 i = fromrow; i < torow; ++i) {
-			for (int32 j = 0; j < StickerPanPerRow; ++j) {
+			for (int32 j = fromcol; j < tocol; ++j) {
 				int32 index = i * StickerPanPerRow + j;
 				if (index >= size) break;
 
@@ -1266,24 +1331,7 @@ void StickerPanInner::paintEvent(QPaintEvent *e) {
 				}
 			}
 		}
-
-		if (y - st::emojiPanHeader < _top) {
-			p.fillRect(QRect(0, qMin(_top, tilly - int(st::emojiPanHeader)), width(), st::emojiPanHeader), st::emojiPanHeaderBg->b);
-		} else if (!special && y >= _top + 2 * st::emojiPanHeader) {
-			p.setOpacity(st::stickerPanDeleteOpacity + (1 - st::stickerPanDeleteOpacity) * _sets[c].hovers[size]);
-			p.drawSpriteRight(QPoint(st::emojiPanHeaderLeft, y - (st::emojiPanHeader + st::notifyClose.icon.pxHeight()) / 2), width(), st::notifyClose.icon);
-			p.setOpacity(1);
-		}
-
-		p.setFont(st::emojiPanHeaderFont->f);
-		p.setPen(st::emojiPanHeaderColor->p);
-		p.drawTextLeft(st::emojiPanHeaderLeft, qMin(qMax(y - int(st::emojiPanHeader), _top), tilly - int(st::emojiPanHeader)) + st::emojiPanHeaderTop, width(), title);
 	}
-
-	p.setFont(st::emojiPanHeaderFont->f);
-	p.setPen(st::emojiSwitchColor->p);
-	p.drawTextRight(st::emojiSwitchImgSkip - st::emojiSwitchEmoji.pxWidth(), _top + st::emojiPanHeaderTop, width(), lang(lng_switch_emoji), _emojiWidth);
-	p.drawSpriteRight(QPoint(st::emojiSwitchSkip + _emojiWidth - st::emojiSwitchEmoji.pxWidth(), _top + (st::emojiPanHeader - st::emojiSwitchEmoji.pxHeight()) / 2), width(), st::emojiSwitchEmoji);
 }
 
 void StickerPanInner::mousePressEvent(QMouseEvent *e) {
@@ -1301,10 +1349,6 @@ void StickerPanInner::mouseReleaseEvent(QMouseEvent *e) {
 	updateSelected();
 
 	if (_selected < 0 || _selected != pressed) return;
-	if (_selected == SwitcherSelected) {
-		emit switchToEmoji();
-		return;
-	}
 	if (_selected >= MatrixRowShift * _sets.size()) {
 		return;
 	}
@@ -1347,8 +1391,6 @@ void StickerPanInner::mouseReleaseEvent(QMouseEvent *e) {
 	}
 	if (sel < _sets[tab].pack.size()) {
 		emit selected(_sets[tab].pack[sel]);
-	} else if (sel == _sets[tab].pack.size()) {
-		emit removing(_sets[tab].id);
 	}
 }
 
@@ -1375,9 +1417,25 @@ void StickerPanInner::clearSelection(bool fast) {
 	if (fast) {
 		for (Animations::const_iterator i = _animations.cbegin(); i != _animations.cend(); ++i) {
 			int index = qAbs(i.key()) - 1, tab = (index / MatrixRowShift), sel = index % MatrixRowShift;
-			(index == SwitcherSelected ? _switcherHover : _sets[tab].hovers[sel]) = 0;
+			_sets[tab].hovers[sel] = 0;
 		}
 		_animations.clear();
+		if (_selected >= 0) {
+			int index = qAbs(_selected), tab = (index / MatrixRowShift), sel = index % MatrixRowShift;
+			if (index >= 0 && tab < _sets.size() && _sets[tab].id == RecentStickerSetId && sel >= tab * MatrixRowShift + _sets[tab].pack.size()) {
+				_sets[tab].hovers[sel] = 0;
+				sel -= _sets[tab].pack.size();
+			}
+			_sets[tab].hovers[sel] = 0;
+		}
+		if (_pressedSel >= 0) {
+			int index = qAbs(_pressedSel), tab = (index / MatrixRowShift), sel = index % MatrixRowShift;
+			if (index >= 0 && tab < _sets.size() && _sets[tab].id == RecentStickerSetId && sel >= tab * MatrixRowShift + _sets[tab].pack.size()) {
+				_sets[tab].hovers[sel] = 0;
+				sel -= _sets[tab].pack.size();
+			}
+			_sets[tab].hovers[sel] = 0;
+		}
 		_selected = _pressedSel = -1;
 		anim::stop(this);
 	} else {
@@ -1457,9 +1515,7 @@ void StickerPanInner::appendSet(uint64 setId) {
 	for (int32 i = 0, l = it->stickers.size(); i < l; ++i) {
 		pack.push_back(it->stickers.at(i));
 	}
-	int32 availw = width() - st::emojiPanHeaderLeft - st::emojiSwitchSkip - _emojiWidth - (st::emojiSwitchSkip - st::emojiSwitchImgSkip);
-	QString title = st::emojiPanHeaderFont->m.elidedText(it->title, Qt::ElideRight, availw);
-	_sets.push_back(DisplayedSet(it->id, it->flags, title, pack.size() + 1, pack));
+	_sets.push_back(DisplayedSet(it->id, it->flags, it->title, pack.size() + 1, pack));
 }
 
 void StickerPanInner::refreshRecent(bool performResize) {
@@ -1501,7 +1557,10 @@ void StickerPanInner::refreshRecent(bool performResize) {
 
 	if (performResize) {
 		int32 h = countHeight();
-		if (h != height()) resize(width(), h);
+		if (h != height()) {
+			resize(width(), h);
+			emit needRefreshPanels();
+		}
 
 		updateSelected();
 	}
@@ -1532,49 +1591,71 @@ void StickerPanInner::fillIcons(QVector<StickerIcon> &icons) {
 	}
 }
 
+void StickerPanInner::fillPanels(QVector<EmojiPanel*> &panels) {
+	for (int32 i = 0; i < panels.size(); ++i) {
+		panels.at(i)->hide();
+		panels.at(i)->deleteLater();
+	}
+	panels.clear();
+	if (_sets.isEmpty()) return;
+
+	int y = 0;
+	panels.reserve(_sets.size());
+	for (int32 i = 0, l = _sets.size(); i < l; ++i) {
+		bool special = (_sets.at(i).flags & MTPDstickerSet_flag_official);
+		panels.push_back(new EmojiPanel(parentWidget(), _sets.at(i).title, _sets.at(i).id, special, y));
+		panels.back()->show();
+		connect(panels.back(), SIGNAL(deleteClicked(quint64)), this, SIGNAL(removing(quint64)));
+		int cnt = _sets.at(i).pack.size(), rows = (cnt / StickerPanPerRow) + ((cnt % StickerPanPerRow) ? 1 : 0);
+		int h = st::emojiPanHeader + rows * st::stickerPanSize.height();
+		y += h;
+	}
+}
+
+
+void StickerPanInner::refreshPanels(QVector<EmojiPanel*> &panels) {
+	if (panels.size() != _sets.size()) return fillPanels(panels);
+
+	int32 y = 0;
+	for (int32 i = 0, l = _sets.size(); i < l; ++i) {
+		panels.at(i)->setWantedY(y);
+		int cnt = _sets.at(i).pack.size(), rows = (cnt / StickerPanPerRow) + ((cnt % StickerPanPerRow) ? 1 : 0);
+		int h = st::emojiPanHeader + rows * st::stickerPanSize.height();
+		y += h;
+	}
+}
+
 void StickerPanInner::updateSelected() {
 	if (_pressedSel >= 0) return;
 
 	int32 selIndex = -1;
 	QPoint p(mapFromGlobal(_lastMousePos));
 
-	if (p.y() < _top + st::emojiPanHeader) {
-		bool upon1 = rtl() && p.x() >= 0 && p.x() < st::emojiSwitchSkip + _emojiWidth + (st::emojiSwitchSkip - st::emojiSwitchImgSkip);
-		bool upon2 = !rtl() && p.x() < width() && p.x() >= width() - st::emojiSwitchSkip - _emojiWidth - (st::emojiSwitchSkip - st::emojiSwitchImgSkip);
-		if (upon1 || upon2) {
-			selIndex = SwitcherSelected;
-		}
-	} else {
-		int y, ytill = 0, sx = (rtl() ? width() - p.x() : p.x()) - st::stickerPanPadding;
-		for (int c = 0, l = _sets.size(); c < l; ++c) {
-			const DisplayedSet &set(_sets.at(c));
-			int cnt = set.pack.size();
-			bool special = (set.flags & MTPDstickerSet_flag_official);
+	int y, ytill = 0, sx = (rtl() ? width() - p.x() : p.x()) - st::stickerPanPadding;
+	for (int c = 0, l = _sets.size(); c < l; ++c) {
+		const DisplayedSet &set(_sets.at(c));
+		int cnt = set.pack.size();
+		bool special = (set.flags & MTPDstickerSet_flag_official);
 
-			y = ytill;
-			ytill = y + st::emojiPanHeader + ((cnt / StickerPanPerRow) + ((cnt % StickerPanPerRow) ? 1 : 0)) * st::stickerPanSize.height();
-			if (p.y() >= y && p.y() < ytill) {
-				if (!special && p.y() >= y && p.y() < y + st::emojiPanHeader && sx + st::stickerPanPadding >= width() - st::emojiPanHeaderLeft - st::notifyClose.icon.pxWidth() && sx + st::stickerPanPadding < width() - st::emojiPanHeaderLeft) {
-					selIndex = c * MatrixRowShift + set.pack.size();
+		y = ytill;
+		ytill = y + st::emojiPanHeader + ((cnt / StickerPanPerRow) + ((cnt % StickerPanPerRow) ? 1 : 0)) * st::stickerPanSize.height();
+		if (p.y() >= y && p.y() < ytill) {
+			y += st::emojiPanHeader;
+			if (p.y() >= y && sx >= 0 && sx < StickerPanPerRow * st::stickerPanSize.width()) {
+				selIndex = qFloor((p.y() - y) / st::stickerPanSize.height()) * StickerPanPerRow + qFloor(sx / st::stickerPanSize.width());
+				if (selIndex >= set.pack.size()) {
+					selIndex = -1;
 				} else {
-					y += st::emojiPanHeader;
-					if (p.y() >= y && sx >= 0 && sx < StickerPanPerRow * st::stickerPanSize.width()) {
-						selIndex = qFloor((p.y() - y) / st::stickerPanSize.height()) * StickerPanPerRow + qFloor(sx / st::stickerPanSize.width());
-						if (selIndex >= set.pack.size()) {
-							selIndex = -1;
-						} else {
-							if (set.id == RecentStickerSetId && _custom[selIndex]) {
-								int32 inx = sx - (selIndex % StickerPanPerRow) * st::stickerPanSize.width(), iny = p.y() - y - ((selIndex / StickerPanPerRow) * st::stickerPanSize.height());
-								if (inx >= st::stickerPanSize.width() - st::stickerPanDelete.pxWidth() && iny < st::stickerPanDelete.pxHeight()) {
-									selIndex += set.pack.size();
-								}
-							}
-							selIndex += c * MatrixRowShift;
+					if (set.id == RecentStickerSetId && _custom[selIndex]) {
+						int32 inx = sx - (selIndex % StickerPanPerRow) * st::stickerPanSize.width(), iny = p.y() - y - ((selIndex / StickerPanPerRow) * st::stickerPanSize.height());
+						if (inx >= st::stickerPanSize.width() - st::stickerPanDelete.pxWidth() && iny < st::stickerPanDelete.pxHeight()) {
+							selIndex += set.pack.size();
 						}
 					}
+					selIndex += c * MatrixRowShift;
 				}
-				break;
 			}
+			break;
 		}
 	}
 
@@ -1627,18 +1708,20 @@ void StickerPanInner::updateSelected() {
 
 bool StickerPanInner::animStep(float64 ms) {
 	uint64 now = getms();
+	QRegion toUpdate;
 	for (Animations::iterator i = _animations.begin(); i != _animations.end();) {
 		int index = qAbs(i.key()) - 1, tab = (index / MatrixRowShift), sel = index % MatrixRowShift;
 		float64 dt = float64(now - i.value()) / st::emojiPanDuration;
 		if (dt >= 1) {
-			(index == SwitcherSelected ? _switcherHover : _sets[tab].hovers[sel]) = (i.key() > 0) ? 1 : 0;
+			_sets[tab].hovers[sel] = (i.key() > 0) ? 1 : 0;
 			i = _animations.erase(i);
 		} else {
-			(index == SwitcherSelected ? _switcherHover : _sets[tab].hovers[sel]) = (i.key() > 0) ? dt : (1 - dt);
+			_sets[tab].hovers[sel] = (i.key() > 0) ? dt : (1 - dt);
 			++i;
 		}
+		toUpdate += stickerRect(tab, sel);
 	}
-	update();
+	rtlupdate(toUpdate.boundingRect());
 	return !_animations.isEmpty();
 }
 
@@ -1659,6 +1742,89 @@ void StickerPanInner::showStickerSet(uint64 setId) {
 	update();
 }
 
+EmojiPanel::EmojiPanel(QWidget *parent, const QString &text, uint64 setId, bool special, int32 wantedY) : TWidget(parent),
+_wantedY(wantedY), _setId(setId), _special(special), _deleteVisible(false), _delete(special ? 0 : new IconedButton(this, st::notifyClose)) { // NoneStickerSetId if in emoji
+	resize(st::emojiPanWidth, st::emojiPanHeader);
+	setMouseTracking(true);
+	setFocusPolicy(Qt::NoFocus);
+	setText(text);
+	if (_delete) {
+		_delete->hide();
+		_delete->moveToRight(st::emojiPanHeaderLeft - ((_delete->width() - st::notifyClose.icon.pxWidth()) / 2), (st::emojiPanHeader - _delete->height()) / 2, width());
+		connect(_delete, SIGNAL(clicked()), this, SLOT(onDelete()));
+	}
+}
+
+void EmojiPanel::onDelete() {
+	emit deleteClicked(_setId);
+}
+
+void EmojiPanel::setText(const QString &text) {
+	_fullText = text;
+	updateText();
+}
+
+void EmojiPanel::updateText() {
+	int32 availw = st::emojiPanWidth - st::emojiPanHeaderLeft * 2;
+	if (_deleteVisible) {
+		if (!_special && _setId != NoneStickerSetId) {
+			availw -= st::notifyClose.icon.pxWidth() + st::emojiPanHeaderLeft;
+		}
+	} else {
+		QString switchText = lang((_setId != NoneStickerSetId) ? lng_switch_emoji : lng_switch_stickers);
+		availw -= st::emojiSwitchSkip + st::emojiPanHeaderFont->m.width(switchText);
+	}
+	_text = st::emojiPanHeaderFont->m.elidedText(_fullText, Qt::ElideRight, availw);
+	update();
+}
+
+void EmojiPanel::setDeleteVisible(bool isVisible) {
+	if (_deleteVisible != isVisible) {
+		_deleteVisible = isVisible;
+		updateText();
+		if (_delete) {
+			_delete->setVisible(_deleteVisible);
+		}
+	}
+}
+
+void EmojiPanel::mousePressEvent(QMouseEvent *e) {
+	emit mousePressed();
+}
+
+void EmojiPanel::paintEvent(QPaintEvent *e) {
+	Painter p(this);
+
+	if (!_deleteVisible) {
+		p.fillRect(0, 0, width(), st::emojiPanHeader, st::emojiPanHeaderBg->b);
+	}
+	p.setFont(st::emojiPanHeaderFont->f);
+	p.setPen(st::emojiPanHeaderColor->p);
+	p.drawTextLeft(st::emojiPanHeaderLeft, st::emojiPanHeaderTop, width(), _text);
+}
+
+EmojiSwitchButton::EmojiSwitchButton(QWidget *parent, bool toStickers) : Button(parent),
+_toStickers(toStickers), _text(lang(_toStickers ? lng_switch_stickers : lng_switch_emoji)),
+_textWidth(st::emojiPanHeaderFont->m.width(_text)) {
+	int32 w = st::emojiSwitchSkip + _textWidth + (st::emojiSwitchSkip - st::emojiSwitchImgSkip);
+	setCursor(style::cur_pointer);
+	resize(w, st::emojiPanHeader);
+}
+
+void EmojiSwitchButton::paintEvent(QPaintEvent *e) {
+	Painter p(this);
+
+	p.setFont(st::emojiPanHeaderFont->f);
+	p.setPen(st::emojiSwitchColor->p);
+	if (_toStickers) {
+		p.drawTextRight(st::emojiSwitchSkip, st::emojiPanHeaderTop, width(), _text, _textWidth);
+		p.drawSpriteRight(QPoint(st::emojiSwitchImgSkip - st::emojiSwitchStickers.pxWidth(), (st::emojiPanHeader - st::emojiSwitchStickers.pxHeight()) / 2), width(), st::emojiSwitchStickers);
+	} else {
+		p.drawTextRight(st::emojiSwitchImgSkip - st::emojiSwitchEmoji.pxWidth(), st::emojiPanHeaderTop, width(), lang(lng_switch_emoji), _textWidth);
+		p.drawSpriteRight(QPoint(st::emojiSwitchSkip + _textWidth - st::emojiSwitchEmoji.pxWidth(), (st::emojiPanHeader - st::emojiSwitchEmoji.pxHeight()) / 2), width(), st::emojiSwitchEmoji);
+	}
+}
+
 EmojiPan::EmojiPan(QWidget *parent) : TWidget(parent), _maxHeight(st::emojiPanMaxHeight),
 _horizontal(false), _noTabUpdate(false), _hiding(false), a_opacity(0), _shadow(st::dropdownDef.shadow),
 _recent(this     , qsl("emoji_group"), dbietRecent     , QString(), true , st::rbEmojiRecent),
@@ -1673,7 +1839,8 @@ _iconOver(-1), _iconSel(0), _iconDown(-1), _iconsDragging(false),
 _iconAnim(animFunc(this, &EmojiPan::iconAnim)),
 _iconsLeft(0), _iconsTop(0), _iconsStartX(0), _iconsMax(0), _iconsX(0, 0), _iconSelX(0, 0), _iconsStartAnim(0),
 _stickersShown(false), _moveStart(0),
-e_scroll(this, st::emojiScroll), e_inner(), s_scroll(this, st::emojiScroll), s_inner(), _removingSetId(0) {
+e_scroll(this, st::emojiScroll), e_inner(), e_switch(&e_scroll, true),
+s_scroll(this, st::emojiScroll), s_inner(), s_switch(&s_scroll, false), _removingSetId(0) {
 	setFocusPolicy(Qt::NoFocus);
 	e_scroll.setFocusPolicy(Qt::NoFocus);
 	e_scroll.viewport()->setFocusPolicy(Qt::NoFocus);
@@ -1692,10 +1859,8 @@ e_scroll(this, st::emojiScroll), e_inner(), s_scroll(this, st::emojiScroll), s_i
 	s_scroll.move(st::dropdownDef.padding.left(), st::dropdownDef.padding.top());
 	s_scroll.setWidget(&s_inner);
 
-	e_inner.setAttribute(Qt::WA_OpaquePaintEvent);
-	e_scroll.setAutoFillBackground(true);
-	s_inner.setAttribute(Qt::WA_OpaquePaintEvent);
-	s_scroll.setAutoFillBackground(true);
+	e_inner.moveToLeft(0, 0, e_scroll.width());
+	s_inner.moveToLeft(0, 0, s_scroll.width());
 
 	int32 left = _iconsLeft = st::dropdownDef.padding.left() + (st::emojiPanWidth - 8 * st::rbEmoji.width) / 2;
 	int32 top = _iconsTop = st::dropdownDef.padding.top() + _maxHeight - st::rbEmoji.height;
@@ -1707,6 +1872,8 @@ e_scroll(this, st::emojiScroll), e_inner(), s_scroll(this, st::emojiScroll), s_i
 	prepareTab(left, top, _width, _activity);
 	prepareTab(left, top, _width, _travel);
 	prepareTab(left, top, _width, _objects);
+	e_inner.fillPanels(e_panels);
+	updatePanelsPositions(e_panels, 0);
 
 	_hideTimer.setSingleShot(true);
 	connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideStart()));
@@ -1722,11 +1889,15 @@ e_scroll(this, st::emojiScroll), e_inner(), s_scroll(this, st::emojiScroll), s_i
 	connect(&e_inner, SIGNAL(selected(EmojiPtr)), this, SIGNAL(emojiSelected(EmojiPtr)));
 	connect(&s_inner, SIGNAL(selected(DocumentData*)), this, SIGNAL(stickerSelected(DocumentData*)));
 
-	connect(&e_inner, SIGNAL(switchToStickers()), this, SLOT(onSwitch()));
-	connect(&s_inner, SIGNAL(switchToEmoji()), this, SLOT(onSwitch()));
+	connect(&s_switch, SIGNAL(clicked()), this, SLOT(onSwitch()));
+	connect(&e_switch, SIGNAL(clicked()), this, SLOT(onSwitch()));
+	s_switch.moveToRight(0, 0, st::emojiPanWidth);
+	e_switch.moveToRight(0, 0, st::emojiPanWidth);
 
-	connect(&s_inner, SIGNAL(removing(uint64)), this, SLOT(onRemoveSet(uint64)));
+	connect(&s_inner, SIGNAL(removing(quint64)), this, SLOT(onRemoveSet(quint64)));
 	connect(&s_inner, SIGNAL(refreshIcons()), this, SLOT(onRefreshIcons()));
+	connect(&e_inner, SIGNAL(needRefreshPanels()), this, SLOT(onRefreshPanels()));
+	connect(&s_inner, SIGNAL(needRefreshPanels()), this, SLOT(onRefreshPanels()));
 
 	if (cPlatform() == dbipMac) {
 		connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWndActiveChanged()));
@@ -1798,6 +1969,7 @@ void EmojiPan::paintEvent(QPaintEvent *e) {
 
 	if (_toCache.isNull()) {
 		if (_cache.isNull()) {
+			p.fillRect(rtlrect(r.x() + r.width() - st::emojiScroll.width, r.y(), st::emojiScroll.width, e_scroll.height(), width()), st::white->b);
 			if (_stickersShown) {
 				p.fillRect(r.left(), _iconsTop, r.width(), st::rbEmoji.height, st::emojiPanCategories->b);
 				if (!_icons.isEmpty()) {
@@ -2028,6 +2200,7 @@ void EmojiPan::onRefreshIcons() {
 	_iconHovers.clear();
 	_iconAnimations.clear();
 	s_inner.fillIcons(_icons);
+	s_inner.fillPanels(s_panels);
 	_iconsX = anim::ivalue(0, 0);
 	_iconSelX.finish();
 	_iconsStartAnim = 0;
@@ -2038,11 +2211,21 @@ void EmojiPan::onRefreshIcons() {
 		_iconHovers = QVector<float64>(_icons.size(), 0);
 		_iconsMax = qMax(int((_icons.size() - 8) * st::rbEmoji.width), 0);
 	}
+	updatePanelsPositions(s_panels, s_scroll.scrollTop());
 	updateSelected();
-
 	updateIcons();
 }
 
+void EmojiPan::onRefreshPanels() {
+	s_inner.refreshPanels(s_panels);
+	e_inner.refreshPanels(e_panels);
+	if (_stickersShown) {
+		updatePanelsPositions(s_panels, s_scroll.scrollTop());
+	} else {
+		updatePanelsPositions(e_panels, e_scroll.scrollTop());
+	}
+}
+
 void EmojiPan::leaveToChildEvent(QEvent *e) {
 	if (!_stickersShown) return;
 	_iconsMousePos = QCursor::pos();
@@ -2334,10 +2517,23 @@ void EmojiPan::onTabChange() {
 	e_inner.showEmojiPack(newTab);
 }
 
+void EmojiPan::updatePanelsPositions(const QVector<EmojiPanel*> &panels, int32 st) {
+	for (int32 i = 0, l = panels.size(); i < l; ++i) {
+		int32 y = panels.at(i)->wantedY() - st;
+		if (y < 0) {
+			y = (i + 1 < l) ? qMin(panels.at(i + 1)->wantedY() - st - int(st::emojiPanHeader), 0) : 0;
+		}
+		panels.at(i)->move(0, y);
+		panels.at(i)->setDeleteVisible(y >= st::emojiPanHeader);
+	}
+}
+
 void EmojiPan::onScroll() {
-	int top = e_scroll.scrollTop();
+	int st = e_scroll.scrollTop();
 	if (!_stickersShown) {
-		DBIEmojiTab tab = e_inner.currentTab(top);
+		updatePanelsPositions(e_panels, st);
+
+		DBIEmojiTab tab = e_inner.currentTab(st);
 		FlatRadiobutton *check = 0;
 		switch (tab) {
 			case dbietRecent     : check = &_recent     ; break;
@@ -2355,11 +2551,13 @@ void EmojiPan::onScroll() {
 			_noTabUpdate = false;
 		}
 	}
-	e_inner.setScrollTop(top);
+	e_inner.setScrollTop(st);
 
-	top = s_scroll.scrollTop();
+	st = s_scroll.scrollTop();
 	if (_stickersShown) {
-		uint64 setId = s_inner.currentSet(top);
+		updatePanelsPositions(s_panels, st);
+
+		uint64 setId = s_inner.currentSet(st);
 		int32 newSel = 0;
 		for (int32 i = 0, l = _icons.size(); i < l; ++i) {
 			if (_icons.at(i).setId == setId) {
@@ -2377,7 +2575,7 @@ void EmojiPan::onScroll() {
 			updateIcons();
 		}
 	}
-	s_inner.setScrollTop(top);
+	s_inner.setScrollTop(st);
 }
 
 void EmojiPan::onSwitch() {
@@ -2398,6 +2596,10 @@ void EmojiPan::onSwitch() {
 	hideAll();
 	_moveStart = getms();
 
+	if (_stickersShown) {
+		e_inner.hideFinish();
+	}
+
 	a_toCoord = (_stickersShown != rtl()) ? anim::ivalue(st::emojiPanWidth, 0) : anim::ivalue(-st::emojiPanWidth, 0);
 	a_toAlpha = anim::fvalue(0, 1);
 	a_fromCoord = (_stickersShown != rtl()) ? anim::ivalue(0, -st::emojiPanWidth) : anim::ivalue(0, st::emojiPanWidth);
@@ -2407,7 +2609,7 @@ void EmojiPan::onSwitch() {
 	update();
 }
 
-void EmojiPan::onRemoveSet(uint64 setId) {
+void EmojiPan::onRemoveSet(quint64 setId) {
 	StickerSets::const_iterator it = cStickerSets().constFind(setId);
 	if (it != cStickerSets().cend() && !(it->flags & MTPDstickerSet_flag_official)) {
 		_removingSetId = it->id;
@@ -2677,8 +2879,9 @@ void MentionsInner::leaveEvent(QEvent *e) {
 }
 
 void MentionsInner::setSel(int sel, bool scroll) {
+	if (_sel >= 0) update(0, _sel * st::mentionHeight, width(), st::mentionHeight);
 	_sel = sel;
-	parentWidget()->update();
+	if (_sel >= 0) update(0, _sel * st::mentionHeight, width(), st::mentionHeight);
 	int32 maxSel = _rows->isEmpty() ? (_hrows->isEmpty() ? _crows->size() : _hrows->size()) : _rows->size();
 	if (scroll && _sel >= 0 && _sel < maxSel) emit mustScrollTo(_sel * st::mentionHeight, (_sel + 1) * st::mentionHeight);
 }
@@ -2936,6 +3139,7 @@ void MentionsDropdown::hideStart() {
 		_scroll.hide();
 		_hiding = true;
 		a_opacity.start(0);
+		setAttribute(Qt::WA_OpaquePaintEvent, false);
 		anim::start(this);
 	}
 }
@@ -2959,6 +3163,7 @@ void MentionsDropdown::showStart() {
 	_hiding = false;
 	show();
 	a_opacity.start(1);
+	setAttribute(Qt::WA_OpaquePaintEvent, false);
 	anim::start(this);
 }
 
@@ -2968,6 +3173,7 @@ bool MentionsDropdown::animStep(float64 ms) {
 	if (dt >= 1) {
 		a_opacity.finish();
 		_cache = QPixmap();
+		setAttribute(Qt::WA_OpaquePaintEvent);
 		if (_hiding) {
 			hideFinish();
 		} else {
diff --git a/Telegram/SourceFiles/dropdown.h b/Telegram/SourceFiles/dropdown.h
index 8da58424b..e79ec8e0a 100644
--- a/Telegram/SourceFiles/dropdown.h
+++ b/Telegram/SourceFiles/dropdown.h
@@ -47,6 +47,16 @@ public:
 
 	bool eventFilter(QObject *obj, QEvent *e);
 
+	bool overlaps(const QRect &globalRect) {
+		if (isHidden() || animating()) return false;
+
+		return QRect(_st.padding.left(),
+					 _st.padding.top(),
+					 _width - _st.padding.left() - _st.padding.right(),
+					 _height - _st.padding.top() - _st.padding.bottom()
+					 ).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size()));
+	}
+
 signals:
 
 	void hiding();
@@ -108,6 +118,16 @@ public:
 
 	bool animStep(float64 ms);
 
+	bool overlaps(const QRect &globalRect) {
+		if (isHidden() || animating()) return false;
+
+		return QRect(st::dragPadding.left(),
+					 st::dragPadding.top(),
+					 width() - st::dragPadding.left() - st::dragPadding.right(),
+					 height() - st::dragPadding.top() - st::dragPadding.bottom()
+					 ).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size()));
+	}
+
 signals:
 
 	void dropped(const QMimeData *data);
@@ -132,6 +152,7 @@ private:
 
 };
 
+class EmojiPanel;
 static const int EmojiColorsCount = 5;
 
 class EmojiColorPicker : public TWidget, public Animated {
@@ -139,7 +160,7 @@ class EmojiColorPicker : public TWidget, public Animated {
 
 public:
 
-	EmojiColorPicker(QWidget *parent);
+	EmojiColorPicker();
 
 	void showEmoji(uint32 code);
 
@@ -193,14 +214,12 @@ private:
 
 };
 
-static const int32 SwitcherSelected = (INT_MAX / 2);
-
 class EmojiPanInner : public TWidget, public Animated {
 	Q_OBJECT
 
 public:
 
-	EmojiPanInner(QWidget *parent = 0);
+	EmojiPanInner();
 
 	void setMaxHeight(int32 h);
 	void paintEvent(QPaintEvent *e);
@@ -224,6 +243,9 @@ public:
 	void refreshRecent();
 
 	void setScrollTop(int top);
+
+	void fillPanels(QVector<EmojiPanel*> &panels);
+	void refreshPanels(QVector<EmojiPanel*> &panels);
 	
 public slots:
 
@@ -234,6 +256,8 @@ public slots:
 	void onPickerHidden();
 	void onColorSelected(EmojiPtr emoji);
 
+	bool checkPickerHide();
+
 signals:
 
 	void selected(EmojiPtr emoji);
@@ -243,6 +267,8 @@ signals:
 	void scrollToY(int y);
 	void disableScroll(bool dis);
 
+	void needRefreshPanels();
+
 private:
 
 	int32 _maxHeight;
@@ -250,6 +276,8 @@ private:
 	int32 countHeight();
 	void selectEmoji(EmojiPtr emoji);
 
+	QRect emojiRect(int tab, int sel);
+
 	typedef QMap<int32, uint64> Animations; // index - showing, -index - hiding
 	Animations _animations;
 
@@ -267,9 +295,6 @@ private:
 
 	EmojiColorPicker _picker;
 	QTimer _showPickerTimer;
-
-	float64 _switcherHover;
-	int32 _stickersWidth;
 };
 
 struct StickerIcon {
@@ -287,7 +312,7 @@ class StickerPanInner : public TWidget, public Animated {
 
 public:
 
-	StickerPanInner(QWidget *parent = 0);
+	StickerPanInner();
 
 	void setMaxHeight(int32 h);
 	void paintEvent(QPaintEvent *e);
@@ -309,6 +334,8 @@ public:
 	void refreshRecent(bool resize = true);
 
 	void fillIcons(QVector<StickerIcon> &icons);
+	void fillPanels(QVector<EmojiPanel*> &panels);
+	void refreshPanels(QVector<EmojiPanel*> &panels);
 
 	void setScrollTop(int top);
 	void preloadImages();
@@ -322,7 +349,7 @@ public slots:
 signals:
 
 	void selected(DocumentData *sticker);
-	void removing(uint64 setId);
+	void removing(quint64 setId);
 
 	void refreshIcons();
 
@@ -330,6 +357,7 @@ signals:
 
 	void scrollToY(int y);
 	void disableScroll(bool dis);
+	void needRefreshPanels();
 
 private:
 
@@ -339,6 +367,7 @@ private:
 
 	int32 countHeight();
 	void selectEmoji(EmojiPtr emoji);
+	QRect stickerRect(int tab, int sel);
 
 	typedef QMap<int32, uint64> Animations; // index - showing, -index - hiding
 	Animations _animations;
@@ -359,9 +388,62 @@ private:
 
 	int32 _selected, _pressedSel;
 	QPoint _lastMousePos;
+};
+
+class EmojiPanel : public TWidget {
+	Q_OBJECT
+
+public:
+
+	EmojiPanel(QWidget *parent, const QString &text, uint64 setId, bool special, int32 wantedY); // NoneStickerSetId if in emoji
+	void setText(const QString &text);
+	void setDeleteVisible(bool isVisible);
+
+	void paintEvent(QPaintEvent *e);
+	void mousePressEvent(QMouseEvent *e);
+
+	int32 wantedY() const {
+		return _wantedY;
+	}
+	void setWantedY(int32 y) {
+		_wantedY = y;
+	}
+
+signals:
+
+	void deleteClicked(quint64 setId);
+	void mousePressed();
+
+public slots:
+
+	void onDelete();
+
+private:
+
+	void updateText();
+
+	int32 _wantedY;
+	QString _text, _fullText;
+	uint64 _setId;
+	bool _special, _deleteVisible;
+	IconedButton *_delete;
+
+};
+
+class EmojiSwitchButton : public Button {
+	Q_OBJECT
+
+public:
+
+	EmojiSwitchButton(QWidget *parent, bool toStickers); // otherwise toEmoji
+	void paintEvent(QPaintEvent *e);
+
+protected:
+
+	bool _toStickers;
+	QString _text;
+	int32 _textWidth;
 
-	float64 _switcherHover;
-	int32 _emojiWidth;
 };
 
 class EmojiPan : public TWidget, public Animated {
@@ -397,6 +479,16 @@ public:
 	bool eventFilter(QObject *obj, QEvent *e);
 	void stickersInstalled(uint64 setId);
 
+	bool overlaps(const QRect &globalRect) {
+		if (isHidden() || !_cache.isNull()) return false;
+
+		return QRect(st::dropdownDef.padding.left(),
+					 st::dropdownDef.padding.top(),
+					 _width - st::dropdownDef.padding.left() - st::dropdownDef.padding.right(),
+					 _height - st::dropdownDef.padding.top() - st::dropdownDef.padding.bottom()
+					 ).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size()));
+	}
+
 public slots:
 
 	void refreshStickers();
@@ -411,11 +503,12 @@ public slots:
 	void onScroll();
 	void onSwitch();
 
-	void onRemoveSet(uint64 setId);
+	void onRemoveSet(quint64 setId);
 	void onRemoveSetSure();
 	void onDelayedHide();
 
 	void onRefreshIcons();
+	void onRefreshPanels();
 
 signals:
 
@@ -434,6 +527,7 @@ private:
 	void updateIcons();
 
 	void prepareTab(int32 &left, int32 top, int32 _width, FlatRadiobutton &tab);
+	void updatePanelsPositions(const QVector<EmojiPanel*> &panels, int32 st);
 
 	void showAll();
 	void hideAll();
@@ -472,8 +566,12 @@ private:
 
 	ScrollArea e_scroll;
 	EmojiPanInner e_inner;
+	QVector<EmojiPanel*> e_panels;
+	EmojiSwitchButton e_switch;
 	ScrollArea s_scroll;
 	StickerPanInner s_inner;
+	QVector<EmojiPanel*> s_panels;
+	EmojiSwitchButton s_switch;
 
 	uint64 _removingSetId;
 
@@ -557,6 +655,12 @@ public:
 	bool eventFilter(QObject *obj, QEvent *e);
 	QString getSelected() const;
 
+	bool overlaps(const QRect &globalRect) {
+		if (isHidden() || !testAttribute(Qt::WA_OpaquePaintEvent)) return false;
+
+		return rect().contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size()));
+	}
+
 	~MentionsDropdown();
 
 signals:
diff --git a/Telegram/SourceFiles/gui/countryinput.cpp b/Telegram/SourceFiles/gui/countryinput.cpp
index d0d2fa7f7..ec85de36e 100644
--- a/Telegram/SourceFiles/gui/countryinput.cpp
+++ b/Telegram/SourceFiles/gui/countryinput.cpp
@@ -304,11 +304,8 @@ void CountryList::paintEvent(QPaintEvent *e) {
 
 	int l = countriesNow->size();
 	if (l) {
-		int from = (r.top() > _st.verticalMargin) ? (r.top() - _st.verticalMargin) / _st.rowHeight : 0, to = from + (r.height() / _st.rowHeight) + 2;
-		if (to >= l) {
-			if (from >= l) return;
-			to = l;
-		}
+		int32 from = floorclamp(r.y() - _st.verticalMargin, _st.rowHeight, 0, l);
+		int32 to = ceilclamp(r.y() + r.height() - _st.verticalMargin, _st.rowHeight, 0, l);
 		p.setFont(_st.font->f);
 		QRectF textRect(_st.margin + _st.borderMargin, _st.verticalMargin + from * _st.rowHeight, width() - 2 * _st.margin - 2 * _st.borderMargin, _st.rowHeight - _st.borderWidth);
 		for (int i = from; i < to; ++i) {
diff --git a/Telegram/SourceFiles/gui/images.cpp b/Telegram/SourceFiles/gui/images.cpp
index 67a704400..a03cd513c 100644
--- a/Telegram/SourceFiles/gui/images.cpp
+++ b/Telegram/SourceFiles/gui/images.cpp
@@ -526,6 +526,15 @@ LocalImage::LocalImage(const QPixmap &pixmap, QByteArray format) : Image(format)
 	}
 }
 
+LocalImage::LocalImage(const QByteArray &filecontent, QByteArray fmt, const QPixmap &pixmap) {
+	data = pixmap;
+	format = fmt;
+	saved = filecontent;
+	if (!data.isNull()) {
+		globalAquiredSize += int64(data.width()) * data.height() * 4;
+	}
+}
+
 const QPixmap &LocalImage::pixData() const {
 	return data;
 }
@@ -564,6 +573,10 @@ LocalImage *getImage(const QPixmap &pixmap, QByteArray format) {
 	return new LocalImage(pixmap, format);
 }
 
+LocalImage *getImage(const QByteArray &filecontent, QByteArray format, const QPixmap &pixmap) {
+	return new LocalImage(filecontent, format, pixmap);
+}
+
 void clearStorageImages() {
 	for (StorageImages::const_iterator i = storageImages.cbegin(), e = storageImages.cend(); i != e; ++i) {
 		delete i.value();
diff --git a/Telegram/SourceFiles/gui/images.h b/Telegram/SourceFiles/gui/images.h
index 34df39d39..a77ae6ee8 100644
--- a/Telegram/SourceFiles/gui/images.h
+++ b/Telegram/SourceFiles/gui/images.h
@@ -117,7 +117,8 @@ public:
 	LocalImage(const QString &file, QByteArray format = QByteArray());
 	LocalImage(const QByteArray &filecontent, QByteArray format = QByteArray());
 	LocalImage(const QPixmap &pixmap, QByteArray format = QByteArray());
-	
+	LocalImage(const QByteArray &filecontent, QByteArray format, const QPixmap &pixmap);
+
 	int32 width() const;
 	int32 height() const;
 
@@ -143,6 +144,7 @@ private:
 LocalImage *getImage(const QString &file, QByteArray format);
 LocalImage *getImage(const QByteArray &filecontent, QByteArray format);
 LocalImage *getImage(const QPixmap &pixmap, QByteArray format);
+LocalImage *getImage(const QByteArray &filecontent, QByteArray format, const QPixmap &pixmap);
 
 typedef QPair<uint64, uint64> StorageKey;
 inline uint64 storageMix32To64(int32 a, int32 b) {
@@ -220,6 +222,8 @@ public:
 	}
 	ImagePtr(const QByteArray &filecontent, QByteArray format = QByteArray()) : Parent(getImage(filecontent, format)) {
 	}
+	ImagePtr(const QByteArray &filecontent, QByteArray format, const QPixmap &pixmap) : Parent(getImage(filecontent, format, pixmap)) {
+	}
 	ImagePtr(const QPixmap &pixmap, QByteArray format) : Parent(getImage(pixmap, format)) {
 	}
 	ImagePtr(const StorageImageLocation &location, int32 size = 0) : Parent(getImage(location, size)) {
diff --git a/Telegram/SourceFiles/gui/scrollarea.cpp b/Telegram/SourceFiles/gui/scrollarea.cpp
index cfa8a071b..048e23035 100644
--- a/Telegram/SourceFiles/gui/scrollarea.cpp
+++ b/Telegram/SourceFiles/gui/scrollarea.cpp
@@ -598,7 +598,7 @@ void ScrollArea::touchScrollUpdated(const QPoint &screenPos) {
 
 void ScrollArea::disableScroll(bool dis) {
 	_disabled = dis;
-	if (_disabled) {
+	if (_disabled && _st.hiding) {
 		hor.hideTimeout(0);
 		vert.hideTimeout(0);
 	}
@@ -706,6 +706,7 @@ void ScrollArea::setWidget(QWidget *w) {
 	} else if (!_other && splitted) {
 		_other = new SplittedWidgetOther(this);
 		_other->setAttribute(Qt::WA_OpaquePaintEvent);
+		_other->resize(vert.width(), _other->height());
 		connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(onVerticalScroll()));
 		hor.raise();
 		vert.raise();
@@ -720,7 +721,7 @@ void ScrollArea::setWidget(QWidget *w) {
 		}
 		if (splitted) {
 			splitted->setOtherWidth(vert.width());
-			w->resize(width() - splitted->otherWidth(), w->height());
+			w->setGeometry(rtl() ? splitted->otherWidth() : 0, 0, width() - splitted->otherWidth(), w->height());
 			connect(splitted, SIGNAL(resizeOther()), this, SLOT(onResizeOther()));
 			connect(splitted, SIGNAL(updateOther(const QRect&)), this, SLOT(onUpdateOther(const QRect&)));
 			connect(splitted, SIGNAL(updateOther(const QRegion&)), this, SLOT(onUpdateOther(const QRegion&)));
diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp
index ab6c4b065..9ee6b8e89 100644
--- a/Telegram/SourceFiles/history.cpp
+++ b/Telegram/SourceFiles/history.cpp
@@ -2114,7 +2114,7 @@ void History::updateShowFrom() {
 		--i;
 		for (HistoryBlock::Items::const_iterator j = (*i)->items.cend(); j != (*i)->items.cbegin();) {
 			--j;
-			if ((*j)->type() == HistoryItemMsg && (*j)->id > 0) {
+			if ((*j)->type() == HistoryItemMsg && (*j)->id > 0 && (!(*j)->out() || !showFrom)) {
 				if ((*j)->id >= inboxReadBefore) {
 					showFrom = *j;
 				} else {
@@ -2896,11 +2896,11 @@ int32 HistoryPhoto::resize(int32 width, const HistoryItem *parent) {
 }
 
 const QString HistoryPhoto::inDialogsText() const {
-	return lang(lng_in_dlg_photo);
+	return _caption.isEmpty() ? lang(lng_in_dlg_photo) : _caption.original(0, 0xFFFF, false);
 }
 
 const QString HistoryPhoto::inHistoryText() const {
-	return qsl("[ ") + lang(lng_in_dlg_photo) + qsl(" ]");
+	return qsl("[ ") + lang(lng_in_dlg_photo) + (_caption.isEmpty() ? QString() : (qsl(", ") + _caption.original(0, 0xFFFF))) + qsl(" ]");
 }
 
 const Text &HistoryPhoto::captionForClone() const {
@@ -3230,11 +3230,11 @@ void HistoryVideo::unregItem(HistoryItem *item) {
 }
 
 const QString HistoryVideo::inDialogsText() const {
-	return lang(lng_in_dlg_video);
+	return _caption.isEmpty() ? lang(lng_in_dlg_video) : _caption.original(0, 0xFFFF, false);
 }
 
 const QString HistoryVideo::inHistoryText() const {
-	return qsl("[ ") + lang(lng_in_dlg_video) + qsl(" ]");
+	return qsl("[ ") + lang(lng_in_dlg_video) + (_caption.isEmpty() ? QString() : (qsl(", ") + _caption.original(0, 0xFFFF))) + qsl(" ]");
 }
 
 bool HistoryVideo::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const {
diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp
index 7b2543148..f516c3231 100644
--- a/Telegram/SourceFiles/historywidget.cpp
+++ b/Telegram/SourceFiles/historywidget.cpp
@@ -96,6 +96,8 @@ void HistoryInner::updateMsg(const HistoryItem *msg) {
 }
 
 void HistoryInner::paintEvent(QPaintEvent *e) {
+	if (App::wnd() && App::wnd()->contentOverlapped(this, e)) return;
+
 	if (!App::main()) return;
 
 	QRect r(e->rect());
@@ -107,7 +109,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
 	}
 
 	if (!_firstLoading && botInfo && !botInfo->text.isEmpty() && botDescHeight > 0) {
-		if (r.top() < botDescRect.y() + botDescRect.height() && r.bottom() > botDescRect.y()) {
+		if (r.y() < botDescRect.y() + botDescRect.height() && r.y() + r.height() > botDescRect.y()) {
 			textstyleSet(&st::inTextStyle);
 			App::roundRect(p, botDescRect, st::msgInBg, MessageInCorners, &st::msgInShadow);
 
@@ -131,7 +133,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
 		SelectedItems::const_iterator selEnd = _selected.cend();
 		bool hasSel = !_selected.isEmpty();
 
-		int32 drawToY = r.bottom() - ySkip;
+		int32 drawToY = r.y() + r.height() - ySkip;
 
 		int32 selfromy = 0, seltoy = 0;
 		if (_dragSelFrom && _dragSelTo) {
@@ -1746,8 +1748,8 @@ void BotKeyboard::paintEvent(QPaintEvent *e) {
 		for (; j != s; ++j) {
 			const Button &btn(_btns.at(i).at(j));
 			QRect rect(btn.rect);
-			if (rect.top() >= r.bottom()) break;
-			if (rect.bottom() < r.top()) continue;
+			if (rect.y() >= r.y() + r.height()) break;
+			if (rect.y() + rect.height() < r.y()) continue;
 
 			if (rtl()) rect.moveLeft(width() - rect.left() - rect.width());
 
@@ -3077,6 +3079,14 @@ void HistoryWidget::updateNotifySettings() {
 	_muteUnmute.setText(lang(_history->mute ? lng_channel_unmute : lng_channel_mute));
 }
 
+bool HistoryWidget::contentOverlapped(const QRect &globalRect) {
+	return (_attachDragDocument.overlaps(globalRect) ||
+			_attachDragPhoto.overlaps(globalRect) ||
+			_attachType.overlaps(globalRect) ||
+			_attachMention.overlaps(globalRect) ||
+			_emojiPan.overlaps(globalRect));
+}
+
 void HistoryWidget::updateReportSpamStatus() {
 	if (!_peer || (_peer->isUser() && (peerToUser(_peer->id) == MTP::authedId() || isNotificationsUser(_peer->id) || isServiceUser(_peer->id) || _peer->asUser()->botInfo))) {
 		_reportSpamStatus = dbiprsNoButton;
@@ -6202,6 +6212,8 @@ void HistoryWidget::drawRecording(Painter &p) {
 }
 
 void HistoryWidget::paintEvent(QPaintEvent *e) {
+	if (App::wnd() && App::wnd()->contentOverlapped(this, e)) return;
+
 	Painter p(this);
 	QRect r(e->rect());
 	if (r != rect()) {
diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h
index db38cec39..f5b4c583f 100644
--- a/Telegram/SourceFiles/historywidget.h
+++ b/Telegram/SourceFiles/historywidget.h
@@ -534,6 +534,8 @@ public:
 
 	void updateNotifySettings();
 
+	bool contentOverlapped(const QRect &globalRect);
+
 	~HistoryWidget();
 
 signals:
diff --git a/Telegram/SourceFiles/layerwidget.cpp b/Telegram/SourceFiles/layerwidget.cpp
index 69795b3ad..286ede18a 100644
--- a/Telegram/SourceFiles/layerwidget.cpp
+++ b/Telegram/SourceFiles/layerwidget.cpp
@@ -54,7 +54,7 @@ void BackgroundWidget::paintEvent(QPaintEvent *e) {
 	p.fillRect(rect(), st::layerBG->b);
 
 	p.setOpacity(aBackground.current());
-	shadow.paint(p, w->boxRect(), st::boxShadowShift);
+	shadow.paint(p, w->geometry(), st::boxShadowShift);
 }
 
 void BackgroundWidget::keyPressEvent(QKeyEvent *e) {
@@ -105,6 +105,11 @@ void BackgroundWidget::setInnerFocus() {
 	}
 }
 
+bool BackgroundWidget::contentOverlapped(const QRect &globalRect) {
+	if (isHidden()) return false;
+	return w && w->overlaps(globalRect);
+}
+
 void BackgroundWidget::resizeEvent(QResizeEvent *e) {
 	w->parentResized();
 }
diff --git a/Telegram/SourceFiles/layerwidget.h b/Telegram/SourceFiles/layerwidget.h
index 0d4b57a71..bf23ac43e 100644
--- a/Telegram/SourceFiles/layerwidget.h
+++ b/Telegram/SourceFiles/layerwidget.h
@@ -38,16 +38,15 @@ public:
 		emit resized();
 	}
 
-	virtual QRect boxRect() const {
-		QRect res(rect());
-		res.moveTopLeft(geometry().topLeft());
-		return res;
-	}
-
 	void mousePressEvent(QMouseEvent *e) {
 		e->accept();
 	}
 
+	bool overlaps(const QRect &globalRect) {
+		if (isHidden() || !testAttribute(Qt::WA_OpaquePaintEvent)) return false;
+		return rect().contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size()));
+	}
+
 signals:
 
 	void closed();
@@ -78,6 +77,8 @@ public:
 	bool canSetFocus() const;
 	void setInnerFocus();
 
+	bool contentOverlapped(const QRect &globalRect);
+
 	~BackgroundWidget();
 
 public slots:
diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp
index 0aa38af55..ab33cec92 100644
--- a/Telegram/SourceFiles/localstorage.cpp
+++ b/Telegram/SourceFiles/localstorage.cpp
@@ -2279,11 +2279,11 @@ namespace Local {
 		}
 	}
 
-	class ImageLoadTask : public Task {
+	class AbstractCachedLoadTask : public Task {
 	public:
 
-		ImageLoadTask(const FileKey &key, const StorageKey &location, mtpFileLoader *loader) :
-			_key(key), _location(location), _loader(loader), _result(0) {
+		AbstractCachedLoadTask(const FileKey &key, const StorageKey &location, bool readImageFlag, mtpFileLoader *loader) :
+			_key(key), _location(location), _readImageFlag(readImageFlag), _loader(loader), _result(0) {
 		}
 		void process() {
 			FileReadDescriptor image;
@@ -2294,43 +2294,44 @@ namespace Local {
 			QByteArray imageData;
 			quint64 locFirst, locSecond;
 			quint32 imageType;
-			image.stream >> locFirst >> locSecond >> imageType >> imageData;
+			readFromStream(image.stream, locFirst, locSecond, imageType, imageData);
 
 			if (locFirst != _location.first || locSecond != _location.second) {
 				return;
 			}
 
-			_result = new Result(StorageFileType(imageType), imageData);
+			_result = new Result(StorageFileType(imageType), imageData, _readImageFlag);
 		}
 		void finish() {
 			if (_result) {
 				_loader->localLoaded(_result->image, _result->format, _result->pixmap);
 			} else {
-				StorageMap::iterator j = _imagesMap.find(_location);
-				if (j != _imagesMap.cend() && j->first == _key) {
-					clearKey(_key, UserPath);
-					_storageImagesSize -= j->second;
-					_imagesMap.erase(j);
-				}
+				clearInMap();
 				_loader->localLoaded(StorageImageSaved());
 			}
 		}
+		virtual void readFromStream(QDataStream &stream, quint64 &first, quint64 &second, quint32 &type, QByteArray &data) = 0;
+		virtual void clearInMap() = 0;
 
-	private:
+	protected:
 		FileKey _key;
 		StorageKey _location;
+		bool _readImageFlag;
 		struct Result {
-			Result(StorageFileType type, const QByteArray &data) : image(type, data) {
-				QByteArray guessFormat;
-				switch (type) {
-				case mtpc_storage_fileGif: guessFormat = "GIF"; break;
-				case mtpc_storage_fileJpeg: guessFormat = "JPG"; break;
-				case mtpc_storage_filePng: guessFormat = "PNG"; break;
-				default: guessFormat = QByteArray(); break;
-				}
-				pixmap = QPixmap::fromImage(App::readImage(data, &guessFormat, false), Qt::ColorOnly);
-				if (!pixmap.isNull()) {
-					format = guessFormat;
+			Result(StorageFileType type, const QByteArray &data, bool readImageFlag) : image(type, data) {
+				if (readImageFlag) {
+					QByteArray guessFormat;
+					switch (type) {
+						case StorageFileGif: guessFormat = "GIF"; break;
+						case StorageFileJpeg: guessFormat = "JPG"; break;
+						case StorageFilePng: guessFormat = "PNG"; break;
+						case StorageFileWebp: guessFormat = "WEBP"; break;
+						default: guessFormat = QByteArray(); break;
+					}
+					pixmap = QPixmap::fromImage(App::readImage(data, &guessFormat, false), Qt::ColorOnly);
+					if (!pixmap.isNull()) {
+						format = guessFormat;
+					}
 				}
 			}
 			StorageImageSaved image;
@@ -2342,6 +2343,24 @@ namespace Local {
 
 	};
 
+	class ImageLoadTask : public AbstractCachedLoadTask {
+	public:
+		ImageLoadTask(const FileKey &key, const StorageKey &location, mtpFileLoader *loader) :
+		AbstractCachedLoadTask(key, location, true, loader) {
+		}
+		void readFromStream(QDataStream &stream, quint64 &first, quint64 &second, quint32 &type, QByteArray &data) {
+			stream >> first >> second >> type >> data;
+		}
+		void clearInMap() {
+			StorageMap::iterator j = _imagesMap.find(_location);
+			if (j != _imagesMap.cend() && j->first == _key) {
+				clearKey(_key, UserPath);
+				_storageImagesSize -= j->second;
+				_imagesMap.erase(j);
+			}
+		}
+	};
+
 	TaskId startImageLoad(const StorageKey &location, mtpFileLoader *loader) {
 		StorageMap::iterator j = _imagesMap.find(location);
 		if (j == _imagesMap.cend() || !_localLoader) {
@@ -2350,27 +2369,6 @@ namespace Local {
 		return _localLoader->addTask(new ImageLoadTask(j->first, location, loader));
 	}
 
-	StorageImageSaved readImage(const StorageKey &location) {
-		StorageMap::iterator j = _imagesMap.find(location);
-		if (j == _imagesMap.cend()) {
-			return StorageImageSaved();
-		}
-		FileReadDescriptor image;
-		if (!readEncryptedFile(image, j.value().first, UserPath)) {
-			clearKey(j.value().first, UserPath);
-			_storageImagesSize -= j.value().second;
-			_imagesMap.erase(j);
-			return StorageImageSaved();
-		}
-
-		QByteArray imageData;
-		quint64 locFirst, locSecond;
-		quint32 imageType;
-		image.stream >> locFirst >> locSecond >> imageType >> imageData;
-
-		return (locFirst == location.first && locSecond == location.second) ? StorageImageSaved(StorageFileType(imageType), imageData) : StorageImageSaved();
-	}
-
 	int32 hasImages() {
 		return _imagesMap.size();
 	}
@@ -2403,32 +2401,31 @@ namespace Local {
 		}
 	}
 
+	class StickerImageLoadTask : public AbstractCachedLoadTask {
+	public:
+		StickerImageLoadTask(const FileKey &key, const StorageKey &location, mtpFileLoader *loader) :
+		AbstractCachedLoadTask(key, location, true, loader) {
+		}
+		void readFromStream(QDataStream &stream, quint64 &first, quint64 &second, quint32 &type, QByteArray &data) {
+			stream >> first >> second >> data;
+			type = StorageFilePartial;
+		}
+		void clearInMap() {
+			StorageMap::iterator j = _stickerImagesMap.find(_location);
+			if (j != _stickerImagesMap.cend() && j->first == _key) {
+				clearKey(j.value().first, UserPath);
+				_storageStickersSize -= j.value().second;
+				_stickerImagesMap.erase(j);
+			}
+		}
+	};
+
 	TaskId startStickerImageLoad(const StorageKey &location, mtpFileLoader *loader) {
 		StorageMap::iterator j = _stickerImagesMap.find(location);
 		if (j == _stickerImagesMap.cend()) {
 			return 0;
 		}
-		return 0;
-	}
-
-	QByteArray readStickerImage(const StorageKey &location) {
-		StorageMap::iterator j = _stickerImagesMap.find(location);
-		if (j == _stickerImagesMap.cend()) {
-			return QByteArray();
-		}
-		FileReadDescriptor draft;
-		if (!readEncryptedFile(draft, j.value().first, UserPath)) {
-			clearKey(j.value().first, UserPath);
-			_storageStickersSize -= j.value().second;
-			_stickerImagesMap.erase(j);
-			return QByteArray();
-		}
-
-		QByteArray stickerData;
-		quint64 locFirst, locSecond;
-		draft.stream >> locFirst >> locSecond >> stickerData;
-
-		return (locFirst == location.first && locSecond == location.second) ? stickerData : QByteArray();
+		return _localLoader->addTask(new StickerImageLoadTask(j->first, location, loader));
 	}
 
 	int32 hasStickers() {
@@ -2463,32 +2460,31 @@ namespace Local {
 		}
 	}
 
+	class AudioLoadTask : public AbstractCachedLoadTask {
+	public:
+		AudioLoadTask(const FileKey &key, const StorageKey &location, mtpFileLoader *loader) :
+		AbstractCachedLoadTask(key, location, false, loader) {
+		}
+		void readFromStream(QDataStream &stream, quint64 &first, quint64 &second, quint32 &type, QByteArray &data) {
+			stream >> first >> second >> data;
+			type = StorageFilePartial;
+		}
+		void clearInMap() {
+			StorageMap::iterator j = _audiosMap.find(_location);
+			if (j != _audiosMap.cend() && j->first == _key) {
+				clearKey(j.value().first, UserPath);
+				_storageAudiosSize -= j.value().second;
+				_audiosMap.erase(j);
+			}
+		}
+	};
+
 	TaskId startAudioLoad(const StorageKey &location, mtpFileLoader *loader) {
 		StorageMap::iterator j = _audiosMap.find(location);
 		if (j == _audiosMap.cend()) {
 			return 0;
 		}
-		return 0;
-	}
-
-	QByteArray readAudio(const StorageKey &location) {
-		StorageMap::iterator j = _audiosMap.find(location);
-		if (j == _audiosMap.cend()) {
-			return QByteArray();
-		}
-		FileReadDescriptor draft;
-		if (!readEncryptedFile(draft, j.value().first, UserPath)) {
-			clearKey(j.value().first, UserPath);
-			_storageAudiosSize -= j.value().second;
-			_audiosMap.erase(j);
-			return QByteArray();
-		}
-
-		QByteArray audioData;
-		quint64 locFirst, locSecond;
-		draft.stream >> locFirst >> locSecond >> audioData;
-
-		return (locFirst == location.first && locSecond == location.second) ? audioData : QByteArray();
+		return _localLoader->addTask(new AudioLoadTask(j->first, location, loader));
 	}
 
 	int32 hasAudios() {
diff --git a/Telegram/SourceFiles/localstorage.h b/Telegram/SourceFiles/localstorage.h
index 02361be3a..b6789065a 100644
--- a/Telegram/SourceFiles/localstorage.h
+++ b/Telegram/SourceFiles/localstorage.h
@@ -121,19 +121,16 @@ namespace Local {
 	void writeImage(const StorageKey &location, const ImagePtr &img);
 	void writeImage(const StorageKey &location, const StorageImageSaved &jpeg, bool overwrite = true);
 	TaskId startImageLoad(const StorageKey &location, mtpFileLoader *loader);
-	StorageImageSaved readImage(const StorageKey &location);
 	int32 hasImages();
 	qint64 storageImagesSize();
 
 	void writeStickerImage(const StorageKey &location, const QByteArray &data, bool overwrite = true);
 	TaskId startStickerImageLoad(const StorageKey &location, mtpFileLoader *loader);
-	QByteArray readStickerImage(const StorageKey &location);
 	int32 hasStickers();
 	qint64 storageStickersSize();
 
 	void writeAudio(const StorageKey &location, const QByteArray &data, bool overwrite = true);
 	TaskId startAudioLoad(const StorageKey &location, mtpFileLoader *loader);
-	QByteArray readAudio(const StorageKey &location);
 	int32 hasAudios();
 	qint64 storageAudiosSize();
 
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index 899fd0f39..17b150faa 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -3568,6 +3568,11 @@ void MainWidget::onSelfParticipantUpdated(ChannelData *channel) {
 	}
 }
 
+bool MainWidget::contentOverlapped(const QRect &globalRect) {
+	return (history.contentOverlapped(globalRect) ||
+			_mediaType.overlaps(globalRect));
+}
+
 void MainWidget::usernameResolveDone(QPair<bool, QString> toProfileStartToken, const MTPcontacts_ResolvedPeer &result) {
 	App::wnd()->hideLayer();
 	if (result.type() != mtpc_contacts_resolvedPeer) return;
diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h
index 009dc42fb..b9a44d4ec 100644
--- a/Telegram/SourceFiles/mainwidget.h
+++ b/Telegram/SourceFiles/mainwidget.h
@@ -398,6 +398,8 @@ public:
 	void gotRangeDifference(ChannelData *channel, const MTPupdates_ChannelDifference &diff);
 	void onSelfParticipantUpdated(ChannelData *channel);
 
+	bool contentOverlapped(const QRect &globalRect);
+
 	~MainWidget();
 
 signals:
diff --git a/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp b/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp
index 56c625dd5..22929f3fe 100644
--- a/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp
+++ b/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp
@@ -335,11 +335,9 @@ bool mtpFileLoader::tryLoadLocal() {
 		if (duplicateInData) {
 			MediaKey mkey = mediaKey(_locationType, dc, id);
 			if (_locationType == DocumentFileLocation) {
-				data = Local::readStickerImage(mkey);
-				if (!data.isEmpty()) type = mtpc_storage_filePartial;
+				_localTaskId = Local::startStickerImageLoad(mkey, this);
 			} else if (_locationType == AudioFileLocation) {
-				data = Local::readAudio(mkey);
-				if (!data.isEmpty()) type = mtpc_storage_filePartial;
+				_localTaskId = Local::startAudioLoad(mkey, this);
 			}
 		}
 	}
@@ -382,7 +380,7 @@ void mtpFileLoader::localLoaded(const StorageImageSaved &result, const QByteArra
 	}
 	data = result.data;
 	type = mtpFromStorageType(result.type);
-	if (_locationType == UnknownFileLocation) { // photo
+	if (!imagePixmap.isNull()) {
 		_imageFormat = imageFormat;
 		_imagePixmap = imagePixmap;
 	}
diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp
index 04d33b7b8..2525ae8d3 100644
--- a/Telegram/SourceFiles/overviewwidget.cpp
+++ b/Telegram/SourceFiles/overviewwidget.cpp
@@ -988,6 +988,8 @@ QPixmap OverviewInner::genPix(PhotoData *photo, int32 size) {
 }
 
 void OverviewInner::paintEvent(QPaintEvent *e) {
+	if (App::wnd() && App::wnd()->contentOverlapped(this, e)) return;
+
 	Painter p(this);
 
 	QRect r(e->rect());
@@ -1014,10 +1016,10 @@ void OverviewInner::paintEvent(QPaintEvent *e) {
 	bool hasSel = !_selected.isEmpty();
 
 	if (_type == OverviewPhotos) {
-		int32 rowFrom = int32(r.top() - _addToY - st::overviewPhotoSkip) / int32(_vsize + st::overviewPhotoSkip);
-		int32 rowTo = int32(r.bottom() - _addToY - st::overviewPhotoSkip) / int32(_vsize + st::overviewPhotoSkip) + 1;
 		History::MediaOverview &overview(_hist->overview[_type]);
 		int32 count = overview.size();
+		int32 rowFrom = floorclamp(r.y() - _addToY - st::overviewPhotoSkip, _vsize + st::overviewPhotoSkip, 0, count);
+		int32 rowTo = ceilclamp(r.y() + r.height() - _addToY - st::overviewPhotoSkip, _vsize + st::overviewPhotoSkip, 0, count);
 		float64 w = float64(_width - st::overviewPhotoSkip) / _photosInRow;
 		for (int32 row = rowFrom; row < rowTo; ++row) {
 			if (row * _photosInRow >= _photosToAdd + count) break;
@@ -1097,10 +1099,10 @@ void OverviewInner::paintEvent(QPaintEvent *e) {
 			}
 		}
 	} else if (_type == OverviewAudioDocuments) {
-		int32 from = int32(r.top() - _addToY) / int32(_audioHeight);
-		int32 to = int32(r.bottom() - _addToY) / int32(_audioHeight) + 1;
 		History::MediaOverview &overview(_hist->overview[_type]);
 		int32 count = overview.size();
+		int32 from = floorclamp(r.y() - _addToY, _audioHeight, 0, count);
+		int32 to = ceilclamp(r.y() + r.height() - _addToY, _audioHeight, 0, count);
 		p.translate(_audioLeft, _addToY + from * _audioHeight);
 		for (int32 index = from; index < to; ++index) {
 			if (index >= count) break;
@@ -1128,7 +1130,7 @@ void OverviewInner::paintEvent(QPaintEvent *e) {
 		for (int32 i = 0, l = _items.size(); i < l; ++i) {
 			if (i + 1 == l || _addToY + _items[i + 1].y > r.top()) {
 				int32 left = st::dlgPhotoSize + st::dlgPhotoPadding, top = st::linksMargin + st::linksBorder, curY = _items[i].y;
-				if (_addToY + curY >= r.bottom()) break;
+				if (_addToY + curY >= r.y() + r.height()) break;
 
 				p.translate(0, curY - y);
 				if (_items[i].msgid) { // draw item
@@ -1220,7 +1222,7 @@ void OverviewInner::paintEvent(QPaintEvent *e) {
 			--i;
 			if (!i || (_addToY + _height - _items[i - 1].y > r.top())) {
 				int32 curY = _height - _items[i].y;
-				if (_addToY + curY >= r.bottom()) break;
+				if (_addToY + curY >= r.y() + r.height()) break;
 
 				p.translate(0, curY - y);
 				if (_items[i].msgid) { // draw item
@@ -2598,7 +2600,9 @@ void OverviewWidget::resizeEvent(QResizeEvent *e) {
 }
 
 void OverviewWidget::paintEvent(QPaintEvent *e) {
-	QPainter p(this);
+	if (App::wnd() && App::wnd()->contentOverlapped(this, e)) return;
+
+	Painter p(this);
 	if (animating() && _showing) {
 		p.setOpacity(a_bgAlpha.current());
 		p.drawPixmap(a_bgCoord.current(), 0, _bgAnimCache);
diff --git a/Telegram/SourceFiles/profilewidget.cpp b/Telegram/SourceFiles/profilewidget.cpp
index 320f54157..5970be2a0 100644
--- a/Telegram/SourceFiles/profilewidget.cpp
+++ b/Telegram/SourceFiles/profilewidget.cpp
@@ -651,6 +651,8 @@ bool ProfileInner::event(QEvent *e) {
 }
 
 void ProfileInner::paintEvent(QPaintEvent *e) {
+	if (App::wnd() && App::wnd()->contentOverlapped(this, e)) return;
+
 	Painter p(this);
 
 	QRect r(e->rect());
@@ -810,7 +812,7 @@ void ProfileInner::paintEvent(QPaintEvent *e) {
 			for (Participants::const_iterator i = _participants.cbegin(), e = _participants.cend(); i != e; ++i, ++cnt) {
 				int32 top = partfrom + cnt * _pHeight;
 				if (top + _pHeight <= r.top()) continue;
-				if (top > r.bottom()) break;
+				if (top >= r.y() + r.height()) break;
 
 				if (_selectedRow == cnt) {
 					p.fillRect(_left - st::profileListPadding.width(), top, _width + 2 * st::profileListPadding.width(), _pHeight, st::profileHoverBG->b);
@@ -1553,7 +1555,9 @@ void ProfileWidget::mousePressEvent(QMouseEvent *e) {
 }
 
 void ProfileWidget::paintEvent(QPaintEvent *e) {
-	QPainter p(this);
+	if (App::wnd() && App::wnd()->contentOverlapped(this, e)) return;
+
+	Painter p(this);
 	if (animating() && _showing) {
 		p.setOpacity(a_bgAlpha.current());
 		p.drawPixmap(a_bgCoord.current(), 0, _bgAnimCache);
diff --git a/Telegram/SourceFiles/settings.cpp b/Telegram/SourceFiles/settings.cpp
index f7412a36c..f8e994e3c 100644
--- a/Telegram/SourceFiles/settings.cpp
+++ b/Telegram/SourceFiles/settings.cpp
@@ -208,7 +208,7 @@ void settingsParseArgs(int argc, char *argv[]) {
 RecentEmojiPack &cGetRecentEmojis() {
 	if (cRecentEmojis().isEmpty()) {
 		RecentEmojiPack r;
-		if (!cRecentEmojisPreload().isEmpty()) {
+		if (!cRecentEmojisPreload().isEmpty() && false) {
 			RecentEmojisPreload p(cRecentEmojisPreload());
 			cSetRecentEmojisPreload(RecentEmojisPreload());
 			r.reserve(p.size());
diff --git a/Telegram/SourceFiles/settings.h b/Telegram/SourceFiles/settings.h
index 0e14a422f..8f7aed552 100644
--- a/Telegram/SourceFiles/settings.h
+++ b/Telegram/SourceFiles/settings.h
@@ -205,7 +205,8 @@ RecentStickerPack &cGetRecentStickers();
 DeclareSetting(uint64, LastStickersUpdate);
 
 static const uint64 DefaultStickerSetId = 0; // for backward compatibility
-static const uint64 CustomStickerSetId = 0xFFFFFFFFFFFFFFFFLLU, RecentStickerSetId = 0xFFFFFFFFFFFFFFFELLU;
+static const uint64 CustomStickerSetId = 0xFFFFFFFFFFFFFFFFULL, RecentStickerSetId = 0xFFFFFFFFFFFFFFFEULL;
+static const uint64 NoneStickerSetId = 0xFFFFFFFFFFFFFFFDULL; // for emoji/stickers panel
 struct StickerSet {
 	StickerSet(uint64 id, uint64 access, const QString &title, const QString &shortName, int32 count, int32 hash, int32 flags) : id(id), access(access), title(title), shortName(shortName), count(count), hash(hash), flags(flags) {
 	}
diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h
index e36a1a1c7..6cff40ea6 100644
--- a/Telegram/SourceFiles/structs.h
+++ b/Telegram/SourceFiles/structs.h
@@ -936,6 +936,9 @@ struct DocumentData {
 		if (loader->done()) {
 			location = FileLocation(mtpToStorageType(loader->fileType()), loader->fileName());
 			data = loader->bytes();
+			if (sticker() && !loader->imagePixmap().isNull()) {
+				sticker()->img = ImagePtr(data, loader->imageFormat(), loader->imagePixmap());
+			}
 		}
 		loader->deleteLater();
 		loader->rpcInvalidate();
diff --git a/Telegram/SourceFiles/title.cpp b/Telegram/SourceFiles/title.cpp
index a4932d05b..47e23d5e2 100644
--- a/Telegram/SourceFiles/title.cpp
+++ b/Telegram/SourceFiles/title.cpp
@@ -65,6 +65,7 @@ TitleWidget::TitleWidget(Window *window)
     , lastMaximized(!(window->windowState() & Qt::WindowMaximized))
 {
 	setGeometry(0, 0, wnd->width(), st::titleHeight);
+	setAttribute(Qt::WA_OpaquePaintEvent);
 	_lock.hide();
 	_update.hide();
     _cancel.hide();
@@ -363,7 +364,7 @@ void TitleWidget::maximizedChanged(bool maximized, bool force) {
 HitTestType TitleWidget::hitTest(const QPoint &p) {
 	if (App::wnd() && App::wnd()->layerShown()) return HitTestNone;
 
-	int x(p.x()), y(p.y()), w(width()), h(height() - st::titleShadow);
+	int x(p.x()), y(p.y()), w(width()), h(height());
 	if (cWideMode() && hider && x >= App::main()->dlgsWidth()) return HitTestNone;
 
 	if (x >= st::titleIconPos.x() && y >= st::titleIconPos.y() && x < st::titleIconPos.x() + st::titleIconImg.pxWidth() && y < st::titleIconPos.y() + st::titleIconImg.pxHeight()) {
diff --git a/Telegram/SourceFiles/types.h b/Telegram/SourceFiles/types.h
index af3ff52d5..a7447b9d3 100644
--- a/Telegram/SourceFiles/types.h
+++ b/Telegram/SourceFiles/types.h
@@ -417,3 +417,16 @@ private:
 MimeType mimeTypeForName(const QString &mime);
 MimeType mimeTypeForFile(const QFileInfo &file);
 MimeType mimeTypeForData(const QByteArray &data);
+
+inline int32 floorclamp(int32 value, int32 step, int32 lowest, int32 highest) {
+	return qMin(qMax(value / step, lowest), highest);
+}
+inline int32 floorclamp(float64 value, int32 step, int32 lowest, int32 highest) {
+	return qMin(qMax(qFloor(value / step), lowest), highest);
+}
+inline int32 ceilclamp(int32 value, int32 step, int32 lowest, int32 highest) {
+	return qMax(qMin((value / step) + ((value % step) ? 1 : 0), highest), lowest);
+}
+inline int32 ceilclamp(float64 value, int32 step, int32 lowest, int32 highest) {
+	return qMax(qMin(qCeil(value / step), highest), lowest);
+}
diff --git a/Telegram/SourceFiles/window.cpp b/Telegram/SourceFiles/window.cpp
index 139f54340..b5db91a42 100644
--- a/Telegram/SourceFiles/window.cpp
+++ b/Telegram/SourceFiles/window.cpp
@@ -865,6 +865,12 @@ void Window::hideMediaview() {
     }
 }
 
+bool Window::contentOverlapped(const QRect &globalRect) {
+	if (main && main->contentOverlapped(globalRect)) return true;
+	if (layerBG && layerBG->contentOverlapped(globalRect)) return true;
+	return false;
+}
+
 void Window::setInnerFocus() {
 	if (layerBG && layerBG->canSetFocus()) {
 		layerBG->setInnerFocus();
@@ -1177,7 +1183,7 @@ void Window::resizeEvent(QResizeEvent *e) {
 		cSetWideMode(wideMode);
 		updateWideMode();
 	}
-	title->setGeometry(QRect(0, 0, width(), st::titleHeight + st::titleShadow));
+	title->setGeometry(0, 0, width(), st::titleHeight);
 	if (layerBG) layerBG->resize(width(), height());
 	if (_connecting) _connecting->setGeometry(0, height() - _connecting->height(), _connecting->width(), _connecting->height());
 	emit resized(QSize(width(), height() - st::titleHeight));
diff --git a/Telegram/SourceFiles/window.h b/Telegram/SourceFiles/window.h
index f81793ef9..be20c0b4b 100644
--- a/Telegram/SourceFiles/window.h
+++ b/Telegram/SourceFiles/window.h
@@ -233,6 +233,14 @@ public:
 	bool isActive(bool cached = true) const;
 	void hideMediaview();
 
+	bool contentOverlapped(const QRect &globalRect);
+	bool contentOverlapped(QWidget *w, QPaintEvent *e) {
+		return contentOverlapped(QRect(w->mapToGlobal(e->rect().topLeft()), e->rect().size()));
+	}
+	bool contentOverlapped(QWidget *w, const QRegion &r) {
+		return contentOverlapped(QRect(w->mapToGlobal(r.boundingRect().topLeft()), r.boundingRect().size()));
+	}
+
 public slots:
 
 	void updateIsActive(int timeout = 0);