mirror of https://github.com/procxx/kepka.git
Ui::MultiSelect control ready.
This commit is contained in:
parent
3455344c62
commit
48332c0c6b
Binary file not shown.
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
Binary file not shown.
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 82 KiB |
|
@ -193,6 +193,7 @@ defaultInputArea: InputArea {
|
||||||
heightMax: 128px;
|
heightMax: 128px;
|
||||||
}
|
}
|
||||||
defaultInputField: InputField {
|
defaultInputField: InputField {
|
||||||
|
textBg: white;
|
||||||
textFg: black;
|
textFg: black;
|
||||||
textMargins: margins(0px, 6px, 0px, 4px);
|
textMargins: margins(0px, 6px, 0px, 4px);
|
||||||
textAlign: align(topleft);
|
textAlign: align(topleft);
|
||||||
|
@ -216,15 +217,6 @@ defaultInputField: InputField {
|
||||||
|
|
||||||
height: 32px;
|
height: 32px;
|
||||||
}
|
}
|
||||||
dialogsSearchField: InputField(defaultInputField) {
|
|
||||||
textMargins: margins(34px, 7px, 34px, 7px);
|
|
||||||
|
|
||||||
iconSprite: sprite(227px, 21px, 24px, 24px);
|
|
||||||
iconPosition: point(6px, 5px);
|
|
||||||
|
|
||||||
width: 240px;
|
|
||||||
height: 34px;
|
|
||||||
}
|
|
||||||
defaultCheckbox: Checkbox {
|
defaultCheckbox: Checkbox {
|
||||||
textFg: black;
|
textFg: black;
|
||||||
textBg: white;
|
textBg: white;
|
||||||
|
@ -746,12 +738,11 @@ dlgFilter: flatInput(inpDefGray) {
|
||||||
bgColor: #f2f2f2;
|
bgColor: #f2f2f2;
|
||||||
phColor: #949494;
|
phColor: #949494;
|
||||||
phFocusColor: #a4a4a4;
|
phFocusColor: #a4a4a4;
|
||||||
imgRect: sprite(227px, 21px, 24px, 24px);
|
icon: icon {{ "box_search_icon", #aaaaaa, point(10px, 9px) }};
|
||||||
|
|
||||||
width: 240px;
|
width: 240px;
|
||||||
height: 34px;
|
height: 34px;
|
||||||
textMrg: margins(34px, 2px, 34px, 4px);
|
textMrg: margins(34px, 2px, 34px, 4px);
|
||||||
imgPos: point(6px, 5px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
topBarHeight: 54px;
|
topBarHeight: 54px;
|
||||||
|
|
|
@ -130,8 +130,7 @@ flatInput {
|
||||||
font: font;
|
font: font;
|
||||||
cursor: cursor;
|
cursor: cursor;
|
||||||
|
|
||||||
imgRect: sprite;
|
icon: icon;
|
||||||
imgPos: point;
|
|
||||||
|
|
||||||
borderWidth: pixels;
|
borderWidth: pixels;
|
||||||
borderColor: color;
|
borderColor: color;
|
||||||
|
@ -394,6 +393,7 @@ InputArea {
|
||||||
}
|
}
|
||||||
|
|
||||||
InputField {
|
InputField {
|
||||||
|
textBg: color;
|
||||||
textFg: color;
|
textFg: color;
|
||||||
textMargins: margins;
|
textMargins: margins;
|
||||||
textAlign: align;
|
textAlign: align;
|
||||||
|
@ -418,9 +418,6 @@ InputField {
|
||||||
|
|
||||||
width: pixels;
|
width: pixels;
|
||||||
height: pixels;
|
height: pixels;
|
||||||
|
|
||||||
iconSprite: sprite;
|
|
||||||
iconPosition: point;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PeerAvatarButton {
|
PeerAvatarButton {
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 350 B |
Binary file not shown.
After Width: | Height: | Size: 707 B |
|
@ -69,20 +69,19 @@ aboutRevokePublicLabel: flatLabel(labelDefFlat) {
|
||||||
}
|
}
|
||||||
|
|
||||||
boxSearchField: InputField(defaultInputField) {
|
boxSearchField: InputField(defaultInputField) {
|
||||||
textMargins: margins(41px, 16px, 41px, 0px);
|
textBg: transparent;
|
||||||
|
textMargins: margins(2px, 7px, 2px, 0px);
|
||||||
|
|
||||||
placeholderFg: #999;
|
placeholderFg: #999;
|
||||||
placeholderFgActive: #aaa;
|
placeholderFgActive: #aaa;
|
||||||
placeholderMargins: margins(4px, 0px, 4px, 0px);
|
placeholderMargins: margins(2px, 0px, 2px, 0px);
|
||||||
|
duration: 150;
|
||||||
|
|
||||||
border: 0px;
|
border: 0px;
|
||||||
borderActive: 0px;
|
borderActive: 0px;
|
||||||
borderError: 0px;
|
borderError: 0px;
|
||||||
|
|
||||||
height: 48px;
|
height: 32px;
|
||||||
|
|
||||||
iconSprite: sprite(227px, 21px, 24px, 24px);
|
|
||||||
iconPosition: point(15px, 14px);
|
|
||||||
|
|
||||||
font: normalFont;
|
font: normalFont;
|
||||||
}
|
}
|
||||||
|
@ -95,15 +94,46 @@ boxSearchCancel: IconButton {
|
||||||
|
|
||||||
icon: icon {{ "box_search_cancel", #000000 }};
|
icon: icon {{ "box_search_cancel", #000000 }};
|
||||||
iconPosition: point(8px, 18px);
|
iconPosition: point(8px, 18px);
|
||||||
downIconPosition: point(8px, 18px);
|
downIconPosition: point(8px, 19px);
|
||||||
|
|
||||||
duration: 150;
|
duration: 150;
|
||||||
}
|
}
|
||||||
|
|
||||||
contactsMultiSelect: MultiSelect {
|
contactsMultiSelect: MultiSelect {
|
||||||
field: boxSearchField;
|
padding: margins(8px, 8px, 8px, 8px);
|
||||||
cancel: boxSearchCancel;
|
|
||||||
maxHeight: 104px;
|
maxHeight: 104px;
|
||||||
|
scroll: flatScroll(solidScroll) {
|
||||||
|
deltat: 3px;
|
||||||
|
deltab: 3px;
|
||||||
|
round: 1px;
|
||||||
|
width: 8px;
|
||||||
|
deltax: 3px;
|
||||||
|
hiding: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
item: MultiSelectItem {
|
||||||
|
padding: margins(6px, 7px, 12px, 0px);
|
||||||
|
maxWidth: 128px;
|
||||||
|
height: 32px;
|
||||||
|
font: normalFont;
|
||||||
|
textBg: contactsBgOver;
|
||||||
|
textFg: windowTextFg;
|
||||||
|
textActiveBg: titleBg;
|
||||||
|
textActiveFg: white;
|
||||||
|
deleteFg: white;
|
||||||
|
deleteLeft: 9px;
|
||||||
|
deleteStroke: 3px;
|
||||||
|
duration: 150;
|
||||||
|
minScale: 0.3;
|
||||||
|
}
|
||||||
|
itemSkip: 8px;
|
||||||
|
|
||||||
|
field: boxSearchField;
|
||||||
|
fieldIcon: icon {{ "box_search_icon", #aaaaaa, point(11px, 9px) }};
|
||||||
|
fieldIconSkip: 36px;
|
||||||
|
fieldCancel: boxSearchCancel;
|
||||||
|
fieldCancelSkip: 34px;
|
||||||
|
fieldMinWidth: 42px;
|
||||||
}
|
}
|
||||||
contactsPhotoCheckbox: RoundImageCheckbox {
|
contactsPhotoCheckbox: RoundImageCheckbox {
|
||||||
imageRadius: 21px;
|
imageRadius: 21px;
|
||||||
|
|
|
@ -23,6 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||||
|
|
||||||
#include "dialogs/dialogs_indexed_list.h"
|
#include "dialogs/dialogs_indexed_list.h"
|
||||||
#include "styles/style_dialogs.h"
|
#include "styles/style_dialogs.h"
|
||||||
|
#include "styles/style_history.h"
|
||||||
#include "styles/style_boxes.h"
|
#include "styles/style_boxes.h"
|
||||||
#include "lang.h"
|
#include "lang.h"
|
||||||
#include "boxes/addcontactbox.h"
|
#include "boxes/addcontactbox.h"
|
||||||
|
@ -97,10 +98,18 @@ ContactsBox::ContactsBox(UserData *bot) : ItemListBox(st::contactsScroll)
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContactsBox::init() {
|
void ContactsBox::init() {
|
||||||
ItemListBox::init(_inner);
|
_select->resizeToWidth(st::boxWideWidth);
|
||||||
|
|
||||||
|
auto inviting = (_inner->creating() == CreatingGroupGroup) || (_inner->channel() && _inner->membersFilter() == MembersFilter::Recent) || _inner->chat();
|
||||||
|
auto topSkip = st::boxTitleHeight + _select->height();
|
||||||
|
auto bottomSkip = inviting ? (st::boxButtonPadding.top() + _next.height() + st::boxButtonPadding.bottom()) : st::boxScrollSkip;
|
||||||
|
ItemListBox::init(_inner, bottomSkip, topSkip);
|
||||||
|
|
||||||
connect(_inner, SIGNAL(chosenChanged()), this, SLOT(onChosenChanged()));
|
|
||||||
connect(_inner, SIGNAL(addRequested()), App::wnd(), SLOT(onShowAddContact()));
|
connect(_inner, SIGNAL(addRequested()), App::wnd(), SLOT(onShowAddContact()));
|
||||||
|
_inner->setPeerSelectedChangedCallback([this](PeerData *peer, bool checked) {
|
||||||
|
onPeerSelectedChanged(peer, checked);
|
||||||
|
});
|
||||||
|
|
||||||
if (_inner->channel() && _inner->membersFilter() == MembersFilter::Admins) {
|
if (_inner->channel() && _inner->membersFilter() == MembersFilter::Admins) {
|
||||||
_next.hide();
|
_next.hide();
|
||||||
_cancel.hide();
|
_cancel.hide();
|
||||||
|
@ -120,7 +129,14 @@ void ContactsBox::init() {
|
||||||
connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose()));
|
connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose()));
|
||||||
connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll()));
|
connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll()));
|
||||||
_select->setQueryChangedCallback([this](const QString &query) { onFilterUpdate(query); });
|
_select->setQueryChangedCallback([this](const QString &query) { onFilterUpdate(query); });
|
||||||
|
_select->setItemRemovedCallback([this](uint64 itemId) {
|
||||||
|
if (auto peer = App::peerLoaded(itemId)) {
|
||||||
|
_inner->peerUnselected(peer);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
});
|
||||||
_select->setSubmittedCallback([this](bool) { onSubmit(); });
|
_select->setSubmittedCallback([this](bool) { onSubmit(); });
|
||||||
|
_select->setResizedCallback([this] { updateScrollSkips(); });
|
||||||
connect(_inner, SIGNAL(mustScrollTo(int, int)), scrollArea(), SLOT(scrollToY(int, int)));
|
connect(_inner, SIGNAL(mustScrollTo(int, int)), scrollArea(), SLOT(scrollToY(int, int)));
|
||||||
connect(_inner, SIGNAL(searchByUsername()), this, SLOT(onNeedSearchByUsername()));
|
connect(_inner, SIGNAL(searchByUsername()), this, SLOT(onNeedSearchByUsername()));
|
||||||
connect(_inner, SIGNAL(adminAdded()), this, SIGNAL(adminAdded()));
|
connect(_inner, SIGNAL(adminAdded()), this, SIGNAL(adminAdded()));
|
||||||
|
@ -184,7 +200,7 @@ void ContactsBox::peopleReceived(const MTPcontacts_Found &result, mtpRequestId r
|
||||||
}
|
}
|
||||||
|
|
||||||
_peopleRequest = 0;
|
_peopleRequest = 0;
|
||||||
_inner->updateSel();
|
_inner->updateSelection();
|
||||||
onScroll();
|
onScroll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -266,21 +282,31 @@ void ContactsBox::paintEvent(QPaintEvent *e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ContactsBox::updateScrollSkips() {
|
||||||
|
auto oldScrollHeight = scrollArea()->height();
|
||||||
|
auto inviting = (_inner->creating() == CreatingGroupGroup) || (_inner->channel() && _inner->membersFilter() == MembersFilter::Recent) || _inner->chat();
|
||||||
|
auto topSkip = st::boxTitleHeight + _select->height();
|
||||||
|
auto bottomSkip = inviting ? (st::boxButtonPadding.top() + _next.height() + st::boxButtonPadding.bottom()) : st::boxScrollSkip;
|
||||||
|
setScrollSkips(bottomSkip, topSkip);
|
||||||
|
auto scrollHeightDelta = scrollArea()->height() - oldScrollHeight;
|
||||||
|
if (scrollHeightDelta) {
|
||||||
|
scrollArea()->scrollToY(scrollArea()->scrollTop() - scrollHeightDelta);
|
||||||
|
}
|
||||||
|
|
||||||
|
_topShadow.setGeometry(0, st::boxTitleHeight + _select->height(), width(), st::lineWidth);
|
||||||
|
}
|
||||||
|
|
||||||
void ContactsBox::resizeEvent(QResizeEvent *e) {
|
void ContactsBox::resizeEvent(QResizeEvent *e) {
|
||||||
ItemListBox::resizeEvent(e);
|
ItemListBox::resizeEvent(e);
|
||||||
|
|
||||||
_select->resizeToWidth(width());
|
_select->resizeToWidth(width());
|
||||||
_select->moveToLeft(0, st::boxTitleHeight);
|
_select->moveToLeft(0, st::boxTitleHeight);
|
||||||
|
|
||||||
auto inviting = (_inner->creating() == CreatingGroupGroup) || (_inner->channel() && _inner->membersFilter() == MembersFilter::Recent) || _inner->chat();
|
updateScrollSkips();
|
||||||
auto topSkip = st::boxTitleHeight + _select->height();
|
|
||||||
auto bottomSkip = inviting ? (st::boxButtonPadding.top() + _next.height() + st::boxButtonPadding.bottom()) : st::boxScrollSkip;
|
|
||||||
setScrollSkips(bottomSkip, topSkip);
|
|
||||||
|
|
||||||
_inner->resize(width(), _inner->height());
|
_inner->resize(width(), _inner->height());
|
||||||
_next.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _next.height());
|
_next.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _next.height());
|
||||||
_cancel.moveToRight(st::boxButtonPadding.right() + _next.width() + st::boxButtonPadding.left(), _next.y());
|
_cancel.moveToRight(st::boxButtonPadding.right() + _next.width() + st::boxButtonPadding.left(), _next.y());
|
||||||
_topShadow.setGeometry(0, st::boxTitleHeight + _select->height(), width(), st::lineWidth);
|
|
||||||
if (_bottomShadow) _bottomShadow->setGeometry(0, height() - st::boxButtonPadding.bottom() - _next.height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth);
|
if (_bottomShadow) _bottomShadow->setGeometry(0, height() - st::boxButtonPadding.bottom() - _next.height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,7 +321,25 @@ void ContactsBox::onFilterUpdate(const QString &filter) {
|
||||||
_inner->updateFilter(filter);
|
_inner->updateFilter(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContactsBox::onChosenChanged() {
|
void ContactsBox::onPeerSelectedChanged(PeerData *peer, bool checked) {
|
||||||
|
if (checked) {
|
||||||
|
auto getColor = [peer]() -> const style::color &{
|
||||||
|
switch (peer->colorIndex) {
|
||||||
|
case 1: return st::historyPeer2UserpicFg;
|
||||||
|
case 2: return st::historyPeer3UserpicFg;
|
||||||
|
case 3: return st::historyPeer4UserpicFg;
|
||||||
|
case 4: return st::historyPeer5UserpicFg;
|
||||||
|
case 5: return st::historyPeer6UserpicFg;
|
||||||
|
case 6: return st::historyPeer7UserpicFg;
|
||||||
|
case 7: return st::historyPeer8UserpicFg;
|
||||||
|
default: return st::historyPeer1UserpicFg;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_select->addItem(peer->id, peer->shortName(), getColor(), PaintUserpicCallback(peer));
|
||||||
|
_select->clearQuery();
|
||||||
|
} else {
|
||||||
|
_select->removeItem(peer->id);
|
||||||
|
}
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -482,12 +526,8 @@ bool ContactsBox::creationFail(const RPCError &error) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ContactsBox::Inner::ContactData::ContactData() : name(st::boxWideWidth) {
|
ContactsBox::Inner::ContactData::ContactData(PeerData *peer, base::lambda_wrap<void()> updateCallback)
|
||||||
}
|
: checkbox(std_::make_unique<Ui::RoundImageCheckbox>(st::contactsPhotoCheckbox, std_::move(updateCallback), PaintUserpicCallback(peer))) {
|
||||||
|
|
||||||
ContactsBox::Inner::ContactData::ContactData(PeerData *peer, Ui::RoundImageCheckbox::UpdateCallback &&updateCallback)
|
|
||||||
: checkbox(std_::make_unique<Ui::RoundImageCheckbox>(st::contactsPhotoCheckbox, std_::move(updateCallback), PaintUserpicCallback(peer)))
|
|
||||||
, name(st::boxWideWidth) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ContactsBox::Inner::Inner(QWidget *parent, CreatingGroupType creating) : ScrolledWidget(parent)
|
ContactsBox::Inner::Inner(QWidget *parent, CreatingGroupType creating) : ScrolledWidget(parent)
|
||||||
|
@ -613,7 +653,7 @@ void ContactsBox::Inner::initList() {
|
||||||
if (i.key()->id == peerFromUser(_chat->creator)) continue;
|
if (i.key()->id == peerFromUser(_chat->creator)) continue;
|
||||||
if (!_allAdmins.checked() && _chat->admins.contains(i.key())) {
|
if (!_allAdmins.checked() && _chat->admins.contains(i.key())) {
|
||||||
admins.push_back(i.key());
|
admins.push_back(i.key());
|
||||||
_checkedContacts.insert(i.key(), true);
|
_checkedContacts.insert(i.key());
|
||||||
} else {
|
} else {
|
||||||
others.push_back(i.key());
|
others.push_back(i.key());
|
||||||
}
|
}
|
||||||
|
@ -1186,13 +1226,13 @@ void ContactsBox::Inner::leaveEvent(QEvent *e) {
|
||||||
void ContactsBox::Inner::mouseMoveEvent(QMouseEvent *e) {
|
void ContactsBox::Inner::mouseMoveEvent(QMouseEvent *e) {
|
||||||
_mouseSel = true;
|
_mouseSel = true;
|
||||||
_lastMousePos = e->globalPos();
|
_lastMousePos = e->globalPos();
|
||||||
updateSel();
|
updateSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContactsBox::Inner::mousePressEvent(QMouseEvent *e) {
|
void ContactsBox::Inner::mousePressEvent(QMouseEvent *e) {
|
||||||
_mouseSel = true;
|
_mouseSel = true;
|
||||||
_lastMousePos = e->globalPos();
|
_lastMousePos = e->globalPos();
|
||||||
updateSel();
|
updateSelection();
|
||||||
if (e->button() == Qt::LeftButton) {
|
if (e->button() == Qt::LeftButton) {
|
||||||
chooseParticipant();
|
chooseParticipant();
|
||||||
}
|
}
|
||||||
|
@ -1205,29 +1245,35 @@ void ContactsBox::Inner::chooseParticipant() {
|
||||||
_time = unixtime();
|
_time = unixtime();
|
||||||
if (_filter.isEmpty()) {
|
if (_filter.isEmpty()) {
|
||||||
if (_byUsernameSel >= 0 && _byUsernameSel < _byUsername.size()) {
|
if (_byUsernameSel >= 0 && _byUsernameSel < _byUsername.size()) {
|
||||||
if (d_byUsername[_byUsernameSel]->disabledChecked) return;
|
auto data = d_byUsername[_byUsernameSel];
|
||||||
changeCheckState(d_byUsername[_byUsernameSel], _byUsername[_byUsernameSel]);
|
auto peer = _byUsername[_byUsernameSel];
|
||||||
} else {
|
if (data->disabledChecked) return;
|
||||||
if (!_sel || contactData(_sel)->disabledChecked) return;
|
|
||||||
|
changeCheckState(data, peer);
|
||||||
|
} else if (_sel) {
|
||||||
|
auto data = contactData(_sel);
|
||||||
|
auto peer = _sel->history()->peer;
|
||||||
|
if (data->disabledChecked) return;
|
||||||
|
|
||||||
changeCheckState(_sel);
|
changeCheckState(_sel);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (_byUsernameSel >= 0 && _byUsernameSel < _byUsernameFiltered.size()) {
|
if (_byUsernameSel >= 0 && _byUsernameSel < _byUsernameFiltered.size()) {
|
||||||
if (d_byUsernameFiltered[_byUsernameSel]->disabledChecked) return;
|
auto data = d_byUsernameFiltered[_byUsernameSel];
|
||||||
changeCheckState(d_byUsernameFiltered[_byUsernameSel], _byUsernameFiltered[_byUsernameSel]);
|
auto peer = _byUsernameFiltered[_byUsernameSel];
|
||||||
|
if (data->disabledChecked) return;
|
||||||
|
|
||||||
ContactData *moving = d_byUsernameFiltered[_byUsernameSel];
|
int i = 0, l = d_byUsername.size();
|
||||||
int32 i = 0, l = d_byUsername.size();
|
|
||||||
for (; i < l; ++i) {
|
for (; i < l; ++i) {
|
||||||
if (d_byUsername[i] == moving) {
|
if (d_byUsername[i] == data) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (i == l) {
|
if (i == l) {
|
||||||
d_byUsername.push_back(moving);
|
d_byUsername.push_back(data);
|
||||||
_byUsername.push_back(_byUsernameFiltered[_byUsernameSel]);
|
_byUsername.push_back(peer);
|
||||||
for (i = 0, l = _byUsernameDatas.size(); i < l;) {
|
for (i = 0, l = _byUsernameDatas.size(); i < l;) {
|
||||||
if (_byUsernameDatas[i] == moving) {
|
if (_byUsernameDatas[i] == data) {
|
||||||
_byUsernameDatas.removeAt(i);
|
_byUsernameDatas.removeAt(i);
|
||||||
--l;
|
--l;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1235,9 +1281,14 @@ void ContactsBox::Inner::chooseParticipant() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (_filteredSel < 0 || _filteredSel >= _filtered.size() || contactData(_filtered[_filteredSel])->disabledChecked) return;
|
changeCheckState(data, peer);
|
||||||
changeCheckState(_filtered[_filteredSel]);
|
} else if (_filteredSel >= 0 && _filteredSel < _filtered.size()) {
|
||||||
|
auto data = contactData(_filtered[_filteredSel]);
|
||||||
|
auto peer = _filtered[_filteredSel]->history()->peer;
|
||||||
|
if (data->disabledChecked) return;
|
||||||
|
|
||||||
|
changeCheckState(data, peer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1304,25 +1355,43 @@ void ContactsBox::Inner::changeCheckState(Dialogs::Row *row) {
|
||||||
void ContactsBox::Inner::changeCheckState(ContactData *data, PeerData *peer) {
|
void ContactsBox::Inner::changeCheckState(ContactData *data, PeerData *peer) {
|
||||||
t_assert(usingMultiSelect());
|
t_assert(usingMultiSelect());
|
||||||
|
|
||||||
int32 cnt = _selCount;
|
|
||||||
if (data->checkbox->checked()) {
|
if (data->checkbox->checked()) {
|
||||||
data->checkbox->setChecked(false);
|
changePeerCheckState(data, peer, false);
|
||||||
_checkedContacts.remove(peer);
|
|
||||||
--_selCount;
|
|
||||||
} else if (selectedCount() < ((_channel && _channel->isMegagroup()) ? Global::MegagroupSizeMax() : Global::ChatSizeMax())) {
|
} else if (selectedCount() < ((_channel && _channel->isMegagroup()) ? Global::MegagroupSizeMax() : Global::ChatSizeMax())) {
|
||||||
data->checkbox->setChecked(true);
|
changePeerCheckState(data, peer, true);
|
||||||
_checkedContacts.insert(peer, true);
|
|
||||||
++_selCount;
|
|
||||||
} else if (_channel && !_channel->isMegagroup()) {
|
} else if (_channel && !_channel->isMegagroup()) {
|
||||||
Ui::showLayer(new MaxInviteBox(_channel->inviteLink()), KeepOtherLayers);
|
Ui::showLayer(new MaxInviteBox(_channel->inviteLink()), KeepOtherLayers);
|
||||||
} else if (!_channel && selectedCount() >= Global::ChatSizeMax() && selectedCount() < Global::MegagroupSizeMax()) {
|
} else if (!_channel && selectedCount() >= Global::ChatSizeMax() && selectedCount() < Global::MegagroupSizeMax()) {
|
||||||
Ui::showLayer(new InformBox(lng_profile_add_more_after_upgrade(lt_count, Global::MegagroupSizeMax())), KeepOtherLayers);
|
Ui::showLayer(new InformBox(lng_profile_add_more_after_upgrade(lt_count, Global::MegagroupSizeMax())), KeepOtherLayers);
|
||||||
}
|
}
|
||||||
if (cnt != _selCount) emit chosenChanged();
|
}
|
||||||
|
|
||||||
|
void ContactsBox::Inner::peerUnselected(PeerData *peer) {
|
||||||
|
// If data is nullptr we simply won't do anything.
|
||||||
|
auto data = _contactsData.value(peer, nullptr);
|
||||||
|
changePeerCheckState(data, peer, false, ChangeStateWay::SkipCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContactsBox::Inner::setPeerSelectedChangedCallback(base::lambda_unique<void(PeerData *peer, bool selected)> callback) {
|
||||||
|
_peerSelectedChangedCallback = std_::move(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContactsBox::Inner::changePeerCheckState(ContactData *data, PeerData *peer, bool checked, ChangeStateWay useCallback) {
|
||||||
|
if (data) {
|
||||||
|
data->checkbox->setChecked(checked);
|
||||||
|
}
|
||||||
|
if (checked) {
|
||||||
|
_checkedContacts.insert(peer);
|
||||||
|
} else {
|
||||||
|
_checkedContacts.remove(peer);
|
||||||
|
}
|
||||||
|
if (useCallback != ChangeStateWay::SkipCallback) {
|
||||||
|
_peerSelectedChangedCallback(peer, checked);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int32 ContactsBox::Inner::selectedCount() const {
|
int32 ContactsBox::Inner::selectedCount() const {
|
||||||
int32 result = _selCount;
|
auto result = _checkedContacts.size();
|
||||||
if (_chat) {
|
if (_chat) {
|
||||||
result += qMax(_chat->count, 1);
|
result += qMax(_chat->count, 1);
|
||||||
} else if (_channel) {
|
} else if (_channel) {
|
||||||
|
@ -1333,7 +1402,7 @@ int32 ContactsBox::Inner::selectedCount() const {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContactsBox::Inner::updateSel() {
|
void ContactsBox::Inner::updateSelection() {
|
||||||
if (!_mouseSel) return;
|
if (!_mouseSel) return;
|
||||||
|
|
||||||
QPoint p(mapFromGlobal(_lastMousePos));
|
QPoint p(mapFromGlobal(_lastMousePos));
|
||||||
|
|
|
@ -56,8 +56,7 @@ public:
|
||||||
signals:
|
signals:
|
||||||
void adminAdded();
|
void adminAdded();
|
||||||
|
|
||||||
public slots:
|
private slots:
|
||||||
void onChosenChanged();
|
|
||||||
void onScroll();
|
void onScroll();
|
||||||
|
|
||||||
void onInvite();
|
void onInvite();
|
||||||
|
@ -80,7 +79,9 @@ protected:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void init();
|
void init();
|
||||||
|
void updateScrollSkips();
|
||||||
void onFilterUpdate(const QString &filter);
|
void onFilterUpdate(const QString &filter);
|
||||||
|
void onPeerSelectedChanged(PeerData *peer, bool checked);
|
||||||
|
|
||||||
class Inner;
|
class Inner;
|
||||||
ChildWidget<Inner> _inner;
|
ChildWidget<Inner> _inner;
|
||||||
|
@ -136,10 +137,11 @@ public:
|
||||||
Inner(QWidget *parent, ChatData *chat, MembersFilter membersFilter);
|
Inner(QWidget *parent, ChatData *chat, MembersFilter membersFilter);
|
||||||
Inner(QWidget *parent, UserData *bot);
|
Inner(QWidget *parent, UserData *bot);
|
||||||
|
|
||||||
void init();
|
void setPeerSelectedChangedCallback(base::lambda_unique<void(PeerData *peer, bool selected)> callback);
|
||||||
void initList();
|
void peerUnselected(PeerData *peer);
|
||||||
|
|
||||||
void updateFilter(QString filter = QString());
|
void updateFilter(QString filter = QString());
|
||||||
|
void updateSelection();
|
||||||
|
|
||||||
void selectSkip(int32 dir);
|
void selectSkip(int32 dir);
|
||||||
void selectSkipPage(int32 h, int32 dir);
|
void selectSkipPage(int32 h, int32 dir);
|
||||||
|
@ -177,14 +179,12 @@ public:
|
||||||
signals:
|
signals:
|
||||||
void mustScrollTo(int ymin, int ymax);
|
void mustScrollTo(int ymin, int ymax);
|
||||||
void searchByUsername();
|
void searchByUsername();
|
||||||
void chosenChanged();
|
|
||||||
void adminAdded();
|
void adminAdded();
|
||||||
void addRequested();
|
void addRequested();
|
||||||
|
|
||||||
public slots:
|
private slots:
|
||||||
void onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow);
|
void onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow);
|
||||||
|
|
||||||
void updateSel();
|
|
||||||
void peerUpdated(PeerData *peer);
|
void peerUpdated(PeerData *peer);
|
||||||
void onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars);
|
void onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars);
|
||||||
|
|
||||||
|
@ -204,8 +204,8 @@ protected:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct ContactData {
|
struct ContactData {
|
||||||
ContactData();
|
ContactData() = default;
|
||||||
ContactData(PeerData *peer, Ui::RoundImageCheckbox::UpdateCallback &&updateCallback);
|
ContactData(PeerData *peer, base::lambda_wrap<void()> updateCallback);
|
||||||
|
|
||||||
std_::unique_ptr<Ui::RoundImageCheckbox> checkbox;
|
std_::unique_ptr<Ui::RoundImageCheckbox> checkbox;
|
||||||
Text name;
|
Text name;
|
||||||
|
@ -214,6 +214,9 @@ private:
|
||||||
bool disabledChecked = false;
|
bool disabledChecked = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void init();
|
||||||
|
void initList();
|
||||||
|
|
||||||
void updateRowWithTop(int rowTop);
|
void updateRowWithTop(int rowTop);
|
||||||
int getSelectedRowTop() const;
|
int getSelectedRowTop() const;
|
||||||
void updateSelectedRow();
|
void updateSelectedRow();
|
||||||
|
@ -227,6 +230,11 @@ private:
|
||||||
|
|
||||||
void changeCheckState(Dialogs::Row *row);
|
void changeCheckState(Dialogs::Row *row);
|
||||||
void changeCheckState(ContactData *data, PeerData *peer);
|
void changeCheckState(ContactData *data, PeerData *peer);
|
||||||
|
enum class ChangeStateWay {
|
||||||
|
Default,
|
||||||
|
SkipCallback,
|
||||||
|
};
|
||||||
|
void changePeerCheckState(ContactData *data, PeerData *peer, bool checked, ChangeStateWay useCallback = ChangeStateWay::Default);
|
||||||
|
|
||||||
template <typename FilterCallback>
|
template <typename FilterCallback>
|
||||||
void addDialogsToList(FilterCallback callback);
|
void addDialogsToList(FilterCallback callback);
|
||||||
|
@ -235,6 +243,8 @@ private:
|
||||||
return (_chat != nullptr) || (_creating != CreatingGroupNone && (!_channel || _membersFilter != MembersFilter::Admins));
|
return (_chat != nullptr) || (_creating != CreatingGroupNone && (!_channel || _membersFilter != MembersFilter::Admins));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
base::lambda_unique<void(PeerData *peer, bool selected)> _peerSelectedChangedCallback;
|
||||||
|
|
||||||
int32 _rowHeight;
|
int32 _rowHeight;
|
||||||
int _newItemHeight = 0;
|
int _newItemHeight = 0;
|
||||||
bool _newItemSel = false;
|
bool _newItemSel = false;
|
||||||
|
@ -261,24 +271,22 @@ private:
|
||||||
Dialogs::IndexedList *_contacts = nullptr;
|
Dialogs::IndexedList *_contacts = nullptr;
|
||||||
Dialogs::Row *_sel = nullptr;
|
Dialogs::Row *_sel = nullptr;
|
||||||
QString _filter;
|
QString _filter;
|
||||||
typedef QVector<Dialogs::Row*> FilteredDialogs;
|
using FilteredDialogs = QVector<Dialogs::Row*>;
|
||||||
FilteredDialogs _filtered;
|
FilteredDialogs _filtered;
|
||||||
int _filteredSel = -1;
|
int _filteredSel = -1;
|
||||||
bool _mouseSel = false;
|
bool _mouseSel = false;
|
||||||
|
|
||||||
int _selCount = 0;
|
using ContactsData = QMap<PeerData*, ContactData*>;
|
||||||
|
|
||||||
typedef QMap<PeerData*, ContactData*> ContactsData;
|
|
||||||
ContactsData _contactsData;
|
ContactsData _contactsData;
|
||||||
typedef QMap<PeerData*, bool> CheckedContacts;
|
using CheckedContacts = OrderedSet<PeerData*>;
|
||||||
CheckedContacts _checkedContacts;
|
CheckedContacts _checkedContacts;
|
||||||
|
|
||||||
ContactData *contactData(Dialogs::Row *row);
|
ContactData *contactData(Dialogs::Row *row);
|
||||||
|
|
||||||
bool _searching = false;
|
bool _searching = false;
|
||||||
QString _lastQuery;
|
QString _lastQuery;
|
||||||
typedef QVector<PeerData*> ByUsernameRows;
|
using ByUsernameRows = QVector<PeerData*>;
|
||||||
typedef QVector<ContactData*> ByUsernameDatas;
|
using ByUsernameDatas = QVector<ContactData*>;
|
||||||
ByUsernameRows _byUsername, _byUsernameFiltered;
|
ByUsernameRows _byUsername, _byUsernameFiltered;
|
||||||
ByUsernameDatas d_byUsername, d_byUsernameFiltered; // filtered is partly subset of d_byUsername, partly subset of _byUsernameDatas
|
ByUsernameDatas d_byUsername, d_byUsernameFiltered; // filtered is partly subset of d_byUsername, partly subset of _byUsernameDatas
|
||||||
ByUsernameDatas _byUsernameDatas;
|
ByUsernameDatas _byUsernameDatas;
|
||||||
|
|
|
@ -473,7 +473,7 @@ void ShareBox::Inner::paintChat(Painter &p, Chat *chat, int index) {
|
||||||
chat->name.drawLeftElided(p, x + nameLeft, y + nameTop, nameWidth, outerWidth, 2, style::al_top, 0, -1, 0, true);
|
chat->name.drawLeftElided(p, x + nameLeft, y + nameTop, nameWidth, outerWidth, 2, style::al_top, 0, -1, 0, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
ShareBox::Inner::Chat::Chat(PeerData *peer, Ui::RoundImageCheckbox::UpdateCallback &&updateCallback)
|
ShareBox::Inner::Chat::Chat(PeerData *peer, base::lambda_wrap<void()> updateCallback)
|
||||||
: peer(peer)
|
: peer(peer)
|
||||||
, checkbox(st::sharePhotoCheckbox, std_::move(updateCallback), PaintUserpicCallback(peer))
|
, checkbox(st::sharePhotoCheckbox, std_::move(updateCallback), PaintUserpicCallback(peer))
|
||||||
, name(st::sharePhotoCheckbox.imageRadius * 2) {
|
, name(st::sharePhotoCheckbox.imageRadius * 2) {
|
||||||
|
|
|
@ -153,7 +153,7 @@ private:
|
||||||
int displayedChatsCount() const;
|
int displayedChatsCount() const;
|
||||||
|
|
||||||
struct Chat {
|
struct Chat {
|
||||||
Chat(PeerData *peer, Ui::RoundImageCheckbox::UpdateCallback &&updateCallback);
|
Chat(PeerData *peer, base::lambda_wrap<void()> updateCallback);
|
||||||
|
|
||||||
PeerData *peer;
|
PeerData *peer;
|
||||||
Ui::RoundImageCheckbox checkbox;
|
Ui::RoundImageCheckbox checkbox;
|
||||||
|
|
|
@ -359,6 +359,10 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lambda_wrap clone() const {
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename Lambda, typename = IsOther<Lambda>>
|
template <typename Lambda, typename = IsOther<Lambda>>
|
||||||
lambda_wrap(const Lambda &other) : Parent(&internal::lambda_wrap_helper_copy<Lambda, Return, Args...>::instance, typename Parent::Private()) {
|
lambda_wrap(const Lambda &other) : Parent(&internal::lambda_wrap_helper_copy<Lambda, Return, Args...>::instance, typename Parent::Private()) {
|
||||||
internal::lambda_wrap_helper_copy<Lambda, Return, Args...>::construct_copy_lambda_method(this->storage_, &other);
|
internal::lambda_wrap_helper_copy<Lambda, Return, Args...>::construct_copy_lambda_method(this->storage_, &other);
|
||||||
|
|
|
@ -102,6 +102,22 @@ namespace anim {
|
||||||
float64 easeInQuint(const float64 &delta, const float64 &dt);
|
float64 easeInQuint(const float64 &delta, const float64 &dt);
|
||||||
float64 easeOutQuint(const float64 &delta, const float64 &dt);
|
float64 easeOutQuint(const float64 &delta, const float64 &dt);
|
||||||
|
|
||||||
|
template <int BumpRatioNumerator, int BumpRatioDenominator>
|
||||||
|
float64 bumpy(const float64 &delta, const float64 &dt) {
|
||||||
|
struct Bumpy {
|
||||||
|
Bumpy()
|
||||||
|
: bump(BumpRatioNumerator / float64(BumpRatioDenominator))
|
||||||
|
, dt0(bump - sqrt(bump * (bump - 1.)))
|
||||||
|
, k(1 / (2 * dt0 - 1)) {
|
||||||
|
}
|
||||||
|
float64 bump;
|
||||||
|
float64 dt0;
|
||||||
|
float64 k;
|
||||||
|
};
|
||||||
|
static Bumpy data;
|
||||||
|
return delta * (data.bump - data.k * (dt - data.dt0) * (dt - data.dt0));
|
||||||
|
}
|
||||||
|
|
||||||
class fvalue { // float animated value
|
class fvalue { // float animated value
|
||||||
public:
|
public:
|
||||||
using ValueType = float64;
|
using ValueType = float64;
|
||||||
|
@ -499,7 +515,7 @@ public:
|
||||||
template <typename Lambda>
|
template <typename Lambda>
|
||||||
void start(Lambda &&updateCallback, const ValueType &from, const ValueType &to, float64 duration, anim::transition transition = anim::linear) {
|
void start(Lambda &&updateCallback, const ValueType &from, const ValueType &to, float64 duration, anim::transition transition = anim::linear) {
|
||||||
if (!_data) {
|
if (!_data) {
|
||||||
_data = std_::make_unique<Data>(from, std_::move(updateCallback));
|
_data = std_::make_unique<Data>(from, std_::forward<Lambda>(updateCallback));
|
||||||
}
|
}
|
||||||
_data->value.start(to);
|
_data->value.start(to);
|
||||||
_data->duration = duration;
|
_data->duration = duration;
|
||||||
|
@ -522,6 +538,11 @@ private:
|
||||||
, a_animation(animation(this, &Data::step))
|
, a_animation(animation(this, &Data::step))
|
||||||
, updateCallback(std_::move(updateCallback)) {
|
, updateCallback(std_::move(updateCallback)) {
|
||||||
}
|
}
|
||||||
|
Data(const ValueType &from, const base::lambda_wrap<void()> &updateCallback)
|
||||||
|
: value(from, from)
|
||||||
|
, a_animation(animation(this, &Data::step))
|
||||||
|
, updateCallback(base::lambda_wrap<void()>(updateCallback)) {
|
||||||
|
}
|
||||||
void step(float64 ms, bool timer) {
|
void step(float64 ms, bool timer) {
|
||||||
auto dt = (ms >= duration) ? 1. : (ms / duration);
|
auto dt = (ms >= duration) ? 1. : (ms / duration);
|
||||||
if (dt >= 1) {
|
if (dt >= 1) {
|
||||||
|
|
|
@ -56,25 +56,9 @@ void prepareCheckCaches(const style::RoundImageCheckbox *st, QPixmap &checkBgCac
|
||||||
checkFullCache.setDevicePixelRatio(cRetinaFactor());
|
checkFullCache.setDevicePixelRatio(cRetinaFactor());
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AnimBumpy {
|
|
||||||
AnimBumpy(float64 bump) : bump(bump)
|
|
||||||
, dt0(bump - sqrt(bump * (bump - 1.)))
|
|
||||||
, k(1 / (2 * dt0 - 1)) {
|
|
||||||
}
|
|
||||||
float64 bump;
|
|
||||||
float64 dt0;
|
|
||||||
float64 k;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <int BumpRatioPercent>
|
|
||||||
float64 anim_bumpy(const float64 &delta, const float64 &dt) {
|
|
||||||
static AnimBumpy data = { BumpRatioPercent / 100. };
|
|
||||||
return delta * (data.bump - data.k * (dt - data.dt0) * (dt - data.dt0));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
RoundImageCheckbox::RoundImageCheckbox(const style::RoundImageCheckbox &st, UpdateCallback &&updateCallback, PaintRoundImage &&paintRoundImage)
|
RoundImageCheckbox::RoundImageCheckbox(const style::RoundImageCheckbox &st, base::lambda_wrap<void()> updateCallback, PaintRoundImage paintRoundImage)
|
||||||
: _st(st)
|
: _st(st)
|
||||||
, _updateCallback(std_::move(updateCallback))
|
, _updateCallback(std_::move(updateCallback))
|
||||||
, _paintRoundImage(std_::move(paintRoundImage)) {
|
, _paintRoundImage(std_::move(paintRoundImage)) {
|
||||||
|
@ -84,13 +68,14 @@ RoundImageCheckbox::RoundImageCheckbox(const style::RoundImageCheckbox &st, Upda
|
||||||
void RoundImageCheckbox::paint(Painter &p, int x, int y, int outerWidth) {
|
void RoundImageCheckbox::paint(Painter &p, int x, int y, int outerWidth) {
|
||||||
auto selectionLevel = _selection.current(_checked ? 1. : 0.);
|
auto selectionLevel = _selection.current(_checked ? 1. : 0.);
|
||||||
if (_selection.animating()) {
|
if (_selection.animating()) {
|
||||||
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
|
|
||||||
auto userpicRadius = qRound(kWideScale * (_st.imageRadius + (_st.imageSmallRadius - _st.imageRadius) * selectionLevel));
|
auto userpicRadius = qRound(kWideScale * (_st.imageRadius + (_st.imageSmallRadius - _st.imageRadius) * selectionLevel));
|
||||||
auto userpicShift = kWideScale * _st.imageRadius - userpicRadius;
|
auto userpicShift = kWideScale * _st.imageRadius - userpicRadius;
|
||||||
auto userpicLeft = x - (kWideScale - 1) * _st.imageRadius + userpicShift;
|
auto userpicLeft = x - (kWideScale - 1) * _st.imageRadius + userpicShift;
|
||||||
auto userpicTop = y - (kWideScale - 1) * _st.imageRadius + userpicShift;
|
auto userpicTop = y - (kWideScale - 1) * _st.imageRadius + userpicShift;
|
||||||
auto to = QRect(userpicLeft, userpicTop, userpicRadius * 2, userpicRadius * 2);
|
auto to = QRect(userpicLeft, userpicTop, userpicRadius * 2, userpicRadius * 2);
|
||||||
auto from = QRect(QPoint(0, 0), _wideCache.size());
|
auto from = QRect(QPoint(0, 0), _wideCache.size());
|
||||||
|
|
||||||
|
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
|
||||||
p.drawPixmapLeft(to, outerWidth, _wideCache, from);
|
p.drawPixmapLeft(to, outerWidth, _wideCache, from);
|
||||||
p.setRenderHint(QPainter::SmoothPixmapTransform, false);
|
p.setRenderHint(QPainter::SmoothPixmapTransform, false);
|
||||||
} else {
|
} else {
|
||||||
|
@ -159,7 +144,7 @@ void RoundImageCheckbox::setChecked(bool checked, SetStyle speed) {
|
||||||
_checked = checked;
|
_checked = checked;
|
||||||
if (_checked) {
|
if (_checked) {
|
||||||
_icons.push_back(Icon());
|
_icons.push_back(Icon());
|
||||||
_icons.back().fadeIn.start(UpdateCallback(_updateCallback), 0, 1, _st.selectDuration);
|
_icons.back().fadeIn.start(_updateCallback, 0, 1, _st.selectDuration);
|
||||||
if (speed != SetStyle::Animated) {
|
if (speed != SetStyle::Animated) {
|
||||||
_icons.back().fadeIn.finish();
|
_icons.back().fadeIn.finish();
|
||||||
}
|
}
|
||||||
|
@ -176,7 +161,7 @@ void RoundImageCheckbox::setChecked(bool checked, SetStyle speed) {
|
||||||
}
|
}
|
||||||
if (speed == SetStyle::Animated) {
|
if (speed == SetStyle::Animated) {
|
||||||
prepareWideCache();
|
prepareWideCache();
|
||||||
_selection.start(UpdateCallback(_updateCallback), _checked ? 0 : 1, _checked ? 1 : 0, _st.selectDuration, anim_bumpy<125>);
|
_selection.start(_updateCallback, _checked ? 0 : 1, _checked ? 1 : 0, _st.selectDuration, anim::bumpy<125, 100>);
|
||||||
} else {
|
} else {
|
||||||
_selection.finish();
|
_selection.finish();
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,7 @@ namespace Ui {
|
||||||
class RoundImageCheckbox {
|
class RoundImageCheckbox {
|
||||||
public:
|
public:
|
||||||
using PaintRoundImage = base::lambda_unique<void(Painter &p, int x, int y, int outerWidth, int size)>;
|
using PaintRoundImage = base::lambda_unique<void(Painter &p, int x, int y, int outerWidth, int size)>;
|
||||||
using UpdateCallback = base::lambda_wrap<void()>;
|
RoundImageCheckbox(const style::RoundImageCheckbox &st, base::lambda_wrap<void()> updateCallback, PaintRoundImage paintRoundImage);
|
||||||
RoundImageCheckbox(const style::RoundImageCheckbox &st, UpdateCallback &&updateCallback, PaintRoundImage &&paintRoundImage);
|
|
||||||
|
|
||||||
void paint(Painter &p, int x, int y, int outerWidth);
|
void paint(Painter &p, int x, int y, int outerWidth);
|
||||||
float64 checkedAnimationRatio() const;
|
float64 checkedAnimationRatio() const;
|
||||||
|
@ -53,7 +52,7 @@ private:
|
||||||
void prepareWideCheckIconCache(Icon *icon);
|
void prepareWideCheckIconCache(Icon *icon);
|
||||||
|
|
||||||
const style::RoundImageCheckbox &_st;
|
const style::RoundImageCheckbox &_st;
|
||||||
UpdateCallback _updateCallback;
|
base::lambda_wrap<void()> _updateCallback;
|
||||||
PaintRoundImage _paintRoundImage;
|
PaintRoundImage _paintRoundImage;
|
||||||
|
|
||||||
bool _checked = false;
|
bool _checked = false;
|
||||||
|
|
|
@ -184,8 +184,8 @@ void FlatInput::paintEvent(QPaintEvent *e) {
|
||||||
p.drawRoundedRect(QRectF(0, 0, width(), height()).marginsRemoved(QMarginsF(_st.borderWidth / 2., _st.borderWidth / 2., _st.borderWidth / 2., _st.borderWidth / 2.)), st::buttonRadius - (_st.borderWidth / 2.), st::buttonRadius - (_st.borderWidth / 2.));
|
p.drawRoundedRect(QRectF(0, 0, width(), height()).marginsRemoved(QMarginsF(_st.borderWidth / 2., _st.borderWidth / 2., _st.borderWidth / 2., _st.borderWidth / 2.)), st::buttonRadius - (_st.borderWidth / 2.), st::buttonRadius - (_st.borderWidth / 2.));
|
||||||
p.setRenderHint(QPainter::HighQualityAntialiasing, false);
|
p.setRenderHint(QPainter::HighQualityAntialiasing, false);
|
||||||
|
|
||||||
if (_st.imgRect.pxWidth()) {
|
if (!_st.icon.empty()) {
|
||||||
p.drawSprite(_st.imgPos, _st.imgRect);
|
_st.icon.paint(p, 0, 0, width());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool phDraw = _phVisible;
|
bool phDraw = _phVisible;
|
||||||
|
@ -683,10 +683,10 @@ void InputArea::checkContentHeight() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InputArea::InputAreaInner::InputAreaInner(InputArea *parent) : QTextEdit(parent) {
|
InputArea::Inner::Inner(InputArea *parent) : QTextEdit(parent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InputArea::InputAreaInner::viewportEvent(QEvent *e) {
|
bool InputArea::Inner::viewportEvent(QEvent *e) {
|
||||||
if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) {
|
if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) {
|
||||||
QTouchEvent *ev = static_cast<QTouchEvent*>(e);
|
QTouchEvent *ev = static_cast<QTouchEvent*>(e);
|
||||||
if (ev->device()->type() == QTouchDevice::TouchScreen) {
|
if (ev->device()->type() == QTouchDevice::TouchScreen) {
|
||||||
|
@ -790,7 +790,7 @@ void InputArea::contextMenuEvent(QContextMenuEvent *e) {
|
||||||
_inner.contextMenuEvent(e);
|
_inner.contextMenuEvent(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputArea::InputAreaInner::focusInEvent(QFocusEvent *e) {
|
void InputArea::Inner::focusInEvent(QFocusEvent *e) {
|
||||||
f()->focusInInner();
|
f()->focusInInner();
|
||||||
QTextEdit::focusInEvent(e);
|
QTextEdit::focusInEvent(e);
|
||||||
emit f()->focused();
|
emit f()->focused();
|
||||||
|
@ -807,7 +807,7 @@ void InputArea::focusInInner() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputArea::InputAreaInner::focusOutEvent(QFocusEvent *e) {
|
void InputArea::Inner::focusOutEvent(QFocusEvent *e) {
|
||||||
f()->focusOutInner();
|
f()->focusOutInner();
|
||||||
QTextEdit::focusOutEvent(e);
|
QTextEdit::focusOutEvent(e);
|
||||||
emit f()->blurred();
|
emit f()->blurred();
|
||||||
|
@ -943,7 +943,7 @@ void InputArea::insertEmoji(EmojiPtr emoji, QTextCursor c) {
|
||||||
c.insertText(objectReplacement, imageFormat);
|
c.insertText(objectReplacement, imageFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant InputArea::InputAreaInner::loadResource(int type, const QUrl &name) {
|
QVariant InputArea::Inner::loadResource(int type, const QUrl &name) {
|
||||||
QString imageName = name.toDisplayString();
|
QString imageName = name.toDisplayString();
|
||||||
if (imageName.startsWith(qstr("emoji://e."))) {
|
if (imageName.startsWith(qstr("emoji://e."))) {
|
||||||
if (EmojiPtr emoji = emojiFromUrl(imageName)) {
|
if (EmojiPtr emoji = emojiFromUrl(imageName)) {
|
||||||
|
@ -1193,7 +1193,7 @@ void InputArea::updatePlaceholder() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QMimeData *InputArea::InputAreaInner::createMimeDataFromSelection() const {
|
QMimeData *InputArea::Inner::createMimeDataFromSelection() const {
|
||||||
QMimeData *result = new QMimeData();
|
QMimeData *result = new QMimeData();
|
||||||
QTextCursor c(textCursor());
|
QTextCursor c(textCursor());
|
||||||
int32 start = c.selectionStart(), end = c.selectionEnd();
|
int32 start = c.selectionStart(), end = c.selectionEnd();
|
||||||
|
@ -1211,7 +1211,7 @@ void InputArea::setCtrlEnterSubmit(CtrlEnterSubmit ctrlEnterSubmit) {
|
||||||
_ctrlEnterSubmit = ctrlEnterSubmit;
|
_ctrlEnterSubmit = ctrlEnterSubmit;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputArea::InputAreaInner::keyPressEvent(QKeyEvent *e) {
|
void InputArea::Inner::keyPressEvent(QKeyEvent *e) {
|
||||||
bool shift = e->modifiers().testFlag(Qt::ShiftModifier), alt = e->modifiers().testFlag(Qt::AltModifier);
|
bool shift = e->modifiers().testFlag(Qt::ShiftModifier), alt = e->modifiers().testFlag(Qt::AltModifier);
|
||||||
bool macmeta = (cPlatform() == dbipMac || cPlatform() == dbipMacOld) && e->modifiers().testFlag(Qt::ControlModifier) && !e->modifiers().testFlag(Qt::MetaModifier) && !e->modifiers().testFlag(Qt::AltModifier);
|
bool macmeta = (cPlatform() == dbipMac || cPlatform() == dbipMacOld) && e->modifiers().testFlag(Qt::ControlModifier) && !e->modifiers().testFlag(Qt::MetaModifier) && !e->modifiers().testFlag(Qt::AltModifier);
|
||||||
bool ctrl = e->modifiers().testFlag(Qt::ControlModifier) || e->modifiers().testFlag(Qt::MetaModifier);
|
bool ctrl = e->modifiers().testFlag(Qt::ControlModifier) || e->modifiers().testFlag(Qt::MetaModifier);
|
||||||
|
@ -1276,11 +1276,11 @@ void InputArea::InputAreaInner::keyPressEvent(QKeyEvent *e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputArea::InputAreaInner::paintEvent(QPaintEvent *e) {
|
void InputArea::Inner::paintEvent(QPaintEvent *e) {
|
||||||
return QTextEdit::paintEvent(e);
|
return QTextEdit::paintEvent(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputArea::InputAreaInner::contextMenuEvent(QContextMenuEvent *e) {
|
void InputArea::Inner::contextMenuEvent(QContextMenuEvent *e) {
|
||||||
if (QMenu *menu = createStandardContextMenu()) {
|
if (QMenu *menu = createStandardContextMenu()) {
|
||||||
(new PopupMenu(menu))->popup(e->globalPos());
|
(new PopupMenu(menu))->popup(e->globalPos());
|
||||||
}
|
}
|
||||||
|
@ -1338,7 +1338,9 @@ InputField::InputField(QWidget *parent, const style::InputField &st, const QStri
|
||||||
|
|
||||||
_inner.setWordWrapMode(QTextOption::NoWrap);
|
_inner.setWordWrapMode(QTextOption::NoWrap);
|
||||||
|
|
||||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
if (_st.textBg->c.alphaF() >= 1.) {
|
||||||
|
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||||
|
}
|
||||||
|
|
||||||
_inner.setFont(_st.font->f);
|
_inner.setFont(_st.font->f);
|
||||||
_inner.setAlignment(_st.textAlign);
|
_inner.setAlignment(_st.textAlign);
|
||||||
|
@ -1380,10 +1382,10 @@ void InputField::onTouchTimer() {
|
||||||
_touchRightButton = true;
|
_touchRightButton = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
InputField::InputFieldInner::InputFieldInner(InputField *parent) : QTextEdit(parent) {
|
InputField::Inner::Inner(InputField *parent) : QTextEdit(parent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InputField::InputFieldInner::viewportEvent(QEvent *e) {
|
bool InputField::Inner::viewportEvent(QEvent *e) {
|
||||||
if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) {
|
if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) {
|
||||||
QTouchEvent *ev = static_cast<QTouchEvent*>(e);
|
QTouchEvent *ev = static_cast<QTouchEvent*>(e);
|
||||||
if (ev->device()->type() == QTouchDevice::TouchScreen) {
|
if (ev->device()->type() == QTouchDevice::TouchScreen) {
|
||||||
|
@ -1437,7 +1439,9 @@ void InputField::paintEvent(QPaintEvent *e) {
|
||||||
Painter p(this);
|
Painter p(this);
|
||||||
|
|
||||||
QRect r(rect().intersected(e->rect()));
|
QRect r(rect().intersected(e->rect()));
|
||||||
p.fillRect(r, st::white->b);
|
if (_st.textBg->c.alphaF() > 0.) {
|
||||||
|
p.fillRect(r, _st.textBg);
|
||||||
|
}
|
||||||
if (_st.border) {
|
if (_st.border) {
|
||||||
p.fillRect(0, height() - _st.border, width(), _st.border, _st.borderFg->b);
|
p.fillRect(0, height() - _st.border, width(), _st.border, _st.borderFg->b);
|
||||||
}
|
}
|
||||||
|
@ -1446,9 +1450,6 @@ void InputField::paintEvent(QPaintEvent *e) {
|
||||||
p.fillRect(0, height() - _st.borderActive, width(), _st.borderActive, a_borderFg.current());
|
p.fillRect(0, height() - _st.borderActive, width(), _st.borderActive, a_borderFg.current());
|
||||||
p.setOpacity(1);
|
p.setOpacity(1);
|
||||||
}
|
}
|
||||||
if (_st.iconSprite.pxWidth()) {
|
|
||||||
p.drawSpriteLeft(_st.iconPosition, width(), _st.iconSprite);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool drawPlaceholder = _placeholderVisible;
|
bool drawPlaceholder = _placeholderVisible;
|
||||||
if (_a_placeholderShift.animating()) {
|
if (_a_placeholderShift.animating()) {
|
||||||
|
@ -1490,7 +1491,7 @@ void InputField::contextMenuEvent(QContextMenuEvent *e) {
|
||||||
_inner.contextMenuEvent(e);
|
_inner.contextMenuEvent(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputField::InputFieldInner::focusInEvent(QFocusEvent *e) {
|
void InputField::Inner::focusInEvent(QFocusEvent *e) {
|
||||||
f()->focusInInner();
|
f()->focusInInner();
|
||||||
QTextEdit::focusInEvent(e);
|
QTextEdit::focusInEvent(e);
|
||||||
emit f()->focused();
|
emit f()->focused();
|
||||||
|
@ -1507,7 +1508,7 @@ void InputField::focusInInner() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputField::InputFieldInner::focusOutEvent(QFocusEvent *e) {
|
void InputField::Inner::focusOutEvent(QFocusEvent *e) {
|
||||||
f()->focusOutInner();
|
f()->focusOutInner();
|
||||||
QTextEdit::focusOutEvent(e);
|
QTextEdit::focusOutEvent(e);
|
||||||
emit f()->blurred();
|
emit f()->blurred();
|
||||||
|
@ -1643,7 +1644,7 @@ void InputField::insertEmoji(EmojiPtr emoji, QTextCursor c) {
|
||||||
c.insertText(objectReplacement, imageFormat);
|
c.insertText(objectReplacement, imageFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant InputField::InputFieldInner::loadResource(int type, const QUrl &name) {
|
QVariant InputField::Inner::loadResource(int type, const QUrl &name) {
|
||||||
QString imageName = name.toDisplayString();
|
QString imageName = name.toDisplayString();
|
||||||
if (imageName.startsWith(qstr("emoji://e."))) {
|
if (imageName.startsWith(qstr("emoji://e."))) {
|
||||||
if (EmojiPtr emoji = emojiFromUrl(imageName)) {
|
if (EmojiPtr emoji = emojiFromUrl(imageName)) {
|
||||||
|
@ -1928,7 +1929,7 @@ void InputField::setPlaceholderHidden(bool forcePlaceholderHidden) {
|
||||||
updatePlaceholder();
|
updatePlaceholder();
|
||||||
}
|
}
|
||||||
|
|
||||||
QMimeData *InputField::InputFieldInner::createMimeDataFromSelection() const {
|
QMimeData *InputField::Inner::createMimeDataFromSelection() const {
|
||||||
QMimeData *result = new QMimeData();
|
QMimeData *result = new QMimeData();
|
||||||
QTextCursor c(textCursor());
|
QTextCursor c(textCursor());
|
||||||
int32 start = c.selectionStart(), end = c.selectionEnd();
|
int32 start = c.selectionStart(), end = c.selectionEnd();
|
||||||
|
@ -1942,7 +1943,7 @@ void InputField::customUpDown(bool custom) {
|
||||||
_customUpDown = custom;
|
_customUpDown = custom;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputField::InputFieldInner::keyPressEvent(QKeyEvent *e) {
|
void InputField::Inner::keyPressEvent(QKeyEvent *e) {
|
||||||
bool shift = e->modifiers().testFlag(Qt::ShiftModifier), alt = e->modifiers().testFlag(Qt::AltModifier);
|
bool shift = e->modifiers().testFlag(Qt::ShiftModifier), alt = e->modifiers().testFlag(Qt::AltModifier);
|
||||||
bool macmeta = (cPlatform() == dbipMac || cPlatform() == dbipMacOld) && e->modifiers().testFlag(Qt::ControlModifier) && !e->modifiers().testFlag(Qt::MetaModifier) && !e->modifiers().testFlag(Qt::AltModifier);
|
bool macmeta = (cPlatform() == dbipMac || cPlatform() == dbipMacOld) && e->modifiers().testFlag(Qt::ControlModifier) && !e->modifiers().testFlag(Qt::MetaModifier) && !e->modifiers().testFlag(Qt::AltModifier);
|
||||||
bool ctrl = e->modifiers().testFlag(Qt::ControlModifier) || e->modifiers().testFlag(Qt::MetaModifier), ctrlGood = true;
|
bool ctrl = e->modifiers().testFlag(Qt::ControlModifier) || e->modifiers().testFlag(Qt::MetaModifier), ctrlGood = true;
|
||||||
|
@ -1979,36 +1980,41 @@ void InputField::InputFieldInner::keyPressEvent(QKeyEvent *e) {
|
||||||
}
|
}
|
||||||
#endif // Q_OS_MAC
|
#endif // Q_OS_MAC
|
||||||
} else {
|
} else {
|
||||||
QTextCursor tc(textCursor());
|
auto oldCursorPosition = textCursor().position();
|
||||||
if (enter && ctrl) {
|
if (enter && ctrl) {
|
||||||
e->setModifiers(e->modifiers() & ~Qt::ControlModifier);
|
e->setModifiers(e->modifiers() & ~Qt::ControlModifier);
|
||||||
}
|
}
|
||||||
QTextEdit::keyPressEvent(e);
|
QTextEdit::keyPressEvent(e);
|
||||||
if (tc == textCursor()) {
|
auto currentCursor = textCursor();
|
||||||
|
if (textCursor().position() == oldCursorPosition) {
|
||||||
bool check = false;
|
bool check = false;
|
||||||
if (e->key() == Qt::Key_PageUp || e->key() == Qt::Key_Up) {
|
if (e->key() == Qt::Key_PageUp || e->key() == Qt::Key_Up) {
|
||||||
tc.movePosition(QTextCursor::Start, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
|
oldCursorPosition = currentCursor.position();
|
||||||
|
currentCursor.movePosition(QTextCursor::Start, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
|
||||||
check = true;
|
check = true;
|
||||||
} else if (e->key() == Qt::Key_PageDown || e->key() == Qt::Key_Down) {
|
} else if (e->key() == Qt::Key_PageDown || e->key() == Qt::Key_Down) {
|
||||||
tc.movePosition(QTextCursor::End, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
|
oldCursorPosition = currentCursor.position();
|
||||||
|
currentCursor.movePosition(QTextCursor::End, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
|
||||||
check = true;
|
check = true;
|
||||||
|
} else if (e->key() == Qt::Key_Left || e->key() == Qt::Key_Right || e->key() == Qt::Key_Backspace) {
|
||||||
|
e->ignore();
|
||||||
}
|
}
|
||||||
if (check) {
|
if (check) {
|
||||||
if (tc == textCursor()) {
|
if (oldCursorPosition == currentCursor.position()) {
|
||||||
e->ignore();
|
e->ignore();
|
||||||
} else {
|
} else {
|
||||||
setTextCursor(tc);
|
setTextCursor(currentCursor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputField::InputFieldInner::paintEvent(QPaintEvent *e) {
|
void InputField::Inner::paintEvent(QPaintEvent *e) {
|
||||||
return QTextEdit::paintEvent(e);
|
return QTextEdit::paintEvent(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputField::InputFieldInner::contextMenuEvent(QContextMenuEvent *e) {
|
void InputField::Inner::contextMenuEvent(QContextMenuEvent *e) {
|
||||||
if (QMenu *menu = createStandardContextMenu()) {
|
if (QMenu *menu = createStandardContextMenu()) {
|
||||||
(new PopupMenu(menu))->popup(e->globalPos());
|
(new PopupMenu(menu))->popup(e->globalPos());
|
||||||
}
|
}
|
||||||
|
@ -2167,9 +2173,6 @@ void MaskedInputField::paintEvent(QPaintEvent *e) {
|
||||||
p.fillRect(0, height() - _st.borderActive, width(), _st.borderActive, a_borderFg.current());
|
p.fillRect(0, height() - _st.borderActive, width(), _st.borderActive, a_borderFg.current());
|
||||||
p.setOpacity(1);
|
p.setOpacity(1);
|
||||||
}
|
}
|
||||||
if (_st.iconSprite.pxWidth()) {
|
|
||||||
p.drawSpriteLeft(_st.iconPosition, width(), _st.iconSprite);
|
|
||||||
}
|
|
||||||
|
|
||||||
p.setClipRect(r);
|
p.setClipRect(r);
|
||||||
paintPlaceholder(p);
|
paintPlaceholder(p);
|
||||||
|
|
|
@ -262,10 +262,9 @@ private:
|
||||||
bool heightAutoupdated();
|
bool heightAutoupdated();
|
||||||
void checkContentHeight();
|
void checkContentHeight();
|
||||||
|
|
||||||
friend class InputAreaInner;
|
class Inner : public QTextEdit {
|
||||||
class InputAreaInner : public QTextEdit {
|
|
||||||
public:
|
public:
|
||||||
InputAreaInner(InputArea *parent);
|
Inner(InputArea *parent);
|
||||||
|
|
||||||
QVariant loadResource(int type, const QUrl &name) override;
|
QVariant loadResource(int type, const QUrl &name) override;
|
||||||
|
|
||||||
|
@ -286,6 +285,7 @@ private:
|
||||||
friend class InputArea;
|
friend class InputArea;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
friend class Inner;
|
||||||
|
|
||||||
void focusInInner();
|
void focusInInner();
|
||||||
void focusOutInner();
|
void focusOutInner();
|
||||||
|
@ -294,7 +294,7 @@ private:
|
||||||
|
|
||||||
void startBorderAnimation();
|
void startBorderAnimation();
|
||||||
|
|
||||||
InputAreaInner _inner;
|
Inner _inner;
|
||||||
|
|
||||||
QString _oldtext;
|
QString _oldtext;
|
||||||
|
|
||||||
|
@ -431,10 +431,9 @@ private:
|
||||||
int32 _maxLength;
|
int32 _maxLength;
|
||||||
bool _forcePlaceholderHidden = false;
|
bool _forcePlaceholderHidden = false;
|
||||||
|
|
||||||
friend class InputFieldInner;
|
class Inner : public QTextEdit {
|
||||||
class InputFieldInner : public QTextEdit {
|
|
||||||
public:
|
public:
|
||||||
InputFieldInner(InputField *parent);
|
Inner(InputField *parent);
|
||||||
|
|
||||||
QVariant loadResource(int type, const QUrl &name) override;
|
QVariant loadResource(int type, const QUrl &name) override;
|
||||||
|
|
||||||
|
@ -455,6 +454,7 @@ private:
|
||||||
friend class InputField;
|
friend class InputField;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
friend class Inner;
|
||||||
|
|
||||||
void focusInInner();
|
void focusInInner();
|
||||||
void focusOutInner();
|
void focusOutInner();
|
||||||
|
@ -463,7 +463,7 @@ private:
|
||||||
|
|
||||||
void startBorderAnimation();
|
void startBorderAnimation();
|
||||||
|
|
||||||
InputFieldInner _inner;
|
Inner _inner;
|
||||||
|
|
||||||
QString _oldtext;
|
QString _oldtext;
|
||||||
|
|
||||||
|
|
|
@ -105,6 +105,9 @@ public:
|
||||||
return std_::make_unique<Icon>(ColoredCopy { *this, colors });
|
return std_::make_unique<Icon>(ColoredCopy { *this, colors });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool empty() const {
|
||||||
|
return _parts.empty();
|
||||||
|
}
|
||||||
void paint(QPainter &p, const QPoint &pos, int outerw) const;
|
void paint(QPainter &p, const QPoint &pos, int outerw) const;
|
||||||
void paint(QPainter &p, int x, int y, int outerw) const {
|
void paint(QPainter &p, int x, int y, int outerw) const {
|
||||||
paint(p, QPoint(x, y), outerw);
|
paint(p, QPoint(x, y), outerw);
|
||||||
|
|
|
@ -26,91 +26,476 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||||
#include "lang.h"
|
#include "lang.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr int kWideScale = 3;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
class MultiSelect::Inner::Item {
|
||||||
|
public:
|
||||||
|
Item(const style::MultiSelectItem &st, uint64 id, const QString &text, const style::color &color, PaintRoundImage paintRoundImage);
|
||||||
|
|
||||||
|
uint64 id() const {
|
||||||
|
return _id;
|
||||||
|
}
|
||||||
|
int getWidth() const {
|
||||||
|
return _width;
|
||||||
|
}
|
||||||
|
QRect rect() const {
|
||||||
|
return QRect(_x, _y, _width, _st.height);
|
||||||
|
}
|
||||||
|
bool isOverDelete() const {
|
||||||
|
return _overDelete;
|
||||||
|
}
|
||||||
|
void setActive(bool active) {
|
||||||
|
_active = active;
|
||||||
|
}
|
||||||
|
void setPosition(int x, int y, int outerWidth, int maxVisiblePadding);
|
||||||
|
QRect paintArea(int outerWidth) const;
|
||||||
|
|
||||||
|
void setUpdateCallback(base::lambda_wrap<void()> updateCallback) {
|
||||||
|
_updateCallback = std_::move(updateCallback);
|
||||||
|
}
|
||||||
|
void setText(const QString &text);
|
||||||
|
void paint(Painter &p, int outerWidth, uint64 ms);
|
||||||
|
|
||||||
|
void mouseMoveEvent(QPoint point);
|
||||||
|
void leaveEvent();
|
||||||
|
|
||||||
|
void showAnimated() {
|
||||||
|
setVisibleAnimated(true);
|
||||||
|
}
|
||||||
|
void hideAnimated() {
|
||||||
|
setVisibleAnimated(false);
|
||||||
|
}
|
||||||
|
bool hideFinished() const {
|
||||||
|
return (_hiding && !_visibility.animating());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setOver(bool over);
|
||||||
|
void paintOnce(Painter &p, int x, int y, int outerWidth, uint64 ms);
|
||||||
|
void paintDeleteButton(Painter &p, int x, int y, int outerWidth, float64 overOpacity);
|
||||||
|
bool paintCached(Painter &p, int x, int y, int outerWidth);
|
||||||
|
void prepareCache();
|
||||||
|
void setVisibleAnimated(bool visible);
|
||||||
|
|
||||||
|
const style::MultiSelectItem &_st;
|
||||||
|
|
||||||
|
uint64 _id;
|
||||||
|
struct SlideAnimation {
|
||||||
|
SlideAnimation(base::lambda_wrap<void()> updateCallback, int fromX, int toX, int y, float64 duration)
|
||||||
|
: fromX(fromX)
|
||||||
|
, toX(toX)
|
||||||
|
, y(y) {
|
||||||
|
x.start(std_::move(updateCallback), fromX, toX, duration);
|
||||||
|
}
|
||||||
|
IntAnimation x;
|
||||||
|
int fromX, toX;
|
||||||
|
int y;
|
||||||
|
};
|
||||||
|
std_::vector_of_moveable<SlideAnimation> _copies;
|
||||||
|
int _x = -1;
|
||||||
|
int _y = -1;
|
||||||
|
int _width = 0;
|
||||||
|
Text _text;
|
||||||
|
const style::color &_color;
|
||||||
|
bool _over = false;
|
||||||
|
QPixmap _cache;
|
||||||
|
FloatAnimation _visibility;
|
||||||
|
FloatAnimation _overOpacity;
|
||||||
|
bool _overDelete = false;
|
||||||
|
bool _active = false;
|
||||||
|
PaintRoundImage _paintRoundImage;
|
||||||
|
base::lambda_wrap<void()> _updateCallback;
|
||||||
|
bool _hiding = false;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
MultiSelect::Inner::Item::Item(const style::MultiSelectItem &st, uint64 id, const QString &text, const style::color &color, PaintRoundImage paintRoundImage)
|
||||||
|
: _st(st)
|
||||||
|
, _id(id)
|
||||||
|
, _color(color)
|
||||||
|
, _paintRoundImage(std_::move(paintRoundImage)) {
|
||||||
|
setText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSelect::Inner::Item::setText(const QString &text) {
|
||||||
|
_text.setText(_st.font, text, _textNameOptions);
|
||||||
|
_width = _st.height + _st.padding.left() + _text.maxWidth() + _st.padding.right();
|
||||||
|
accumulate_min(_width, _st.maxWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSelect::Inner::Item::paint(Painter &p, int outerWidth, uint64 ms) {
|
||||||
|
if (!_cache.isNull() && !_visibility.animating(ms)) {
|
||||||
|
if (_hiding) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
_cache = QPixmap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_copies.empty()) {
|
||||||
|
paintOnce(p, _x, _y, outerWidth, ms);
|
||||||
|
} else {
|
||||||
|
for (auto i = _copies.begin(), e = _copies.end(); i != e;) {
|
||||||
|
auto x = i->x.current(getms(), _x);
|
||||||
|
auto y = i->y;
|
||||||
|
auto animating = i->x.animating();
|
||||||
|
if (animating || (y == _y)) {
|
||||||
|
paintOnce(p, x, y, outerWidth, ms);
|
||||||
|
}
|
||||||
|
if (animating) {
|
||||||
|
++i;
|
||||||
|
} else {
|
||||||
|
i = _copies.erase(i);
|
||||||
|
e = _copies.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSelect::Inner::Item::paintOnce(Painter &p, int x, int y, int outerWidth, uint64 ms) {
|
||||||
|
if (!_cache.isNull()) {
|
||||||
|
paintCached(p, x, y, outerWidth);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto radius = _st.height / 2;
|
||||||
|
auto inner = rtlrect(x + radius, y, _width - radius, _st.height, outerWidth);
|
||||||
|
|
||||||
|
auto clipEnabled = p.hasClipping();
|
||||||
|
auto clip = clipEnabled ? p.clipRegion() : QRegion();
|
||||||
|
p.setRenderHint(QPainter::HighQualityAntialiasing);
|
||||||
|
p.setClipRect(inner);
|
||||||
|
|
||||||
|
p.setPen(Qt::NoPen);
|
||||||
|
p.setBrush(_active ? _st.textActiveBg : _st.textBg);
|
||||||
|
p.drawRoundedRect(rtlrect(x, y, _width, _st.height, outerWidth), radius, radius);
|
||||||
|
|
||||||
|
if (clipEnabled) {
|
||||||
|
p.setClipRegion(clip);
|
||||||
|
} else {
|
||||||
|
p.setClipping(false);
|
||||||
|
}
|
||||||
|
p.setRenderHint(QPainter::HighQualityAntialiasing, false);
|
||||||
|
|
||||||
|
auto overOpacity = _overOpacity.current(ms, _over ? 1. : 0.);
|
||||||
|
if (overOpacity < 1.) {
|
||||||
|
_paintRoundImage(p, x, y, outerWidth, _st.height);
|
||||||
|
}
|
||||||
|
if (overOpacity > 0.) {
|
||||||
|
paintDeleteButton(p, x, y, outerWidth, overOpacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto textLeft = _st.height + _st.padding.left();
|
||||||
|
auto textWidth = _width - textLeft - _st.padding.right();
|
||||||
|
p.setPen(_active ? _st.textActiveFg : _st.textFg);
|
||||||
|
_text.drawLeftElided(p, x + textLeft, y + _st.padding.top(), textWidth, outerWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSelect::Inner::Item::paintDeleteButton(Painter &p, int x, int y, int outerWidth, float64 overOpacity) {
|
||||||
|
p.setOpacity(overOpacity);
|
||||||
|
p.setRenderHint(QPainter::HighQualityAntialiasing);
|
||||||
|
p.setPen(Qt::NoPen);
|
||||||
|
p.setBrush(_color);
|
||||||
|
p.drawEllipse(rtlrect(x, y, _st.height, _st.height, outerWidth));
|
||||||
|
|
||||||
|
auto deleteScale = overOpacity + _st.minScale * (1. - overOpacity);
|
||||||
|
auto deleteSkip = deleteScale * _st.deleteLeft + (1. - deleteScale) * (_st.height / 2);
|
||||||
|
auto sqrt2 = sqrt(2.);
|
||||||
|
auto deleteLeft = rtlpoint(x + deleteSkip, 0, outerWidth).x() + 0.;
|
||||||
|
auto deleteTop = y + deleteSkip + 0.;
|
||||||
|
auto deleteWidth = _st.height - 2 * deleteSkip;
|
||||||
|
auto deleteHeight = _st.height - 2 * deleteSkip;
|
||||||
|
auto deleteStroke = _st.deleteStroke / sqrt2;
|
||||||
|
QPointF pathDelete[] = {
|
||||||
|
{ deleteLeft, deleteTop + deleteStroke },
|
||||||
|
{ deleteLeft + deleteStroke, deleteTop },
|
||||||
|
{ deleteLeft + (deleteWidth / 2.), deleteTop + (deleteHeight / 2.) - deleteStroke },
|
||||||
|
{ deleteLeft + deleteWidth - deleteStroke, deleteTop },
|
||||||
|
{ deleteLeft + deleteWidth, deleteTop + deleteStroke },
|
||||||
|
{ deleteLeft + (deleteWidth / 2.) + deleteStroke, deleteTop + (deleteHeight / 2.) },
|
||||||
|
{ deleteLeft + deleteWidth, deleteTop + deleteHeight - deleteStroke },
|
||||||
|
{ deleteLeft + deleteWidth - deleteStroke, deleteTop + deleteHeight },
|
||||||
|
{ deleteLeft + (deleteWidth / 2.), deleteTop + (deleteHeight / 2.) + deleteStroke },
|
||||||
|
{ deleteLeft + deleteStroke, deleteTop + deleteHeight },
|
||||||
|
{ deleteLeft, deleteTop + deleteHeight - deleteStroke },
|
||||||
|
{ deleteLeft + (deleteWidth / 2.) - deleteStroke, deleteTop + (deleteHeight / 2.) },
|
||||||
|
};
|
||||||
|
if (overOpacity < 1.) {
|
||||||
|
auto alpha = -(overOpacity - 1.) * M_PI_2;
|
||||||
|
auto cosalpha = cos(alpha);
|
||||||
|
auto sinalpha = sin(alpha);
|
||||||
|
auto shiftx = deleteLeft + (deleteWidth / 2.);
|
||||||
|
auto shifty = deleteTop + (deleteHeight / 2.);
|
||||||
|
for (auto &point : pathDelete) {
|
||||||
|
auto x = point.x() - shiftx;
|
||||||
|
auto y = point.y() - shifty;
|
||||||
|
point.setX(shiftx + x * cosalpha - y * sinalpha);
|
||||||
|
point.setY(shifty + y * cosalpha + x * sinalpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QPainterPath path;
|
||||||
|
path.moveTo(pathDelete[0]);
|
||||||
|
for (int i = 1; i != base::array_size(pathDelete); ++i) {
|
||||||
|
path.lineTo(pathDelete[i]);
|
||||||
|
}
|
||||||
|
p.fillPath(path, _st.deleteFg);
|
||||||
|
|
||||||
|
p.setRenderHint(QPainter::HighQualityAntialiasing, false);
|
||||||
|
p.setOpacity(1.);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MultiSelect::Inner::Item::paintCached(Painter &p, int x, int y, int outerWidth) {
|
||||||
|
auto opacity = _visibility.current(_hiding ? 0. : 1.);
|
||||||
|
auto scale = opacity + _st.minScale * (1. - opacity);
|
||||||
|
auto height = opacity * _cache.height() / _cache.devicePixelRatio();
|
||||||
|
auto width = opacity * _cache.width() / _cache.devicePixelRatio();
|
||||||
|
|
||||||
|
p.setOpacity(opacity);
|
||||||
|
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
|
||||||
|
p.drawPixmap(rtlrect(x + (_width - width) / 2., y + (_st.height - height) / 2., width, height, outerWidth), _cache);
|
||||||
|
p.setRenderHint(QPainter::SmoothPixmapTransform, false);
|
||||||
|
p.setOpacity(1.);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSelect::Inner::Item::mouseMoveEvent(QPoint point) {
|
||||||
|
if (!_cache.isNull()) return;
|
||||||
|
_overDelete = QRect(0, 0, _st.height, _st.height).contains(point);
|
||||||
|
setOver(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSelect::Inner::Item::leaveEvent() {
|
||||||
|
_overDelete = false;
|
||||||
|
setOver(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSelect::Inner::Item::setPosition(int x, int y, int outerWidth, int maxVisiblePadding) {
|
||||||
|
if (_x >= 0 && _y >= 0 && (_x != x || _y != y)) {
|
||||||
|
// Make an animation if it is not the first setPosition().
|
||||||
|
auto found = false;
|
||||||
|
auto leftHidden = -_width - maxVisiblePadding;
|
||||||
|
auto rightHidden = outerWidth + maxVisiblePadding;
|
||||||
|
for (auto i = _copies.begin(), e = _copies.end(); i != e;) {
|
||||||
|
if (i->x.animating()) {
|
||||||
|
if (i->y == y) {
|
||||||
|
i->x.start(_updateCallback, i->toX, x, _st.duration);
|
||||||
|
found = true;
|
||||||
|
} else {
|
||||||
|
i->x.start(_updateCallback, i->fromX, (i->toX > i->fromX) ? rightHidden : leftHidden, _st.duration);
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
} else {
|
||||||
|
i = _copies.erase(i);
|
||||||
|
e = _copies.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_copies.empty()) {
|
||||||
|
if (_y == y) {
|
||||||
|
auto copy = SlideAnimation(_updateCallback, _x, x, _y, _st.duration);
|
||||||
|
_copies.push_back(std_::move(copy));
|
||||||
|
} else {
|
||||||
|
auto copyHiding = SlideAnimation(_updateCallback, _x, (y > _y) ? rightHidden : leftHidden, _y, _st.duration);
|
||||||
|
_copies.push_back(std_::move(copyHiding));
|
||||||
|
auto copyShowing = SlideAnimation(_updateCallback, (y > _y) ? leftHidden : rightHidden, x, y, _st.duration);
|
||||||
|
_copies.push_back(std_::move(copyShowing));
|
||||||
|
}
|
||||||
|
} else if (!found) {
|
||||||
|
auto copy = SlideAnimation(_updateCallback, (y > _y) ? leftHidden : rightHidden, x, y, _st.duration);
|
||||||
|
_copies.push_back(std_::move(copy));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_x = x;
|
||||||
|
_y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
QRect MultiSelect::Inner::Item::paintArea(int outerWidth) const {
|
||||||
|
if (_copies.empty()) {
|
||||||
|
return rect();
|
||||||
|
}
|
||||||
|
auto yMin = 0, yMax = 0;
|
||||||
|
for_const (auto ©, _copies) {
|
||||||
|
accumulate_max(yMax, copy.y);
|
||||||
|
if (yMin) {
|
||||||
|
accumulate_min(yMin, copy.y);
|
||||||
|
} else {
|
||||||
|
yMin = copy.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QRect(0, yMin, outerWidth, yMax - yMin + _st.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSelect::Inner::Item::prepareCache() {
|
||||||
|
if (!_cache.isNull()) return;
|
||||||
|
|
||||||
|
t_assert(!_visibility.animating());
|
||||||
|
auto cacheWidth = _width * kWideScale * cIntRetinaFactor();
|
||||||
|
auto cacheHeight = _st.height * kWideScale * cIntRetinaFactor();
|
||||||
|
auto data = QImage(cacheWidth, cacheHeight, QImage::Format_ARGB32_Premultiplied);
|
||||||
|
data.fill(Qt::transparent);
|
||||||
|
data.setDevicePixelRatio(cRetinaFactor());
|
||||||
|
{
|
||||||
|
Painter p(&data);
|
||||||
|
paintOnce(p, _width * (kWideScale - 1) / 2, _st.height * (kWideScale - 1) / 2, cacheWidth, getms());
|
||||||
|
}
|
||||||
|
_cache = App::pixmapFromImageInPlace(std_::move(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSelect::Inner::Item::setVisibleAnimated(bool visible) {
|
||||||
|
_hiding = !visible;
|
||||||
|
prepareCache();
|
||||||
|
auto from = visible ? 0. : 1.;
|
||||||
|
auto to = visible ? 1. : 0.;
|
||||||
|
auto transition = visible ? anim::bumpy<1125, 1000> : anim::linear;
|
||||||
|
_visibility.start(_updateCallback, from, to, _st.duration, transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSelect::Inner::Item::setOver(bool over) {
|
||||||
|
if (over != _over) {
|
||||||
|
_over = over;
|
||||||
|
_overOpacity.start(_updateCallback, _over ? 0. : 1., _over ? 1. : 0., _st.duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MultiSelect::MultiSelect(QWidget *parent, const style::MultiSelect &st, const QString &placeholder) : TWidget(parent)
|
MultiSelect::MultiSelect(QWidget *parent, const style::MultiSelect &st, const QString &placeholder) : TWidget(parent)
|
||||||
, _st(st)
|
, _st(st)
|
||||||
, _scroll(this, st::boxScroll)
|
, _scroll(this, _st.scroll)
|
||||||
, _inner(this, st, placeholder) {
|
, _inner(this, st, placeholder, [this](int activeTop, int activeBottom) { scrollTo(activeTop, activeBottom); }) {
|
||||||
_scroll->setOwnedWidget(_inner);
|
_scroll->setOwnedWidget(_inner);
|
||||||
|
_scroll->installEventFilter(this);
|
||||||
|
_inner->setResizedCallback([this](int innerHeightDelta) {
|
||||||
|
auto newHeight = resizeGetHeight(width());
|
||||||
|
if (innerHeightDelta > 0) {
|
||||||
|
_scroll->scrollToY(_scroll->scrollTop() + innerHeightDelta);
|
||||||
|
}
|
||||||
|
if (newHeight != height()) {
|
||||||
|
resize(width(), newHeight);
|
||||||
|
if (_resizedCallback) {
|
||||||
|
_resizedCallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_inner->setQueryChangedCallback([this](const QString &query) {
|
||||||
|
_scroll->scrollToY(_scroll->scrollTopMax());
|
||||||
|
if (_queryChangedCallback) {
|
||||||
|
_queryChangedCallback(query);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MultiSelect::eventFilter(QObject *o, QEvent *e) {
|
||||||
|
if (o == _scroll && e->type() == QEvent::KeyPress) {
|
||||||
|
e->ignore();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSelect::scrollTo(int activeTop, int activeBottom) {
|
||||||
|
auto scrollTop = _scroll->scrollTop();
|
||||||
|
auto scrollHeight = _scroll->height();
|
||||||
|
auto scrollBottom = scrollTop + scrollHeight;
|
||||||
|
if (scrollTop > activeTop) {
|
||||||
|
_scroll->scrollToY(activeTop);
|
||||||
|
} else if (scrollBottom < activeBottom) {
|
||||||
|
_scroll->scrollToY(activeBottom - scrollHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MultiSelect::setQueryChangedCallback(base::lambda_unique<void(const QString &query)> callback) {
|
void MultiSelect::setQueryChangedCallback(base::lambda_unique<void(const QString &query)> callback) {
|
||||||
_inner->setQueryChangedCallback(std_::move(callback));
|
_queryChangedCallback = std_::move(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiSelect::setSubmittedCallback(base::lambda_unique<void(bool ctrlShiftEnter)> callback) {
|
void MultiSelect::setSubmittedCallback(base::lambda_unique<void(bool ctrlShiftEnter)> callback) {
|
||||||
_inner->setSubmittedCallback(std_::move(callback));
|
_inner->setSubmittedCallback(std_::move(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MultiSelect::setResizedCallback(base::lambda_unique<void()> callback) {
|
||||||
|
_resizedCallback = std_::move(callback);
|
||||||
|
}
|
||||||
|
|
||||||
void MultiSelect::setInnerFocus() {
|
void MultiSelect::setInnerFocus() {
|
||||||
if (_inner->setInnerFocus()) {
|
if (_inner->setInnerFocus()) {
|
||||||
_scroll->scrollToY(_scroll->scrollTopMax());
|
_scroll->scrollToY(_scroll->scrollTopMax());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MultiSelect::clearQuery() {
|
||||||
|
_inner->clearQuery();
|
||||||
|
}
|
||||||
|
|
||||||
QString MultiSelect::getQuery() const {
|
QString MultiSelect::getQuery() const {
|
||||||
return _inner->getQuery();
|
return _inner->getQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiSelect::addItem(std_::unique_ptr<Item> item) {
|
void MultiSelect::addItem(uint64 itemId, const QString &text, const style::color &color, PaintRoundImage paintRoundImage) {
|
||||||
_inner->addItem(std_::move(item));
|
_inner->addItem(std_::make_unique<Inner::Item>(_st.item, itemId, text, color, std_::move(paintRoundImage)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSelect::setItemRemovedCallback(base::lambda_unique<void(uint64 itemId)> callback) {
|
||||||
|
_inner->setItemRemovedCallback(std_::move(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSelect::removeItem(uint64 itemId) {
|
||||||
|
_inner->removeItem(itemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
int MultiSelect::resizeGetHeight(int newWidth) {
|
int MultiSelect::resizeGetHeight(int newWidth) {
|
||||||
_inner->resizeToWidth(newWidth);
|
if (newWidth != _inner->width()) {
|
||||||
|
_inner->resizeToWidth(newWidth);
|
||||||
|
}
|
||||||
auto newHeight = qMin(_inner->height(), _st.maxHeight);
|
auto newHeight = qMin(_inner->height(), _st.maxHeight);
|
||||||
_scroll->resize(newWidth, newHeight);
|
_scroll->setGeometryToLeft(0, 0, newWidth, newHeight);
|
||||||
return newHeight;
|
return newHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiSelect::resizeEvent(QResizeEvent *e) {
|
MultiSelect::Inner::Inner(QWidget *parent, const style::MultiSelect &st, const QString &placeholder, ScrollCallback callback) : ScrolledWidget(parent)
|
||||||
_scroll->moveToLeft(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
MultiSelect::Item::Item(uint64 id, const QString &text, const style::color &color)
|
|
||||||
: _id(id) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void MultiSelect::Item::setText(const QString &text) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void MultiSelect::Item::paint(Painter &p, int x, int y) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MultiSelect::Inner::Inner(QWidget *parent, const style::MultiSelect &st, const QString &placeholder) : ScrolledWidget(parent)
|
|
||||||
, _st(st)
|
, _st(st)
|
||||||
, _filter(this, _st.field, placeholder)
|
, _scrollCallback(std_::move(callback))
|
||||||
, _cancel(this, _st.cancel) {
|
, _field(this, _st.field, placeholder)
|
||||||
connect(_filter, SIGNAL(changed()), this, SLOT(onQueryChanged()));
|
, _cancel(this, _st.fieldCancel) {
|
||||||
connect(_filter, SIGNAL(submitted(bool)), this, SLOT(onSubmitted(bool)));
|
_field->customUpDown(true);
|
||||||
|
connect(_field, SIGNAL(focused()), this, SLOT(onFieldFocused()));
|
||||||
|
connect(_field, SIGNAL(changed()), this, SLOT(onQueryChanged()));
|
||||||
|
connect(_field, SIGNAL(submitted(bool)), this, SLOT(onSubmitted(bool)));
|
||||||
_cancel->hide();
|
_cancel->hide();
|
||||||
_cancel->setClickedCallback([this] {
|
_cancel->setClickedCallback([this] {
|
||||||
_filter->setText(QString());
|
clearQuery();
|
||||||
_filter->setFocus();
|
_field->setFocus();
|
||||||
});
|
});
|
||||||
|
setMouseTracking(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiSelect::Inner::onQueryChanged() {
|
void MultiSelect::Inner::onQueryChanged() {
|
||||||
auto query = getQuery();
|
auto query = getQuery();
|
||||||
_cancel->setVisible(!query.isEmpty());
|
_cancel->setVisible(!query.isEmpty());
|
||||||
|
updateFieldGeometry();
|
||||||
if (_queryChangedCallback) {
|
if (_queryChangedCallback) {
|
||||||
_queryChangedCallback(query);
|
_queryChangedCallback(query);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString MultiSelect::Inner::getQuery() const {
|
||||||
|
return _field->getLastText().trimmed();
|
||||||
|
}
|
||||||
|
|
||||||
bool MultiSelect::Inner::setInnerFocus() {
|
bool MultiSelect::Inner::setInnerFocus() {
|
||||||
if (!_filter->hasFocus()) {
|
if (_active >= 0) {
|
||||||
_filter->setFocus();
|
setFocus();
|
||||||
|
} else if (!_field->hasFocus()) {
|
||||||
|
_field->setFocus();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MultiSelect::Inner::getQuery() const {
|
void MultiSelect::Inner::clearQuery() {
|
||||||
return _filter->getLastText().trimmed();
|
_field->setText(QString());
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiSelect::Inner::setQueryChangedCallback(base::lambda_unique<void(const QString &query)> callback) {
|
void MultiSelect::Inner::setQueryChangedCallback(base::lambda_unique<void(const QString &query)> callback) {
|
||||||
|
@ -121,41 +506,275 @@ void MultiSelect::Inner::setSubmittedCallback(base::lambda_unique<void(bool ctrl
|
||||||
_submittedCallback = std_::move(callback);
|
_submittedCallback = std_::move(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
int MultiSelect::Inner::resizeGetHeight(int newWidth) {
|
void MultiSelect::Inner::updateFieldGeometry() {
|
||||||
_filter->resizeToWidth(newWidth);
|
auto fieldFinalWidth = _fieldWidth;
|
||||||
return _filter->height();
|
if (!_cancel->isHidden()) {
|
||||||
|
fieldFinalWidth -= _st.fieldCancelSkip;
|
||||||
|
}
|
||||||
|
_field->resizeToWidth(fieldFinalWidth);
|
||||||
|
_field->moveToLeft(_st.padding.left() + _fieldLeft, _st.padding.top() + _fieldTop);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiSelect::Inner::resizeEvent(QResizeEvent *e) {
|
void MultiSelect::Inner::updateHasAnyItems(bool hasAnyItems) {
|
||||||
_filter->moveToLeft(0, 0);
|
_field->setPlaceholderHidden(hasAnyItems);
|
||||||
_cancel->moveToRight(0, 0);
|
updateCursor();
|
||||||
|
_iconOpacity.start([this] {
|
||||||
|
rtlupdate(_st.padding.left(), _st.padding.top(), _st.fieldIcon.width(), _st.fieldIcon.height());
|
||||||
|
}, hasAnyItems ? 1. : 0., hasAnyItems ? 0. : 1., _st.item.duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSelect::Inner::updateCursor() {
|
||||||
|
setCursor(_items.empty() ? style::cur_text : (_overDelete ? style::cur_pointer : style::cur_default));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSelect::Inner::setActiveItem(int active, ChangeActiveWay skipSetFocus) {
|
||||||
|
if (_active == active) return;
|
||||||
|
|
||||||
|
if (_active >= 0) {
|
||||||
|
t_assert(_active < _items.size());
|
||||||
|
_items[_active]->setActive(false);
|
||||||
|
}
|
||||||
|
_active = active;
|
||||||
|
if (_active >= 0) {
|
||||||
|
t_assert(_active < _items.size());
|
||||||
|
_items[_active]->setActive(true);
|
||||||
|
}
|
||||||
|
if (skipSetFocus != ChangeActiveWay::SkipSetFocus) {
|
||||||
|
setInnerFocus();
|
||||||
|
}
|
||||||
|
if (_scrollCallback) {
|
||||||
|
auto rect = (_active >= 0) ? _items[_active]->rect() : _field->geometry().translated(-_st.padding.left(), -_st.padding.top());
|
||||||
|
_scrollCallback(rect.y(), rect.y() + rect.height() + _st.padding.top() + _st.padding.bottom());
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSelect::Inner::setActiveItemPrevious() {
|
||||||
|
if (_active > 0) {
|
||||||
|
setActiveItem(_active - 1);
|
||||||
|
} else if (_active < 0 && !_items.empty()) {
|
||||||
|
setActiveItem(_items.size() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSelect::Inner::setActiveItemNext() {
|
||||||
|
if (_active >= 0 && _active + 1 < _items.size()) {
|
||||||
|
setActiveItem(_active + 1);
|
||||||
|
} else {
|
||||||
|
setActiveItem(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int MultiSelect::Inner::resizeGetHeight(int newWidth) {
|
||||||
|
computeItemsGeometry(newWidth);
|
||||||
|
updateFieldGeometry();
|
||||||
|
|
||||||
|
auto cancelLeft = _fieldLeft + _fieldWidth + _st.padding.right() - _cancel->width();
|
||||||
|
auto cancelTop = _fieldTop - _st.padding.top();
|
||||||
|
_cancel->moveToLeft(_st.padding.left() + cancelLeft, _st.padding.top() + cancelTop);
|
||||||
|
|
||||||
|
return _field->y() + _field->height() + _st.padding.bottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSelect::Inner::paintEvent(QPaintEvent *e) {
|
||||||
|
Painter p(this);
|
||||||
|
|
||||||
|
auto paintRect = e->rect();
|
||||||
|
p.fillRect(paintRect, st::windowBg);
|
||||||
|
|
||||||
|
auto offset = QPoint(rtl() ? _st.padding.right() : _st.padding.left(), _st.padding.top());
|
||||||
|
p.translate(offset);
|
||||||
|
paintRect.translate(-offset);
|
||||||
|
|
||||||
|
auto ms = getms();
|
||||||
|
auto outerWidth = width() - _st.padding.left() - _st.padding.right();
|
||||||
|
auto iconOpacity = _iconOpacity.current(ms, _items.empty() ? 1. : 0.);
|
||||||
|
if (iconOpacity > 0.) {
|
||||||
|
p.setOpacity(iconOpacity);
|
||||||
|
_st.fieldIcon.paint(p, 0, 0, outerWidth);
|
||||||
|
p.setOpacity(1.);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto checkRect = myrtlrect(paintRect);
|
||||||
|
auto paintMargins = itemPaintMargins();
|
||||||
|
for (auto i = _removingItems.begin(), e = _removingItems.end(); i != e;) {
|
||||||
|
auto item = *i;
|
||||||
|
auto itemRect = item->paintArea(outerWidth);
|
||||||
|
itemRect = itemRect.marginsAdded(paintMargins);
|
||||||
|
if (checkRect.intersects(itemRect)) {
|
||||||
|
item->paint(p, outerWidth, ms);
|
||||||
|
}
|
||||||
|
if (item->hideFinished()) {
|
||||||
|
i = _removingItems.erase(i);
|
||||||
|
e = _removingItems.end();
|
||||||
|
} else {
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for_const (auto item, _items) {
|
||||||
|
auto itemRect = item->paintArea(outerWidth);
|
||||||
|
itemRect = itemRect.marginsAdded(paintMargins);
|
||||||
|
if (checkRect.y() + checkRect.height() <= itemRect.y()) {
|
||||||
|
break;
|
||||||
|
} else if (checkRect.intersects(itemRect)) {
|
||||||
|
item->paint(p, outerWidth, ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QMargins MultiSelect::Inner::itemPaintMargins() const {
|
||||||
|
return {
|
||||||
|
qMax(_st.itemSkip, _st.padding.left()),
|
||||||
|
_st.itemSkip,
|
||||||
|
qMax(_st.itemSkip, _st.padding.right()),
|
||||||
|
_st.itemSkip,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSelect::Inner::leaveEvent(QEvent *e) {
|
||||||
|
clearSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSelect::Inner::mouseMoveEvent(QMouseEvent *e) {
|
||||||
|
updateSelection(e->pos());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSelect::Inner::keyPressEvent(QKeyEvent *e) {
|
||||||
|
if (_active >= 0) {
|
||||||
|
t_assert(_active < _items.size());
|
||||||
|
if (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace) {
|
||||||
|
auto itemId = _items[_active]->id();
|
||||||
|
setActiveItemNext();
|
||||||
|
removeItem(itemId);
|
||||||
|
} else if (e->key() == Qt::Key_Left) {
|
||||||
|
setActiveItemPrevious();
|
||||||
|
} else if (e->key() == Qt::Key_Right) {
|
||||||
|
setActiveItemNext();
|
||||||
|
} else if (e->key() == Qt::Key_Escape) {
|
||||||
|
setActiveItem(-1);
|
||||||
|
} else {
|
||||||
|
e->ignore();
|
||||||
|
}
|
||||||
|
} else if (e->key() == Qt::Key_Left || e->key() == Qt::Key_Backspace) {
|
||||||
|
setActiveItemPrevious();
|
||||||
|
} else {
|
||||||
|
e->ignore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSelect::Inner::onFieldFocused() {
|
||||||
|
setActiveItem(-1, ChangeActiveWay::SkipSetFocus);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSelect::Inner::updateSelection(QPoint mousePosition) {
|
||||||
|
auto point = myrtlpoint(mousePosition) - QPoint(_st.padding.left(), _st.padding.right());
|
||||||
|
auto selected = -1;
|
||||||
|
for (auto i = 0, size = _items.size(); i != size; ++i) {
|
||||||
|
auto itemRect = _items[i]->rect();
|
||||||
|
if (itemRect.y() > point.y()) {
|
||||||
|
break;
|
||||||
|
} else if (itemRect.contains(point)) {
|
||||||
|
point -= itemRect.topLeft();
|
||||||
|
selected = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_selected != selected) {
|
||||||
|
if (_selected >= 0) {
|
||||||
|
t_assert(_selected < _items.size());
|
||||||
|
_items[_selected]->leaveEvent();
|
||||||
|
}
|
||||||
|
_selected = selected;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
auto overDelete = false;
|
||||||
|
if (_selected >= 0) {
|
||||||
|
_items[_selected]->mouseMoveEvent(point);
|
||||||
|
overDelete = _items[_selected]->isOverDelete();
|
||||||
|
}
|
||||||
|
if (_overDelete != overDelete) {
|
||||||
|
_overDelete = overDelete;
|
||||||
|
updateCursor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSelect::Inner::mousePressEvent(QMouseEvent *e) {
|
||||||
|
if (_overDelete) {
|
||||||
|
t_assert(_selected >= 0);
|
||||||
|
t_assert(_selected < _items.size());
|
||||||
|
removeItem(_items[_selected]->id());
|
||||||
|
} else if (_selected >= 0) {
|
||||||
|
setActiveItem(_selected);
|
||||||
|
} else {
|
||||||
|
setInnerFocus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiSelect::Inner::addItem(std_::unique_ptr<Item> item) {
|
void MultiSelect::Inner::addItem(std_::unique_ptr<Item> item) {
|
||||||
|
auto wasEmpty = _items.empty();
|
||||||
|
item->setUpdateCallback([this, item = item.get()] {
|
||||||
|
auto itemRect = item->paintArea(width() - _st.padding.left() - _st.padding.top());
|
||||||
|
itemRect = itemRect.translated(_st.padding.left(), _st.padding.top());
|
||||||
|
itemRect = itemRect.marginsAdded(itemPaintMargins());
|
||||||
|
rtlupdate(itemRect);
|
||||||
|
});
|
||||||
_items.push_back(item.release());
|
_items.push_back(item.release());
|
||||||
refreshItemsGeometry(nullptr);
|
updateItemsGeometry();
|
||||||
|
if (wasEmpty) {
|
||||||
|
updateHasAnyItems(true);
|
||||||
|
}
|
||||||
|
_items.back()->showAnimated();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiSelect::Inner::refreshItemsGeometry(Item *startingFromRowWithItem) {
|
void MultiSelect::Inner::computeItemsGeometry(int newWidth) {
|
||||||
int startingFromRow = 0;
|
newWidth -= _st.padding.left() + _st.padding.right();
|
||||||
int startingFromIndex = 0;
|
|
||||||
for (int row = 1, rowsCount = qMin(_rows.size(), 1); row != rowsCount; ++row) {
|
auto itemLeft = 0;
|
||||||
if (startingFromRowWithItem) {
|
auto itemTop = 0;
|
||||||
if (_rows[row - 1].contains(startingFromRowWithItem)) {
|
auto widthLeft = newWidth;
|
||||||
break;
|
auto maxVisiblePadding = qMax(_st.padding.left(), _st.padding.right());
|
||||||
}
|
for_const (auto item, _items) {
|
||||||
|
auto itemWidth = item->getWidth();
|
||||||
|
t_assert(itemWidth <= newWidth);
|
||||||
|
if (itemWidth > widthLeft) {
|
||||||
|
itemLeft = 0;
|
||||||
|
itemTop += _st.item.height + _st.itemSkip;
|
||||||
|
widthLeft = newWidth;
|
||||||
}
|
}
|
||||||
startingFromIndex += _rows[row - 1].size();
|
item->setPosition(itemLeft, itemTop, newWidth, maxVisiblePadding);
|
||||||
++startingFromRow;
|
itemLeft += itemWidth + _st.itemSkip;
|
||||||
|
widthLeft -= itemWidth + _st.itemSkip;
|
||||||
}
|
}
|
||||||
while (_rows.size() > startingFromRow) {
|
|
||||||
_rows.pop_back();
|
auto fieldMinWidth = _st.fieldMinWidth + _st.fieldCancelSkip;
|
||||||
}
|
t_assert(fieldMinWidth <= newWidth);
|
||||||
for (int i = startingFromIndex, count = _items.size(); i != count; ++i) {
|
if (fieldMinWidth > widthLeft) {
|
||||||
Row row;
|
_fieldLeft = 0;
|
||||||
row.append(_items[i]);
|
_fieldTop = itemTop + _st.item.height + _st.itemSkip;
|
||||||
_rows.append(row);
|
} else {
|
||||||
|
_fieldLeft = itemLeft + (_items.empty() ? _st.fieldIconSkip : 0);
|
||||||
|
_fieldTop = itemTop;
|
||||||
}
|
}
|
||||||
|
_fieldWidth = newWidth - _fieldLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiSelect::Inner::updateItemsGeometry() {
|
||||||
|
computeItemsGeometry(width());
|
||||||
|
updateFieldGeometry();
|
||||||
|
auto newHeight = resizeGetHeight(width());
|
||||||
|
if (newHeight == _newHeight) return;
|
||||||
|
|
||||||
|
_newHeight = newHeight;
|
||||||
|
_height.start([this] {
|
||||||
|
auto newHeight = _height.current(_newHeight);
|
||||||
|
if (auto heightDelta = newHeight - height()) {
|
||||||
|
resize(width(), newHeight);
|
||||||
|
if (_resizedCallback) {
|
||||||
|
_resizedCallback(heightDelta);
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}, height(), _newHeight, _st.item.duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiSelect::Inner::setItemText(uint64 itemId, const QString &text) {
|
void MultiSelect::Inner::setItemText(uint64 itemId, const QString &text) {
|
||||||
|
@ -163,7 +782,7 @@ void MultiSelect::Inner::setItemText(uint64 itemId, const QString &text) {
|
||||||
auto item = _items[i];
|
auto item = _items[i];
|
||||||
if (item->id() == itemId) {
|
if (item->id() == itemId) {
|
||||||
item->setText(text);
|
item->setText(text);
|
||||||
refreshItemsGeometry(item);
|
updateItemsGeometry();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,26 +792,50 @@ void MultiSelect::Inner::setItemRemovedCallback(base::lambda_unique<void(uint64
|
||||||
_itemRemovedCallback = std_::move(callback);
|
_itemRemovedCallback = std_::move(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MultiSelect::Inner::setResizedCallback(base::lambda_unique<void(int heightDelta)> callback) {
|
||||||
|
_resizedCallback = std_::move(callback);
|
||||||
|
}
|
||||||
|
|
||||||
void MultiSelect::Inner::removeItem(uint64 itemId) {
|
void MultiSelect::Inner::removeItem(uint64 itemId) {
|
||||||
for (int i = 0, count = _items.size(); i != count; ++i) {
|
for (int i = 0, count = _items.size(); i != count; ++i) {
|
||||||
auto item = _items[i];
|
auto item = _items[i];
|
||||||
if (item->id() == itemId) {
|
if (item->id() == itemId) {
|
||||||
|
clearSelection();
|
||||||
_items.removeAt(i);
|
_items.removeAt(i);
|
||||||
refreshItemsGeometry(item);
|
if (_active == i) {
|
||||||
delete item;
|
_active = -1;
|
||||||
|
} else if (_active > i) {
|
||||||
|
--_active;
|
||||||
|
}
|
||||||
|
_removingItems.insert(item);
|
||||||
|
item->hideAnimated();
|
||||||
|
|
||||||
|
updateItemsGeometry();
|
||||||
|
if (_items.empty()) {
|
||||||
|
updateHasAnyItems(false);
|
||||||
|
}
|
||||||
|
auto point = QCursor::pos();
|
||||||
|
if (auto parent = parentWidget()) {
|
||||||
|
if (parent->rect().contains(parent->mapFromGlobal(point))) {
|
||||||
|
updateSelection(mapFromGlobal(point));
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_itemRemovedCallback) {
|
if (_itemRemovedCallback) {
|
||||||
_itemRemovedCallback(itemId);
|
_itemRemovedCallback(itemId);
|
||||||
}
|
}
|
||||||
|
setInnerFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiSelect::Inner::~Inner() {
|
MultiSelect::Inner::~Inner() {
|
||||||
base::take(_rows);
|
|
||||||
for (auto item : base::take(_items)) {
|
for (auto item : base::take(_items)) {
|
||||||
delete item;
|
delete item;
|
||||||
}
|
}
|
||||||
|
for (auto item : base::take(_removingItems)) {
|
||||||
|
delete item;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
@ -34,6 +34,49 @@ public:
|
||||||
|
|
||||||
QString getQuery() const;
|
QString getQuery() const;
|
||||||
void setInnerFocus();
|
void setInnerFocus();
|
||||||
|
void clearQuery();
|
||||||
|
|
||||||
|
void setQueryChangedCallback(base::lambda_unique<void(const QString &query)> callback);
|
||||||
|
void setSubmittedCallback(base::lambda_unique<void(bool ctrlShiftEnter)> callback);
|
||||||
|
void setResizedCallback(base::lambda_unique<void()> callback);
|
||||||
|
|
||||||
|
using PaintRoundImage = base::lambda_unique<void(Painter &p, int x, int y, int outerWidth, int size)>;
|
||||||
|
void addItem(uint64 itemId, const QString &text, const style::color &color, PaintRoundImage paintRoundImage);
|
||||||
|
void setItemText(uint64 itemId, const QString &text);
|
||||||
|
|
||||||
|
void setItemRemovedCallback(base::lambda_unique<void(uint64 itemId)> callback);
|
||||||
|
void removeItem(uint64 itemId);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int resizeGetHeight(int newWidth) override;
|
||||||
|
bool eventFilter(QObject *o, QEvent *e) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void scrollTo(int activeTop, int activeBottom);
|
||||||
|
|
||||||
|
const style::MultiSelect &_st;
|
||||||
|
|
||||||
|
ChildWidget<ScrollArea> _scroll;
|
||||||
|
|
||||||
|
class Inner;
|
||||||
|
ChildWidget<Inner> _inner;
|
||||||
|
|
||||||
|
base::lambda_unique<void()> _resizedCallback;
|
||||||
|
base::lambda_unique<void(const QString &query)> _queryChangedCallback;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// This class is hold in header because it requires Qt preprocessing.
|
||||||
|
class MultiSelect::Inner : public ScrolledWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
using ScrollCallback = base::lambda_unique<void(int activeTop, int activeBottom)>;
|
||||||
|
Inner(QWidget *parent, const style::MultiSelect &st, const QString &placeholder, ScrollCallback callback);
|
||||||
|
|
||||||
|
QString getQuery() const;
|
||||||
|
bool setInnerFocus();
|
||||||
|
void clearQuery();
|
||||||
|
|
||||||
void setQueryChangedCallback(base::lambda_unique<void(const QString &query)> callback);
|
void setQueryChangedCallback(base::lambda_unique<void(const QString &query)> callback);
|
||||||
void setSubmittedCallback(base::lambda_unique<void(bool ctrlShiftEnter)> callback);
|
void setSubmittedCallback(base::lambda_unique<void(bool ctrlShiftEnter)> callback);
|
||||||
|
@ -43,68 +86,20 @@ public:
|
||||||
void setItemText(uint64 itemId, const QString &text);
|
void setItemText(uint64 itemId, const QString &text);
|
||||||
|
|
||||||
void setItemRemovedCallback(base::lambda_unique<void(uint64 itemId)> callback);
|
void setItemRemovedCallback(base::lambda_unique<void(uint64 itemId)> callback);
|
||||||
void removeItem(uint64 itemId); // Always calls the itemRemovedCallback().
|
void removeItem(uint64 itemId);
|
||||||
|
|
||||||
protected:
|
void setResizedCallback(base::lambda_unique<void(int heightDelta)> callback);
|
||||||
int resizeGetHeight(int newWidth) override;
|
|
||||||
|
|
||||||
void resizeEvent(QResizeEvent *e) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
ChildWidget<ScrollArea> _scroll;
|
|
||||||
|
|
||||||
class Inner;
|
|
||||||
ChildWidget<Inner> _inner;
|
|
||||||
|
|
||||||
const style::MultiSelect &_st;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
class MultiSelect::Item {
|
|
||||||
public:
|
|
||||||
Item(uint64 id, const QString &text, const style::color &color);
|
|
||||||
|
|
||||||
uint64 id() const {
|
|
||||||
return _id;
|
|
||||||
}
|
|
||||||
void setText(const QString &text);
|
|
||||||
void paint(Painter &p, int x, int y);
|
|
||||||
|
|
||||||
virtual ~Item() = default;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void paintImage(Painter &p, int x, int y, int outerWidth, int size) = 0;
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint64 _id;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
// This class is hold in header because it requires Qt preprocessing.
|
|
||||||
class MultiSelect::Inner : public ScrolledWidget {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
Inner(QWidget *parent, const style::MultiSelect &st, const QString &placeholder);
|
|
||||||
|
|
||||||
QString getQuery() const;
|
|
||||||
bool setInnerFocus();
|
|
||||||
|
|
||||||
void setQueryChangedCallback(base::lambda_unique<void(const QString &query)> callback);
|
|
||||||
void setSubmittedCallback(base::lambda_unique<void(bool ctrlShiftEnter)> callback);
|
|
||||||
|
|
||||||
void addItem(std_::unique_ptr<Item> item);
|
|
||||||
void setItemText(uint64 itemId, const QString &text);
|
|
||||||
|
|
||||||
void setItemRemovedCallback(base::lambda_unique<void(uint64 itemId)> callback);
|
|
||||||
void removeItem(uint64 itemId); // Always calls the itemRemovedCallback().
|
|
||||||
|
|
||||||
~Inner();
|
~Inner();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int resizeGetHeight(int newWidth) override;
|
int resizeGetHeight(int newWidth) override;
|
||||||
|
|
||||||
void resizeEvent(QResizeEvent *e) override;
|
void paintEvent(QPaintEvent *e) override;
|
||||||
|
void leaveEvent(QEvent *e) override;
|
||||||
|
void mouseMoveEvent(QMouseEvent *e) override;
|
||||||
|
void mousePressEvent(QMouseEvent *e) override;
|
||||||
|
void keyPressEvent(QKeyEvent *e) override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onQueryChanged();
|
void onQueryChanged();
|
||||||
|
@ -113,25 +108,55 @@ private slots:
|
||||||
_submittedCallback(ctrlShiftEnter);
|
_submittedCallback(ctrlShiftEnter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
void onFieldFocused();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void refreshItemsGeometry(Item *startingFromItem);
|
void computeItemsGeometry(int newWidth);
|
||||||
|
void updateItemsGeometry();
|
||||||
|
void updateFieldGeometry();
|
||||||
|
void updateHasAnyItems(bool hasAnyItems);
|
||||||
|
void updateSelection(QPoint mousePosition);
|
||||||
|
void clearSelection() {
|
||||||
|
updateSelection(QPoint(-1, -1));
|
||||||
|
}
|
||||||
|
void updateCursor();
|
||||||
|
enum class ChangeActiveWay {
|
||||||
|
Default,
|
||||||
|
SkipSetFocus,
|
||||||
|
};
|
||||||
|
void setActiveItem(int active, ChangeActiveWay skipSetFocus = ChangeActiveWay::Default);
|
||||||
|
void setActiveItemPrevious();
|
||||||
|
void setActiveItemNext();
|
||||||
|
|
||||||
|
QMargins itemPaintMargins() const;
|
||||||
|
|
||||||
const style::MultiSelect &_st;
|
const style::MultiSelect &_st;
|
||||||
|
FloatAnimation _iconOpacity;
|
||||||
|
|
||||||
using Row = QList<Item*>;
|
ScrollCallback _scrollCallback;
|
||||||
using Rows = QList<Row>;
|
|
||||||
Rows _rows;
|
|
||||||
|
|
||||||
using Items = QList<Item*>;
|
using Items = QList<Item*>;
|
||||||
Items _items;
|
Items _items;
|
||||||
|
using RemovingItems = OrderedSet<Item*>;
|
||||||
|
RemovingItems _removingItems;
|
||||||
|
|
||||||
ChildWidget<InputField> _filter;
|
int _selected = -1;
|
||||||
|
int _active = -1;
|
||||||
|
bool _overDelete = false;
|
||||||
|
|
||||||
|
int _fieldLeft = 0;
|
||||||
|
int _fieldTop = 0;
|
||||||
|
int _fieldWidth = 0;
|
||||||
|
ChildWidget<InputField> _field;
|
||||||
ChildWidget<Ui::IconButton> _cancel;
|
ChildWidget<Ui::IconButton> _cancel;
|
||||||
|
|
||||||
|
int _newHeight = 0;
|
||||||
|
IntAnimation _height;
|
||||||
|
|
||||||
base::lambda_unique<void(const QString &query)> _queryChangedCallback;
|
base::lambda_unique<void(const QString &query)> _queryChangedCallback;
|
||||||
base::lambda_unique<void(bool ctrlShiftEnter)> _submittedCallback;
|
base::lambda_unique<void(bool ctrlShiftEnter)> _submittedCallback;
|
||||||
base::lambda_unique<void(uint64 itemId)> _itemRemovedCallback;
|
base::lambda_unique<void(uint64 itemId)> _itemRemovedCallback;
|
||||||
|
base::lambda_unique<void(int heightDelta)> _resizedCallback;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -67,10 +67,36 @@ RoundImageCheckbox {
|
||||||
checkIcon: icon;
|
checkIcon: icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MultiSelectItem {
|
||||||
|
padding: margins;
|
||||||
|
maxWidth: pixels;
|
||||||
|
height: pixels;
|
||||||
|
font: font;
|
||||||
|
textBg: color;
|
||||||
|
textFg: color;
|
||||||
|
textActiveBg: color;
|
||||||
|
textActiveFg: color;
|
||||||
|
deleteFg: color;
|
||||||
|
deleteLeft: pixels;
|
||||||
|
deleteStroke: pixels;
|
||||||
|
duration: int;
|
||||||
|
minScale: double;
|
||||||
|
}
|
||||||
|
|
||||||
MultiSelect {
|
MultiSelect {
|
||||||
field: InputField;
|
padding: margins;
|
||||||
cancel: IconButton;
|
|
||||||
maxHeight: pixels;
|
maxHeight: pixels;
|
||||||
|
scroll: flatScroll;
|
||||||
|
|
||||||
|
item: MultiSelectItem;
|
||||||
|
itemSkip: pixels;
|
||||||
|
|
||||||
|
field: InputField;
|
||||||
|
fieldIcon: icon;
|
||||||
|
fieldIconSkip: pixels;
|
||||||
|
fieldCancel: IconButton;
|
||||||
|
fieldCancelSkip: pixels;
|
||||||
|
fieldMinWidth: pixels;
|
||||||
}
|
}
|
||||||
|
|
||||||
widgetSlideDuration: 200;
|
widgetSlideDuration: 200;
|
||||||
|
|
Loading…
Reference in New Issue