mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-08-06 03:42:56 +00:00
291 lines
7.4 KiB
C++
291 lines
7.4 KiB
C++
/*
|
|
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 "boxes/peer_list_section_index.h"
|
|
|
|
#include "ui/painter.h"
|
|
#include "styles/style_boxes.h"
|
|
#include "styles/style_layers.h"
|
|
|
|
namespace {
|
|
|
|
constexpr auto kMaxBoost = 1.8;
|
|
constexpr auto kEase = 0.6;
|
|
constexpr auto kFisheyeReachSlots = 2.2;
|
|
constexpr auto kBulgeReserveSlots = 2.5;
|
|
|
|
} // namespace
|
|
|
|
PeerListSectionIndex::PeerListSectionIndex(QWidget *parent)
|
|
: RpWidget(parent) {
|
|
setMouseTracking(true);
|
|
_fisheye.init([=](crl::time) {
|
|
const auto moving = fisheyeFrame();
|
|
update();
|
|
return moving;
|
|
});
|
|
}
|
|
|
|
void PeerListSectionIndex::setLetters(std::vector<Entry> letters) {
|
|
_letters = std::move(letters);
|
|
relayout();
|
|
update();
|
|
}
|
|
|
|
void PeerListSectionIndex::setVisibleLetters(base::flat_set<QString> letters) {
|
|
if (_visible != letters) {
|
|
_visible = std::move(letters);
|
|
update();
|
|
}
|
|
}
|
|
|
|
void PeerListSectionIndex::setJumpCallback(
|
|
Fn<void(int, anim::type)> callback) {
|
|
_jump = std::move(callback);
|
|
}
|
|
|
|
void PeerListSectionIndex::setScrollCallback(
|
|
Fn<void(not_null<QWheelEvent*>)> callback) {
|
|
_scroll = std::move(callback);
|
|
}
|
|
|
|
int PeerListSectionIndex::idealWidth() const {
|
|
return st::peerListIndexWidth + st::peerListIndexApproach;
|
|
}
|
|
|
|
float64 PeerListSectionIndex::columnCenterX() const {
|
|
return style::RightToLeft()
|
|
? (st::peerListIndexWidth / 2.)
|
|
: (width() - st::peerListIndexWidth / 2.);
|
|
}
|
|
|
|
void PeerListSectionIndex::resizeEvent(QResizeEvent *e) {
|
|
relayout();
|
|
}
|
|
|
|
void PeerListSectionIndex::relayout() {
|
|
_slots.clear();
|
|
_current = -1;
|
|
const auto count = int(_letters.size());
|
|
if (!count || height() <= 0) {
|
|
_scale.clear();
|
|
return;
|
|
}
|
|
const auto itemHeight = st::peerListIndexSlotHeight;
|
|
const auto minHeight = st::peerListIndexMinSlotHeight;
|
|
const auto inset = st::peerListIndexVerticalInset;
|
|
const auto fullHeight = std::max(height() - 2 * inset, minHeight);
|
|
const auto reserve = int(kBulgeReserveSlots * itemHeight);
|
|
const auto usable = std::max(fullHeight - reserve, minHeight);
|
|
const auto maxFit = std::max(usable / minHeight, 1);
|
|
const auto skip = (count <= maxFit)
|
|
? 1
|
|
: ((count + maxFit - 1) / maxFit);
|
|
auto kept = std::vector<int>();
|
|
for (auto i = 0; i < count; i += skip) {
|
|
kept.push_back(i);
|
|
}
|
|
if (kept.back() != count - 1) {
|
|
kept.push_back(count - 1);
|
|
}
|
|
const auto actualCount = int(kept.size());
|
|
const auto pitch = std::min(itemHeight, usable / actualCount);
|
|
_pitch = pitch;
|
|
const auto totalHeight = actualCount * pitch;
|
|
const auto blockTop = inset + std::max(0, (fullHeight - totalHeight) / 2);
|
|
_slots.reserve(actualCount);
|
|
for (auto d = 0; d != actualCount; ++d) {
|
|
_slots.push_back({
|
|
_letters[kept[d]].letter,
|
|
kept[d],
|
|
blockTop + pitch * d + (pitch / 2),
|
|
});
|
|
}
|
|
_scale.assign(actualCount, 1.);
|
|
}
|
|
|
|
float64 PeerListSectionIndex::slotScale(int slotY) const {
|
|
if (_cursorY < 0 || !_pitch) {
|
|
return 1.;
|
|
}
|
|
const auto reach = kFisheyeReachSlots * _pitch;
|
|
const auto vertical = std::clamp(
|
|
std::abs(slotY - _cursorY) / reach,
|
|
0.,
|
|
1.);
|
|
const auto verticalFalloff = std::cos(M_PI_2 * vertical);
|
|
|
|
const auto approach = float64(st::peerListIndexApproach);
|
|
const auto edge = style::RightToLeft()
|
|
? float64(st::peerListIndexWidth)
|
|
: (width() - st::peerListIndexWidth);
|
|
const auto away = style::RightToLeft()
|
|
? std::clamp((_cursorX - edge) / approach, 0., 1.)
|
|
: std::clamp((edge - _cursorX) / approach, 0., 1.);
|
|
const auto horizontalFalloff = 1. - away * away * (3. - 2. * away);
|
|
|
|
return 1. + (kMaxBoost - 1.) * verticalFalloff * horizontalFalloff;
|
|
}
|
|
|
|
bool PeerListSectionIndex::updateCursorFromGlobal() {
|
|
const auto position = mapFromGlobal(QCursor::pos());
|
|
const auto scrollBar = st::boxScroll.width;
|
|
const auto rtl = style::RightToLeft();
|
|
const auto left = rtl ? -scrollBar : 0;
|
|
const auto right = rtl ? width() : (width() + scrollBar);
|
|
if (position.x() >= left
|
|
&& position.x() <= right
|
|
&& position.y() >= 0
|
|
&& position.y() <= height()) {
|
|
_cursorX = position.x();
|
|
_cursorY = position.y();
|
|
return true;
|
|
}
|
|
_cursorX = -1;
|
|
_cursorY = -1;
|
|
return false;
|
|
}
|
|
|
|
bool PeerListSectionIndex::fisheyeFrame() {
|
|
const auto wasX = _cursorX;
|
|
const auto wasY = _cursorY;
|
|
const auto influenced = updateCursorFromGlobal();
|
|
auto moving = false;
|
|
for (auto i = 0; i != int(_slots.size()); ++i) {
|
|
const auto target = slotScale(_slots[i].y);
|
|
const auto delta = target - _scale[i];
|
|
if (std::abs(delta) > 0.002) {
|
|
_scale[i] += delta * kEase;
|
|
moving = true;
|
|
} else {
|
|
_scale[i] = target;
|
|
}
|
|
}
|
|
if (moving || _cursorX != wasX || _cursorY != wasY) {
|
|
update();
|
|
}
|
|
return moving || influenced;
|
|
}
|
|
|
|
void PeerListSectionIndex::setCursor(QPoint position) {
|
|
_cursorX = position.x();
|
|
_cursorY = position.y();
|
|
if (!_fisheye.animating()) {
|
|
_fisheye.start();
|
|
}
|
|
}
|
|
|
|
int PeerListSectionIndex::slotAtY(int y) const {
|
|
auto best = -1;
|
|
auto bestDistance = 0;
|
|
for (auto i = 0; i != int(_slots.size()); ++i) {
|
|
const auto distance = std::abs(_slots[i].y - y);
|
|
if (best < 0 || distance < bestDistance) {
|
|
best = i;
|
|
bestDistance = distance;
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
void PeerListSectionIndex::scrubTo(int slot, anim::type animated) {
|
|
if (slot < 0 || slot == _current) {
|
|
return;
|
|
}
|
|
_current = slot;
|
|
if (_jump) {
|
|
_jump(_letters[_slots[slot].sourceIndex].contentTop, animated);
|
|
}
|
|
update();
|
|
}
|
|
|
|
void PeerListSectionIndex::mousePressEvent(QMouseEvent *e) {
|
|
_scrubbing = true;
|
|
setCursor(e->pos());
|
|
scrubTo(slotAtY(e->pos().y()), anim::type::normal);
|
|
}
|
|
|
|
void PeerListSectionIndex::mouseMoveEvent(QMouseEvent *e) {
|
|
setCursor(e->pos());
|
|
if (_scrubbing) {
|
|
scrubTo(slotAtY(e->pos().y()), anim::type::instant);
|
|
} else {
|
|
update();
|
|
}
|
|
}
|
|
|
|
void PeerListSectionIndex::mouseReleaseEvent(QMouseEvent *e) {
|
|
_scrubbing = false;
|
|
_current = -1;
|
|
update();
|
|
}
|
|
|
|
void PeerListSectionIndex::wheelEvent(QWheelEvent *e) {
|
|
if (_scroll) {
|
|
_scroll(e);
|
|
}
|
|
}
|
|
|
|
void PeerListSectionIndex::leaveEventHook(QEvent *e) {
|
|
if (!_fisheye.animating()) {
|
|
_fisheye.start();
|
|
}
|
|
}
|
|
|
|
void PeerListSectionIndex::paintEvent(QPaintEvent *e) {
|
|
auto p = QPainter(this);
|
|
const auto count = int(_slots.size());
|
|
if (!count) {
|
|
return;
|
|
}
|
|
const auto &font = st::peerListIndexFont;
|
|
p.setFont(font);
|
|
const auto centerX = columnCenterX();
|
|
|
|
auto centers = std::vector<float64>(count);
|
|
if (_cursorY < 0) {
|
|
for (auto i = 0; i != count; ++i) {
|
|
centers[i] = _slots[i].y;
|
|
}
|
|
} else {
|
|
auto expanded = 0.;
|
|
for (auto i = 0; i != count; ++i) {
|
|
expanded += _scale[i] * _pitch;
|
|
}
|
|
const auto inset = st::peerListIndexVerticalInset;
|
|
auto edge = inset + (height() - 2 * inset - expanded) / 2.;
|
|
for (auto i = 0; i != count; ++i) {
|
|
const auto size = _scale[i] * _pitch;
|
|
centers[i] = edge + size / 2.;
|
|
edge += size;
|
|
}
|
|
}
|
|
|
|
for (auto i = 0; i != count; ++i) {
|
|
const auto &slot = _slots[i];
|
|
const auto scale = _scale[i];
|
|
const auto active = (_current == i)
|
|
|| _visible.contains(slot.letter);
|
|
p.setPen(active
|
|
? st::peerListIndexActiveFg
|
|
: st::peerListIndexFg);
|
|
const auto centerY = centers[i];
|
|
const auto baseline = centerY + (font->ascent - font->descent) / 2.;
|
|
const auto x = centerX - (font->width(slot.letter) / 2.);
|
|
if (scale > 1.001) {
|
|
p.save();
|
|
p.translate(centerX, centerY);
|
|
p.scale(scale, scale);
|
|
p.translate(-centerX, -centerY);
|
|
p.drawText(QPointF(x, baseline), slot.letter);
|
|
p.restore();
|
|
} else {
|
|
p.drawText(QPointF(x, baseline), slot.letter);
|
|
}
|
|
}
|
|
}
|