| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100 |
- /*
- This file is part of Telegram Desktop,
- the official desktop application for the Telegram messaging service.
- For license and copyright information please follow this link:
- https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
- */
- #include "ui/widgets/multi_select.h"
- #include "ui/widgets/buttons.h"
- #include "ui/widgets/fields/input_field.h"
- #include "ui/widgets/scroll_area.h"
- #include "ui/effects/animations.h"
- #include "ui/effects/cross_animation.h"
- #include "ui/text/text_options.h"
- #include "ui/painter.h"
- #include "ui/ui_utility.h"
- #include "lang/lang_keys.h"
- #include <set>
- namespace Ui {
- namespace {
- constexpr int kWideScale = 3;
- class Item {
- public:
- Item(
- const style::MultiSelectItem &st,
- uint64 id,
- const QString &text,
- style::color color,
- MultiSelect::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(Fn<void()> updateCallback) {
- _updateCallback = updateCallback;
- }
- void setText(const QString &text);
- void paint(Painter &p, int outerWidth);
- 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);
- 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(
- Fn<void()> updateCallback,
- int fromX,
- int toX,
- int y,
- float64 duration)
- : fromX(fromX)
- , toX(toX)
- , y(y) {
- x.start(updateCallback, fromX, toX, duration);
- }
- Ui::Animations::Simple x;
- int fromX, toX;
- int y;
- };
- std::vector<SlideAnimation> _copies;
- int _x = -1;
- int _y = -1;
- int _width = 0;
- Text::String _text;
- style::color _color;
- bool _over = false;
- QPixmap _cache;
- Ui::Animations::Simple _visibility;
- Ui::Animations::Simple _overOpacity;
- bool _overDelete = false;
- bool _active = false;
- MultiSelect::PaintRoundImage _paintRoundImage;
- Fn<void()> _updateCallback;
- bool _hiding = false;
- };
- Item::Item(
- const style::MultiSelectItem &st,
- uint64 id,
- const QString &text,
- style::color color,
- MultiSelect::PaintRoundImage &&paintRoundImage)
- : _st(st)
- , _id(id)
- , _color(color)
- , _paintRoundImage(std::move(paintRoundImage)) {
- setText(text);
- }
- void Item::setText(const QString &text) {
- _text.setText(_st.style, text, NameTextOptions());
- _width = _st.height
- + _st.padding.left()
- + _text.maxWidth()
- + _st.padding.right();
- accumulate_min(_width, _st.maxWidth);
- }
- void Item::paint(Painter &p, int outerWidth) {
- if (!_cache.isNull() && !_visibility.animating()) {
- if (_hiding) {
- return;
- } else {
- _cache = QPixmap();
- }
- }
- if (_copies.empty()) {
- paintOnce(p, _x, _y, outerWidth);
- } else {
- for (auto i = _copies.begin(), e = _copies.end(); i != e;) {
- auto x = qRound(i->x.value(_x));
- auto y = i->y;
- auto animating = i->x.animating();
- if (animating || (y == _y)) {
- paintOnce(p, x, y, outerWidth);
- }
- if (animating) {
- ++i;
- } else {
- i = _copies.erase(i);
- e = _copies.end();
- }
- }
- }
- }
- void Item::paintOnce(Painter &p, int x, int y, int outerWidth) {
- if (!_cache.isNull()) {
- paintCached(p, x, y, outerWidth);
- return;
- }
- auto radius = _st.height / 2;
- auto inner = style::rtlrect(
- x + radius,
- y,
- _width - radius,
- _st.height,
- outerWidth);
- auto clipEnabled = p.hasClipping();
- auto clip = clipEnabled ? p.clipRegion() : QRegion();
- p.setClipRect(inner);
- p.setPen(Qt::NoPen);
- p.setBrush(_active ? _st.textActiveBg : _st.textBg);
- {
- PainterHighQualityEnabler hq(p);
- p.drawRoundedRect(
- style::rtlrect(x, y, _width, _st.height, outerWidth),
- radius,
- radius);
- }
- if (clipEnabled) {
- p.setClipRegion(clip);
- } else {
- p.setClipping(false);
- }
- auto overOpacity = _overOpacity.value(_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 Item::paintDeleteButton(
- Painter &p,
- int x,
- int y,
- int outerWidth,
- float64 overOpacity) {
- p.setOpacity(overOpacity);
- p.setPen(Qt::NoPen);
- p.setBrush(_color);
- {
- PainterHighQualityEnabler hq(p);
- p.drawEllipse(
- style::rtlrect(x, y, _st.height, _st.height, outerWidth));
- }
- CrossAnimation::paint(
- p,
- _st.deleteCross,
- _st.deleteFg,
- x,
- y,
- outerWidth,
- overOpacity);
- p.setOpacity(1.);
- }
- bool Item::paintCached(Painter &p, int x, int y, int outerWidth) {
- PainterHighQualityEnabler hq(p);
- auto opacity = _visibility.value(_hiding ? 0. : 1.);
- auto height = opacity * _cache.height() / _cache.devicePixelRatio();
- auto width = opacity * _cache.width() / _cache.devicePixelRatio();
- p.setOpacity(opacity);
- p.drawPixmap(
- style::rtlrect(
- x + (_width - width) / 2.,
- y + (_st.height - height) / 2.,
- width,
- height,
- outerWidth),
- _cache);
- p.setOpacity(1.);
- return true;
- }
- void Item::mouseMoveEvent(QPoint point) {
- if (!_cache.isNull()) {
- return;
- }
- _overDelete = QRect(0, 0, _st.height, _st.height).contains(point);
- setOver(true);
- }
- void Item::leaveEvent() {
- _overDelete = false;
- setOver(false);
- }
- void 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 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 Item::prepareCache() {
- if (!_cache.isNull()) return;
- Assert(!_visibility.animating());
- auto cacheWidth = _width * kWideScale * style::DevicePixelRatio();
- auto cacheHeight = _st.height * kWideScale * style::DevicePixelRatio();
- auto data = QImage(
- cacheWidth,
- cacheHeight,
- QImage::Format_ARGB32_Premultiplied);
- data.fill(Qt::transparent);
- data.setDevicePixelRatio(style::DevicePixelRatio());
- {
- Painter p(&data);
- paintOnce(
- p,
- _width * (kWideScale - 1) / 2,
- _st.height * (kWideScale - 1) / 2,
- cacheWidth);
- }
- _cache = Ui::PixmapFromImage(std::move(data));
- }
- void Item::setVisibleAnimated(bool visible) {
- _hiding = !visible;
- prepareCache();
- auto from = visible ? 0. : 1.;
- auto to = visible ? 1. : 0.;
- auto transition = visible ? anim::bumpy(1.0625) : anim::linear;
- _visibility.start(_updateCallback, from, to, _st.duration, transition);
- }
- void Item::setOver(bool over) {
- if (over != _over) {
- _over = over;
- _overOpacity.start(
- _updateCallback,
- _over ? 0. : 1.,
- _over ? 1. : 0.,
- _st.duration);
- }
- }
- } // namespace
- class MultiSelect::Inner : public TWidget {
- public:
- using ScrollCallback = Fn<void(int activeTop, int activeBottom)>;
- Inner(
- QWidget *parent,
- const style::MultiSelect &st,
- rpl::producer<QString> placeholder,
- const QString &query,
- ScrollCallback callback);
- [[nodiscard]] QString getQuery() const;
- void setQuery(const QString &query);
- bool setInnerFocus();
- void clearQuery();
- void setQueryChangedCallback(Fn<void(const QString &query)> callback);
- void setSubmittedCallback(Fn<void(Qt::KeyboardModifiers)> callback);
- void setCancelledCallback(Fn<void()> callback);
- void addItemInBunch(std::unique_ptr<Item> item);
- void finishItemsBunch(AddItemWay way);
- void setItemText(uint64 itemId, const QString &text);
- void setItemRemovedCallback(Fn<void(uint64 itemId)> callback);
- void removeItem(uint64 itemId);
- int getItemsCount() const;
- QVector<uint64> getItems() const;
- bool hasItem(uint64 itemId) const;
- void setResizedCallback(Fn<void(int heightDelta)> callback);
- ~Inner();
- protected:
- int resizeGetHeight(int newWidth) override;
- void paintEvent(QPaintEvent *e) override;
- void leaveEventHook(QEvent *e) override;
- void mouseMoveEvent(QMouseEvent *e) override;
- void mousePressEvent(QMouseEvent *e) override;
- void keyPressEvent(QKeyEvent *e) override;
- private:
- void cancelled();
- void queryChanged();
- void fieldFocused();
- void computeItemsGeometry(int newWidth);
- void updateItemsGeometry();
- void updateFieldGeometry();
- void updateHasAnyItems(bool hasAnyItems);
- void updateSelection(QPoint mousePosition);
- void clearSelection() {
- updateSelection(QPoint(-1, -1));
- }
- void updateCursor();
- void updateHeightStep();
- void finishHeightAnimation();
- enum class ChangeActiveWay {
- Default,
- SkipSetFocus,
- };
- void setActiveItem(
- int active,
- ChangeActiveWay skipSetFocus = ChangeActiveWay::Default);
- void setActiveItemPrevious();
- void setActiveItemNext();
- QMargins itemPaintMargins() const;
- const style::MultiSelect &_st;
- Ui::Animations::Simple _iconOpacity;
- ScrollCallback _scrollCallback;
- std::set<uint64> _idsMap;
- std::vector<std::unique_ptr<Item>> _items;
- std::set<std::unique_ptr<Item>> _removingItems;
- int _selected = -1;
- int _active = -1;
- bool _overDelete = false;
- int _fieldLeft = 0;
- int _fieldTop = 0;
- int _fieldWidth = 0;
- object_ptr<Ui::InputField> _field;
- object_ptr<Ui::CrossButton> _cancel;
- int _newHeight = 0;
- Ui::Animations::Simple _height;
- Fn<void(const QString &query)> _queryChangedCallback;
- Fn<void(Qt::KeyboardModifiers)> _submittedCallback;
- Fn<void()> _cancelledCallback;
- Fn<void(uint64 itemId)> _itemRemovedCallback;
- Fn<void(int heightDelta)> _resizedCallback;
- };
- MultiSelect::MultiSelect(
- QWidget *parent,
- const style::MultiSelect &st,
- rpl::producer<QString> placeholder,
- const QString &query)
- : RpWidget(parent)
- , _st(st)
- , _scroll(this, _st.scroll) {
- const auto scrollCallback = [=](int activeTop, int activeBottom) {
- scrollTo(activeTop, activeBottom);
- };
- _inner = _scroll->setOwnedWidget(object_ptr<Inner>(
- this,
- st,
- std::move(placeholder),
- query,
- scrollCallback));
- _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);
- auto defaultWidth = _st.item.maxWidth + _st.fieldMinWidth + _st.fieldCancelSkip;
- resizeToWidth(_st.padding.left() + defaultWidth + _st.padding.right());
- }
- 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(Fn<void(const QString &query)> callback) {
- _queryChangedCallback = std::move(callback);
- }
- void MultiSelect::setSubmittedCallback(Fn<void(Qt::KeyboardModifiers)> callback) {
- _inner->setSubmittedCallback(std::move(callback));
- }
- void MultiSelect::setCancelledCallback(Fn<void()> callback) {
- _inner->setCancelledCallback(std::move(callback));
- }
- void MultiSelect::setResizedCallback(Fn<void()> callback) {
- _resizedCallback = std::move(callback);
- }
- void MultiSelect::setInnerFocus() {
- if (_inner->setInnerFocus()) {
- _scroll->scrollToY(_scroll->scrollTopMax());
- }
- }
- void MultiSelect::clearQuery() {
- _inner->clearQuery();
- }
- QString MultiSelect::getQuery() const {
- return _inner->getQuery();
- }
- void MultiSelect::setQuery(const QString &query) {
- _inner->setQuery(query);
- }
- void MultiSelect::addItem(uint64 itemId, const QString &text, style::color color, PaintRoundImage paintRoundImage, AddItemWay way) {
- addItemInBunch(itemId, text, color, std::move(paintRoundImage));
- _inner->finishItemsBunch(way);
- }
- void MultiSelect::addItemInBunch(uint64 itemId, const QString &text, style::color color, PaintRoundImage paintRoundImage) {
- _inner->addItemInBunch(std::make_unique<Item>(_st.item, itemId, text, color, std::move(paintRoundImage)));
- }
- void MultiSelect::finishItemsBunch() {
- _inner->finishItemsBunch(AddItemWay::SkipAnimation);
- }
- void MultiSelect::setItemRemovedCallback(Fn<void(uint64 itemId)> callback) {
- _inner->setItemRemovedCallback(std::move(callback));
- }
- void MultiSelect::removeItem(uint64 itemId) {
- _inner->removeItem(itemId);
- }
- int MultiSelect::getItemsCount() const {
- return _inner->getItemsCount();
- }
- QVector<uint64> MultiSelect::getItems() const {
- return _inner->getItems();
- }
- bool MultiSelect::hasItem(uint64 itemId) const {
- return _inner->hasItem(itemId);
- }
- int MultiSelect::resizeGetHeight(int newWidth) {
- if (newWidth != _inner->width()) {
- _inner->resizeToWidth(newWidth);
- }
- auto newHeight = qMin(_inner->height(), _st.maxHeight);
- _scroll->setGeometryToLeft(0, 0, newWidth, newHeight);
- return newHeight;
- }
- MultiSelect::Inner::Inner(
- QWidget *parent,
- const style::MultiSelect &st,
- rpl::producer<QString> placeholder,
- const QString &query,
- ScrollCallback callback)
- : TWidget(parent)
- , _st(st)
- , _scrollCallback(std::move(callback))
- , _field(this, _st.field, std::move(placeholder), query)
- , _cancel(this, _st.fieldCancel) {
- _field->customUpDown(true);
- _field->focusedChanges(
- ) | rpl::filter(rpl::mappers::_1) | rpl::start_with_next([=] {
- fieldFocused();
- }, _field->lifetime());
- _field->changes(
- ) | rpl::start_with_next([=] {
- queryChanged();
- }, _field->lifetime());
- _field->submits(
- ) | rpl::start_with_next([=](Qt::KeyboardModifiers m) {
- if (_submittedCallback) {
- _submittedCallback(m);
- }
- }, _field->lifetime());
- _field->cancelled(
- ) | rpl::start_with_next([=] {
- cancelled();
- }, _field->lifetime());
- _cancel->setClickedCallback([=] {
- clearQuery();
- _field->setFocus();
- });
- setMouseTracking(true);
- }
- void MultiSelect::Inner::queryChanged() {
- auto query = getQuery();
- _cancel->toggle(!query.isEmpty(), anim::type::normal);
- updateFieldGeometry();
- if (_queryChangedCallback) {
- _queryChangedCallback(query);
- }
- }
- QString MultiSelect::Inner::getQuery() const {
- return _field->getLastText().trimmed();
- }
- void MultiSelect::Inner::setQuery(const QString &query) {
- _field->setText(query);
- if (const auto last = _field->getLastText(); !last.isEmpty()) {
- _field->setCursorPosition(last.size());
- }
- }
- bool MultiSelect::Inner::setInnerFocus() {
- if (_active >= 0) {
- setFocus();
- } else if (!_field->hasFocus()) {
- _field->setFocusFast();
- return true;
- }
- return false;
- }
- void MultiSelect::Inner::clearQuery() {
- _field->setText(QString());
- }
- void MultiSelect::Inner::setQueryChangedCallback(Fn<void(const QString &query)> callback) {
- _queryChangedCallback = std::move(callback);
- }
- void MultiSelect::Inner::setSubmittedCallback(
- Fn<void(Qt::KeyboardModifiers)> callback) {
- _submittedCallback = std::move(callback);
- }
- void MultiSelect::Inner::setCancelledCallback(Fn<void()> callback) {
- _cancelledCallback = std::move(callback);
- }
- void MultiSelect::Inner::updateFieldGeometry() {
- auto fieldFinalWidth = _fieldWidth;
- if (_cancel->toggled()) {
- fieldFinalWidth -= _st.fieldCancelSkip;
- }
- _field->resizeToWidth(fieldFinalWidth);
- _field->moveToLeft(_st.padding.left() + _fieldLeft, _st.padding.top() + _fieldTop);
- }
- void MultiSelect::Inner::updateHasAnyItems(bool hasAnyItems) {
- _field->setPlaceholderHidden(hasAnyItems);
- updateCursor();
- _iconOpacity.start([this] {
- rtlupdate(_st.padding.left(), _st.padding.top(), _st.fieldIcon.width(), _st.fieldIcon.height());
- }, hasAnyItems ? 1. : 0., hasAnyItems ? 0. : 1., _st.item.duration);
- }
- void MultiSelect::Inner::updateCursor() {
- setCursor(_items.empty() ? style::cur_text : (_overDelete ? style::cur_pointer : style::cur_default));
- }
- void MultiSelect::Inner::setActiveItem(int active, ChangeActiveWay skipSetFocus) {
- if (_active == active) return;
- if (_active >= 0) {
- Assert(_active < _items.size());
- _items[_active]->setActive(false);
- }
- _active = active;
- if (_active >= 0) {
- 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.bg);
- auto offset = QPoint(
- style::RightToLeft() ? _st.padding.right() : _st.padding.left(),
- _st.padding.top());
- p.translate(offset);
- paintRect.translate(-offset);
- auto outerWidth = width() - _st.padding.left() - _st.padding.right();
- auto iconOpacity = _iconOpacity.value(_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);
- }
- 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);
- }
- }
- }
- 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::leaveEventHook(QEvent *e) {
- clearSelection();
- }
- void MultiSelect::Inner::mouseMoveEvent(QMouseEvent *e) {
- updateSelection(e->pos());
- }
- void MultiSelect::Inner::keyPressEvent(QKeyEvent *e) {
- if (_active >= 0) {
- Expects(_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::cancelled() {
- if (_cancelledCallback) {
- _cancelledCallback();
- }
- }
- void MultiSelect::Inner::fieldFocused() {
- 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, count = int(_items.size()); i != count; ++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) {
- 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) {
- Expects(_selected >= 0);
- Expects(_selected < _items.size());
- removeItem(_items[_selected]->id());
- } else if (_selected >= 0) {
- setActiveItem(_selected);
- } else {
- setInnerFocus();
- }
- }
- void MultiSelect::Inner::addItemInBunch(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);
- });
- _idsMap.insert(item->id());
- _items.push_back(std::move(item));
- if (wasEmpty) {
- updateHasAnyItems(true);
- }
- }
- void MultiSelect::Inner::finishItemsBunch(AddItemWay way) {
- updateItemsGeometry();
- if (way != AddItemWay::SkipAnimation) {
- _items.back()->showAnimated();
- } else {
- _field->finishAnimating();
- finishHeightAnimation();
- }
- }
- void MultiSelect::Inner::computeItemsGeometry(int newWidth) {
- newWidth -= _st.padding.left() + _st.padding.right();
- auto itemLeft = 0;
- auto itemTop = 0;
- auto widthLeft = newWidth;
- auto maxVisiblePadding = qMax(_st.padding.left(), _st.padding.right());
- for (const auto &item : _items) {
- auto itemWidth = item->getWidth();
- Assert(itemWidth <= newWidth);
- if (itemWidth > widthLeft) {
- itemLeft = 0;
- itemTop += _st.item.height + _st.itemSkip;
- widthLeft = newWidth;
- }
- item->setPosition(itemLeft, itemTop, newWidth, maxVisiblePadding);
- itemLeft += itemWidth + _st.itemSkip;
- widthLeft -= itemWidth + _st.itemSkip;
- }
- auto fieldMinWidth = _st.fieldMinWidth + _st.fieldCancelSkip;
- Assert(fieldMinWidth <= newWidth);
- if (fieldMinWidth > widthLeft) {
- _fieldLeft = 0;
- _fieldTop = itemTop + _st.item.height + _st.itemSkip;
- } else {
- _fieldLeft = itemLeft + (_items.empty() ? _st.fieldIconSkip : 0);
- _fieldTop = itemTop;
- }
- _fieldWidth = newWidth - _fieldLeft;
- }
- void MultiSelect::Inner::updateItemsGeometry() {
- auto newHeight = resizeGetHeight(width());
- if (newHeight == _newHeight) return;
- _newHeight = newHeight;
- _height.start([this] { updateHeightStep(); }, height(), _newHeight, _st.item.duration);
- }
- void MultiSelect::Inner::updateHeightStep() {
- auto newHeight = qRound(_height.value(_newHeight));
- if (auto heightDelta = newHeight - height()) {
- resize(width(), newHeight);
- if (_resizedCallback) {
- _resizedCallback(heightDelta);
- }
- update();
- }
- }
- void MultiSelect::Inner::finishHeightAnimation() {
- _height.stop();
- updateHeightStep();
- }
- void MultiSelect::Inner::setItemText(uint64 itemId, const QString &text) {
- for (const auto &item : _items) {
- if (item->id() == itemId) {
- item->setText(text);
- updateItemsGeometry();
- return;
- }
- }
- }
- void MultiSelect::Inner::setItemRemovedCallback(Fn<void(uint64 itemId)> callback) {
- _itemRemovedCallback = std::move(callback);
- }
- void MultiSelect::Inner::setResizedCallback(Fn<void(int heightDelta)> callback) {
- _resizedCallback = std::move(callback);
- }
- void MultiSelect::Inner::removeItem(uint64 itemId) {
- auto found = false;
- for (auto i = 0, count = int(_items.size()); i != count; ++i) {
- auto &item = _items[i];
- if (item->id() == itemId) {
- found = true;
- clearSelection();
- item->hideAnimated();
- _idsMap.erase(item->id());
- _removingItems.insert(std::move(item));
- _items.erase(_items.begin() + i);
- if (_active == i) {
- _active = -1;
- } else if (_active > i) {
- --_active;
- }
- updateItemsGeometry();
- if (_items.empty()) {
- updateHasAnyItems(false);
- }
- auto point = QCursor::pos();
- if (auto parent = parentWidget()) {
- if (parent->rect().contains(parent->mapFromGlobal(point))) {
- updateSelection(mapFromGlobal(point));
- }
- }
- break;
- }
- }
- if (found && _itemRemovedCallback) {
- _itemRemovedCallback(itemId);
- }
- setInnerFocus();
- }
- int MultiSelect::Inner::getItemsCount() const {
- return _items.size();
- }
- QVector<uint64> MultiSelect::Inner::getItems() const {
- auto result = QVector<uint64>();
- result.reserve(_items.size());
- for (const auto &item : _items) {
- result.push_back(item->id());
- }
- return result;
- }
- bool MultiSelect::Inner::hasItem(uint64 itemId) const {
- return _idsMap.find(itemId) != _idsMap.cend();
- }
- MultiSelect::Inner::~Inner() = default;
- } // namespace Ui
|