Add members list to info profile.

This commit is contained in:
John Preston 2017-09-25 19:06:53 +03:00
parent faeb1483f2
commit f2a5862714
34 changed files with 943 additions and 300 deletions

View file

@ -961,23 +961,28 @@ namespace {
}
void feedChatAdmins(const MTPDupdateChatAdmins &d) {
ChatData *chat = App::chat(d.vchat_id.v);
auto chat = App::chat(d.vchat_id.v);
if (chat->version <= d.vversion.v) {
bool badVersion = (chat->version + 1 < d.vversion.v);
if (badVersion) {
chat->invalidateParticipants();
Auth().api().requestPeer(chat);
}
auto wasCanEdit = chat->canEdit();
auto badVersion = (chat->version + 1 < d.vversion.v);
chat->version = d.vversion.v;
if (mtpIsTrue(d.venabled)) {
if (!badVersion) {
chat->invalidateParticipants();
}
chat->flags |= MTPDchat::Flag::f_admins_enabled;
} else {
chat->flags &= ~MTPDchat::Flag::f_admins_enabled;
}
Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::AdminsChanged);
if (badVersion || mtpIsTrue(d.venabled)) {
chat->invalidateParticipants();
Auth().api().requestPeer(chat);
}
if (wasCanEdit != chat->canEdit()) {
Notify::peerUpdatedDelayed(
chat,
Notify::PeerUpdate::Flag::ChatCanEdit);
}
Notify::peerUpdatedDelayed(
chat,
Notify::PeerUpdate::Flag::AdminsChanged);
}
}

View file

@ -90,27 +90,7 @@ membersInnerDropdown: InnerDropdown(defaultInnerDropdown) {
scrollMargin: margins(0px, 5px, 0px, 5px);
scrollPadding: margins(0px, 3px, 0px, 3px);
}
membersInnerItem: ProfilePeerListItem {
left: 0px;
bottom: 0px;
button: OutlineButton {
outlineWidth: 0px;
textBg: windowBg;
textBgOver: windowBgOver;
textFg: windowSubTextFg;
textFgOver: windowSubTextFgOver;
font: normalFont;
padding: margins(11px, 5px, 11px, 5px);
ripple: defaultRippleAnimation;
}
statusFg: windowSubTextFg;
statusFgOver: windowSubTextFgOver;
statusFgActive: windowActiveTextFg;
}
membersInnerItem: defaultProfileMemberItem;
historyFileOutImage: icon {{ "history_file_image", historyFileOutIconFg }};
historyFileOutImageSelected: icon {{ "history_file_image", historyFileOutIconFgSelected }};
@ -456,14 +436,14 @@ historyAdminLogCancelSearch: CrossButton {
height: 54px;
cross: CrossAnimation {
size: 36px;
size: 32px;
skip: 10px;
stroke: 2px;
minScale: 0.3;
}
crossFg: menuIconFg;
crossFgOver: menuIconFgOver;
crossPosition: point(4px, 9px);
crossPosition: point(6px, 11px);
duration: 150;
loadingPeriod: 1000;

View file

@ -158,6 +158,7 @@ infoIconMediaPhoto: icon {{ "info_media_photo", infoIconFg }};
infoInformationIconPosition: point(25px, 12px);
infoNotificationsIconPosition: point(20px, 5px);
infoSharedMediaIconPosition: point(20px, 24px);
infoMembersIconPosition: point(20px, 15px);
infoLabeledOneLine: FlatLabel(defaultFlatLabel) {
width: 0px; // No need to set minWidth in one-line text.
@ -176,6 +177,16 @@ infoLabeled: FlatLabel(infoLabeledOneLine) {
margin: margins(5px, 5px, 5px, 5px);
}
infoBlockHeaderLabel: FlatLabel(infoProfileStatusLabel) {
textFg: windowBoldFg;
style: TextStyle(defaultTextStyle) {
font: semiboldFont;
linkFont: semiboldFont;
linkFontOver: semiboldFont;
}
}
infoBlockHeaderPosition: point(79px, 22px);
infoProfileToggle: Toggle(defaultToggle) {
diameter: 16px;
width: 14px;
@ -209,14 +220,93 @@ infoMainButton: InfoProfileButton(infoProfileButton) {
textFgOver: lightButtonFgOver;
}
infoSharedMediaCoverHeight: 62px;
infoSharedMediaLabelPosition: point(79px, 22px);
infoSharedMediaLabel: FlatLabel(infoProfileStatusLabel) {
textFg: windowBoldFg;
style: TextStyle(defaultTextStyle) {
font: semiboldFont;
linkFont: semiboldFont;
linkFontOver: semiboldFont;
}
}
infoSharedMediaButton: infoProfileButton;
infoSharedMediaBottomSkip: 12px;
infoMembersHeader: 56px;
infoMembersItem: ProfilePeerListItem(defaultProfileMemberItem) {
photoPosition: point(18px, 6px);
namePosition: point(79px, 11px);
statusPosition: point(79px, 31px);
}
infoMembersButtonPosition: point(12px, 9px);
infoMembersButtonIconPosition: point(6px, 6px);
infoMembersButton: IconButton(defaultIconButton) {
width: 44px;
height: 44px;
iconPosition: infoMembersButtonIconPosition;
rippleAreaPosition: point(0px, 0px);
rippleAreaSize: 44px;
ripple: RippleAnimation(defaultRippleAnimation) {
color: windowBgOver;
}
}
infoMembersAddMember: IconButton(infoMembersButton) {
icon: icon {{ "info_add_member", menuIconFg }};
iconOver: icon {{ "info_add_member", menuIconFgOver }};
}
infoMembersSearch: IconButton(infoMembersButton) {
icon: icon {{
"top_bar_search",
menuIconFg,
infoMembersButtonIconPosition
}};
iconOver: icon {{
"top_bar_search",
menuIconFgOver,
infoMembersButtonIconPosition
}};
iconPosition: point(0px, 0px);
}
infoMembersSearchActive: icon {
{ size(44px, 44px), windowBg },
{
"top_bar_search",
menuIconFgOver,
infoMembersButtonIconPosition
}
};
infoMembersSearchActiveLayer: icon {
{ size(44px, 44px), boxBg },
{
"top_bar_search",
menuIconFgOver,
infoMembersButtonIconPosition
}
};
infoMembersSearchField: FlatInput(defaultFlatInput) {
textColor: windowFg;
bgColor: topBarBg;
bgActive: topBarBg;
font: font(fsize);
borderWidth: 0px;
borderColor: topBarBg;
borderActive: topBarBg;
width: 100px;
height: 32px;
textMrg: margins(0px, 0px, 0px, 0px);
}
infoMembersCancelSearch: CrossButton {
width: 44px;
height: 44px;
cross: CrossAnimation {
size: 44px;
skip: 16px;
stroke: 2px;
minScale: 0.3;
}
crossFg: menuIconFg;
crossFgOver: menuIconFgOver;
crossPosition: point(0px, 0px);
duration: 150;
loadingPeriod: 1000;
ripple: RippleAnimation(defaultRippleAnimation) {
color: windowBgOver;
}
}
infoMembersSearchTop: 15px;

View file

@ -23,6 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "info/info_memento.h"
#include "info/info_top_bar.h"
#include "ui/rp_widget.h"
#include "ui/focus_persister.h"
#include "ui/widgets/buttons.h"
#include "window/section_widget.h"
#include "window/window_controller.h"
@ -106,6 +107,7 @@ void LayerWrap::parentResized() {
auto parentSize = parentWidget()->size();
auto parentWidth = parentSize.width();
if (parentWidth < MinimalSupportedWidth()) {
Ui::FocusPersister persister(this);
auto localCopy = _controller;
auto memento = MoveMemento(std::move(_content), Wrap::Narrow);
localCopy->hideSpecialLayer(anim::type::instant);
@ -124,6 +126,7 @@ void LayerWrap::parentResized() {
}
bool LayerWrap::takeToThirdSection() {
Ui::FocusPersister persister(this);
auto localCopy = _controller;
auto memento = MoveMemento(std::move(_content), Wrap::Side);
localCopy->hideSpecialLayer(anim::type::instant);

View file

@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "info/info_memento.h"
#include <rpl/never.h>
#include <rpl/combine.h>
#include "window/window_controller.h"
#include "ui/widgets/scroll_area.h"
#include "lang/lang_keys.h"
@ -51,7 +52,7 @@ ContentWidget::ContentWidget(
void ContentWidget::setWrap(Wrap wrap) {
if (_wrap != wrap) {
_wrap = wrap;
wrapUpdatedHook();
_wrapChanges.fire_copy(_wrap);
update();
}
}
@ -62,7 +63,6 @@ void ContentWidget::resizeEvent(QResizeEvent *e) {
QMargins(0, _scrollTopSkip, 0, 0));
if (_scroll->geometry() != scrollGeometry) {
_scroll->setGeometry(scrollGeometry);
_inner->setMinimumHeight(_scroll->height());
_inner->resizeToWidth(_scroll->width());
}
@ -100,14 +100,18 @@ void ContentWidget::setGeometryWithTopMoved(
Ui::RpWidget *ContentWidget::doSetInnerWidget(
object_ptr<RpWidget> inner,
int scrollTopSkip) {
using namespace rpl::mappers;
_inner = _scroll->setOwnedWidget(std::move(inner));
_inner->move(0, 0);
scrollTopValue()
| rpl::start([this, inner = _inner](int value) {
inner->setVisibleTopBottom(
value,
value + _scroll->height()); // TODO rpl::combine
rpl::combine(
_scroll->scrollTopValue(),
_scroll->heightValue(),
_inner->desiredHeightValue(),
tuple($1, $1 + $2, $3))
| rpl::start([inner = _inner](int top, int bottom, int desired) {
inner->setVisibleTopBottom(top, bottom);
}, _inner->lifetime());
return _inner;
}

View file

@ -121,8 +121,8 @@ protected:
not_null<Window::Controller*> controller() const {
return _controller;
}
Wrap wrap() const {
return _wrap;
rpl::producer<Wrap> wrapValue() const {
return _wrapChanges.events_starting_with_copy(_wrap);
}
void resizeEvent(QResizeEvent *e) override;
@ -132,9 +132,6 @@ protected:
int scrollTopSave() const;
void scrollTopRestore(int scrollTop);
virtual void wrapUpdatedHook() {
}
private:
RpWidget *doSetInnerWidget(
object_ptr<RpWidget> inner,
@ -143,6 +140,7 @@ private:
const not_null<Window::Controller*> _controller;
const not_null<PeerData*> _peer;
Wrap _wrap = Wrap::Layer;
rpl::event_stream<Wrap> _wrapChanges;
int _scrollTopSkip = 0;
object_ptr<Ui::ScrollArea> _scroll;

View file

@ -46,7 +46,7 @@ Button::Button(
}
Button *Button::toggleOn(rpl::producer<bool> &&toggled) {
_toggleOnLifetime.destroy();
Expects(_toggle == nullptr);
_toggle = std::make_unique<Ui::ToggleView>(
isOver() ? _st.toggleOver : _st.toggle,
false,
@ -54,11 +54,11 @@ Button *Button::toggleOn(rpl::producer<bool> &&toggled) {
clicks()
| rpl::start([this](auto) {
_toggle->setCheckedAnimated(!_toggle->checked());
}, _toggleOnLifetime);
}, lifetime());
std::move(toggled)
| rpl::start([this](bool toggled) {
_toggle->setCheckedAnimated(toggled);
}, _toggleOnLifetime);
}, lifetime());
_toggle->finishAnimation();
return this;
}

View file

@ -61,7 +61,6 @@ private:
int _originalWidth = 0;
int _textWidth = 0;
std::unique_ptr<Ui::ToggleView> _toggle;
rpl::lifetime _toggleOnLifetime;
};

View file

@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "info/profile/info_profile_cover.h"
#include <rpl/never.h>
#include <rpl/combine.h>
#include "info/profile/info_profile_values.h"
#include "lang/lang_keys.h"
#include "styles/style_info.h"
@ -36,6 +37,102 @@ namespace Info {
namespace Profile {
namespace {
class SectionToggle : public Ui::AbstractCheckView {
public:
SectionToggle(
const style::InfoToggle &st,
bool checked,
base::lambda<void()> updateCallback);
QSize getSize() const override;
void paint(
Painter &p,
int left,
int top,
int outerWidth,
TimeMs ms) override;
QImage prepareRippleMask() const override;
bool checkRippleStartPosition(QPoint position) const override;
private:
QSize rippleSize() const;
const style::InfoToggle &_st;
};
SectionToggle::SectionToggle(
const style::InfoToggle &st,
bool checked,
base::lambda<void()> updateCallback)
: AbstractCheckView(st.duration, checked, std::move(updateCallback))
, _st(st) {
}
QSize SectionToggle::getSize() const {
return QSize(_st.size, _st.size);
}
void SectionToggle::paint(
Painter &p,
int left,
int top,
int outerWidth,
TimeMs ms) {
auto sqrt2 = sqrt(2.);
auto vLeft = rtlpoint(left + _st.skip, 0, outerWidth).x() + 0.;
auto vTop = top + _st.skip + 0.;
auto vWidth = _st.size - 2 * _st.skip;
auto vHeight = _st.size - 2 * _st.skip;
auto vStroke = _st.stroke / sqrt2;
constexpr auto kPointCount = 6;
std::array<QPointF, kPointCount> pathV = { {
{ vLeft, vTop + (vHeight / 4.) + vStroke },
{ vLeft + vStroke, vTop + (vHeight / 4.) },
{ vLeft + (vWidth / 2.), vTop + (vHeight * 3. / 4.) - vStroke },
{ vLeft + vWidth - vStroke, vTop + (vHeight / 4.) },
{ vLeft + vWidth, vTop + (vHeight / 4.) + vStroke },
{ vLeft + (vWidth / 2.), vTop + (vHeight * 3. / 4.) + vStroke },
} };
auto toggled = currentAnimationValue(ms);
auto alpha = (toggled - 1.) * M_PI_2;
auto cosalpha = cos(alpha);
auto sinalpha = sin(alpha);
auto shiftx = vLeft + (vWidth / 2.);
auto shifty = vTop + (vHeight / 2.);
for (auto &point : pathV) {
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(pathV[0]);
for (int i = 1; i != kPointCount; ++i) {
path.lineTo(pathV[i]);
}
path.lineTo(pathV[0]);
PainterHighQualityEnabler hq(p);
p.fillPath(path, _st.color);
}
QImage SectionToggle::prepareRippleMask() const {
return Ui::RippleAnimation::ellipseMask(rippleSize());
}
QSize SectionToggle::rippleSize() const {
return getSize() + 2 * QSize(
_st.rippleAreaPadding,
_st.rippleAreaPadding);
}
bool SectionToggle::checkRippleStartPosition(QPoint position) const {
return QRect(QPoint(0, 0), rippleSize()).contains(position);
}
auto MembersStatusText(int count) {
return lng_chat_status_members(lt_count, count);
};
@ -59,8 +156,55 @@ auto ChatStatusText(int fullCount, int onlineCount, bool isGroup) {
} // namespace
SectionWithToggle *SectionWithToggle::setToggleShown(
rpl::producer<bool> &&shown) {
_toggle.create(
this,
QString(),
st::infoToggleCheckbox,
std::make_unique<SectionToggle>(
st::infoToggle,
false,
[this] { _toggle->updateCheck(); }));
_toggle->hide();
_toggle->lower();
_toggle->setCheckAlignment(style::al_right);
widthValue()
| rpl::start([this](int newValue) {
_toggle->setGeometry(0, 0, newValue, height());
}, _toggle->lifetime());
std::move(shown)
| rpl::start([this](bool shown) {
if (_toggle->isHidden() == shown) {
_toggle->setVisible(shown);
_toggleShown.fire_copy(shown);
}
}, lifetime());
return this;
}
rpl::producer<bool> SectionWithToggle::toggledValue() const {
return _toggle
? (rpl::single(_toggle->checked())
| rpl::then(
base::ObservableViewer(_toggle->checkedChanged)))
: rpl::never<bool>();
}
rpl::producer<bool> SectionWithToggle::toggleShownValue() const {
return _toggleShown.events_starting_with(
_toggle && !_toggle->isHidden());
}
int SectionWithToggle::toggleSkip() const {
return (!_toggle || _toggle->isHidden())
? 0
: st::infoToggleCheckbox.checkPosition.x()
+ _toggle->checkRect().width();
}
Cover::Cover(QWidget *parent, not_null<PeerData*> peer)
: FixedHeightWidget(
: SectionWithToggle(
parent,
st::infoProfilePhotoTop
+ st::infoProfilePhotoSize
@ -80,7 +224,11 @@ Cover::Cover(QWidget *parent, not_null<PeerData*> peer)
}
void Cover::setupChildGeometry() {
widthValue()
using namespace rpl::mappers;
rpl::combine(
toggleShownValue(),
widthValue(),
$2)
| rpl::start([this](int newWidth) {
_userpic->moveToLeft(
st::infoProfilePhotoLeft,
@ -100,30 +248,6 @@ Cover *Cover::setOnlineCount(rpl::producer<int> &&count) {
return this;
}
Cover *Cover::setToggleShown(rpl::producer<bool> &&shown) {
_toggle.create(
this,
QString(),
st::infoToggleCheckbox,
std::make_unique<SectionToggle>(
st::infoToggle,
false,
[this] { _toggle->updateCheck(); }));
_toggle->lower();
_toggle->setCheckAlignment(style::al_right);
widthValue()
| rpl::start([this](int newValue) {
_toggle->setGeometry(0, 0, newValue, height());
}, _toggle->lifetime());
std::move(shown)
| rpl::start([this](bool shown) {
if (_toggle->isHidden() == shown) {
_toggle->setVisible(shown);
}
}, lifetime());
return this;
}
void Cover::initViewers() {
using Flag = Notify::PeerUpdate::Flag;
PeerUpdateValue(_peer, Flag::PhotoChanged)
@ -201,11 +325,8 @@ void Cover::refreshStatusText() {
void Cover::refreshNameGeometry(int newWidth) {
auto nameWidth = newWidth
- st::infoProfileNameLeft
- st::infoProfileNameRight;
if (_toggle) {
nameWidth -= st::infoToggleCheckbox.checkPosition.x()
+ _toggle->checkRect().width();
}
- st::infoProfileNameRight
- toggleSkip();
_name->resizeToWidth(nameWidth);
_name->moveToLeft(
st::infoProfileNameLeft,
@ -216,11 +337,8 @@ void Cover::refreshNameGeometry(int newWidth) {
void Cover::refreshStatusGeometry(int newWidth) {
auto statusWidth = newWidth
- st::infoProfileStatusLeft
- st::infoProfileStatusRight;
if (_toggle) {
statusWidth -= st::infoToggleCheckbox.checkPosition.x()
+ _toggle->checkRect().width();
}
- st::infoProfileStatusRight
- toggleSkip();
_status->resizeToWidth(statusWidth);
_status->moveToLeft(
st::infoProfileStatusLeft,
@ -228,145 +346,38 @@ void Cover::refreshStatusGeometry(int newWidth) {
newWidth);
}
rpl::producer<bool> Cover::toggledValue() const {
return _toggle
? (rpl::single(_toggle->checked())
| rpl::then(
base::ObservableViewer(_toggle->checkedChanged)))
: rpl::never<bool>();
}
QMargins SharedMediaCover::getMargins() const {
return QMargins(0, 0, 0, st::infoSharedMediaBottomSkip);
}
SharedMediaCover::SharedMediaCover(QWidget *parent)
: FixedHeightWidget(parent, st::infoSharedMediaCoverHeight) {
: SectionWithToggle(parent, st::infoSharedMediaCoverHeight) {
createLabel();
}
void SharedMediaCover::createLabel() {
using namespace rpl::mappers;
auto label = object_ptr<Ui::FlatLabel>(
this,
Lang::Viewer(lng_profile_shared_media) | ToUpperValue(),
st::infoSharedMediaLabel);
st::infoBlockHeaderLabel);
label->setAttribute(Qt::WA_TransparentForMouseEvents);
widthValue()
| rpl::start([weak = label.data()](int newWidth) {
weak->resizeToNaturalWidth(newWidth
- st::infoSharedMediaLabelPosition.x()
- st::infoSharedMediaButton.padding.right());
rpl::combine(
toggleShownValue(),
widthValue(),
$2)
| rpl::start([this, weak = label.data()](int newWidth) {
auto availableWidth = newWidth
- st::infoBlockHeaderPosition.x()
- st::infoSharedMediaButton.padding.right()
- toggleSkip();
weak->resizeToWidth(availableWidth);
weak->moveToLeft(
st::infoSharedMediaLabelPosition.x(),
st::infoSharedMediaLabelPosition.y(),
st::infoBlockHeaderPosition.x(),
st::infoBlockHeaderPosition.y(),
newWidth);
}, label->lifetime());
}
SharedMediaCover *SharedMediaCover::setToggleShown(
rpl::producer<bool> &&shown) {
_toggle.create(
this,
QString(),
st::infoToggleCheckbox,
std::make_unique<SectionToggle>(
st::infoToggle,
false,
[this] { _toggle->updateCheck(); }));
_toggle->lower();
_toggle->setCheckAlignment(style::al_right);
widthValue()
| rpl::start([this](int newValue) {
_toggle->setGeometry(0, 0, newValue, height());
}, _toggle->lifetime());
std::move(shown)
| rpl::start([this](bool shown) {
if (_toggle->isHidden() == shown) {
_toggle->setVisible(shown);
}
}, lifetime());
return this;
}
rpl::producer<bool> SharedMediaCover::toggledValue() const {
return _toggle
? (rpl::single(_toggle->checked())
| rpl::then(
base::ObservableViewer(_toggle->checkedChanged)))
: rpl::never<bool>();
}
SectionToggle::SectionToggle(
const style::InfoToggle &st,
bool checked,
base::lambda<void()> updateCallback)
: AbstractCheckView(st.duration, checked, std::move(updateCallback))
, _st(st) {
}
QSize SectionToggle::getSize() const {
return QSize(_st.size, _st.size);
}
void SectionToggle::paint(
Painter &p,
int left,
int top,
int outerWidth,
TimeMs ms) {
auto sqrt2 = sqrt(2.);
auto vLeft = rtlpoint(left + _st.skip, 0, outerWidth).x() + 0.;
auto vTop = top + _st.skip + 0.;
auto vWidth = _st.size - 2 * _st.skip;
auto vHeight = _st.size - 2 * _st.skip;
auto vStroke = _st.stroke / sqrt2;
constexpr auto kPointCount = 6;
std::array<QPointF, kPointCount> pathV = { {
{ vLeft, vTop + (vHeight / 4.) + vStroke },
{ vLeft + vStroke, vTop + (vHeight / 4.) },
{ vLeft + (vWidth / 2.), vTop + (vHeight * 3. / 4.) - vStroke },
{ vLeft + vWidth - vStroke, vTop + (vHeight / 4.) },
{ vLeft + vWidth, vTop + (vHeight / 4.) + vStroke },
{ vLeft + (vWidth / 2.), vTop + (vHeight * 3. / 4.) + vStroke },
} };
auto toggled = currentAnimationValue(ms);
auto alpha = (toggled - 1.) * M_PI_2;
auto cosalpha = cos(alpha);
auto sinalpha = sin(alpha);
auto shiftx = vLeft + (vWidth / 2.);
auto shifty = vTop + (vHeight / 2.);
for (auto &point : pathV) {
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(pathV[0]);
for (int i = 1; i != kPointCount; ++i) {
path.lineTo(pathV[i]);
}
path.lineTo(pathV[0]);
PainterHighQualityEnabler hq(p);
p.fillPath(path, _st.color);
}
QImage SectionToggle::prepareRippleMask() const {
return Ui::RippleAnimation::ellipseMask(rippleSize());
}
QSize SectionToggle::rippleSize() const {
return getSize() + 2 * QSize(
_st.rippleAreaPadding,
_st.rippleAreaPadding);
}
bool SectionToggle::checkRippleStartPosition(QPoint position) const {
return QRect(QPoint(0, 0), rippleSize()).contains(position);
}
} // namespace Profile
} // namespace Info

View file

@ -40,13 +40,33 @@ class SlideWrap;
namespace Info {
namespace Profile {
class Cover : public Ui::FixedHeightWidget {
class SectionWithToggle : public Ui::FixedHeightWidget {
public:
using FixedHeightWidget::FixedHeightWidget;
SectionWithToggle *setToggleShown(rpl::producer<bool> &&shown);
rpl::producer<bool> toggledValue() const;
protected:
rpl::producer<bool> toggleShownValue() const;
int toggleSkip() const;
private:
object_ptr<Ui::Checkbox> _toggle = { nullptr };
rpl::event_stream<bool> _toggleShown;
};
class Cover : public SectionWithToggle {
public:
Cover(QWidget *parent, not_null<PeerData*> peer);
Cover *setOnlineCount(rpl::producer<int> &&count);
Cover *setToggleShown(rpl::producer<bool> &&shown);
rpl::producer<bool> toggledValue() const;
Cover *setToggleShown(rpl::producer<bool> &&shown) {
return static_cast<Cover*>(
SectionWithToggle::setToggleShown(std::move(shown)));
}
private:
void setupChildGeometry();
@ -64,49 +84,24 @@ private:
object_ptr<::Profile::UserpicButton> _userpic;
object_ptr<Ui::FlatLabel> _name = { nullptr };
object_ptr<Ui::FlatLabel> _status = { nullptr };
object_ptr<Ui::Checkbox> _toggle = { nullptr };
//object_ptr<CoverDropArea> _dropArea = { nullptr };
};
class SharedMediaCover : public Ui::FixedHeightWidget {
class SharedMediaCover : public SectionWithToggle {
public:
SharedMediaCover(QWidget *parent);
SharedMediaCover *setToggleShown(rpl::producer<bool> &&shown);
rpl::producer<bool> toggledValue() const;
SharedMediaCover *setToggleShown(rpl::producer<bool> &&shown) {
return static_cast<SharedMediaCover*>(
SectionWithToggle::setToggleShown(std::move(shown)));
}
QMargins getMargins() const override;
private:
void createLabel();
object_ptr<Ui::Checkbox> _toggle = { nullptr };
};
class SectionToggle : public Ui::AbstractCheckView {
public:
SectionToggle(
const style::InfoToggle &st,
bool checked,
base::lambda<void()> updateCallback);
QSize getSize() const override;
void paint(
Painter &p,
int left,
int top,
int outerWidth,
TimeMs ms) override;
QImage prepareRippleMask() const override;
bool checkRippleStartPosition(QPoint position) const override;
private:
QSize rippleSize() const;
const style::InfoToggle &_st;
};
} // namespace Profile

View file

@ -27,6 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "info/profile/info_profile_values.h"
#include "info/profile/info_profile_cover.h"
#include "info/profile/info_profile_icon.h"
#include "info/profile/info_profile_members.h"
#include "boxes/abstract_box.h"
#include "boxes/add_contact_box.h"
#include "mainwidget.h"
@ -44,12 +45,13 @@ namespace Profile {
InnerWidget::InnerWidget(
QWidget *parent,
rpl::producer<Wrap> &&wrapValue,
not_null<Window::Controller*> controller,
not_null<PeerData*> peer)
: RpWidget(parent)
, _controller(controller)
, _peer(peer)
, _content(setupContent(this)) {
, _content(setupContent(this, std::move(wrapValue))) {
_content->heightValue()
| rpl::start([this](int height) {
TWidget::resizeToWidth(width());
@ -67,7 +69,8 @@ rpl::producer<bool> InnerWidget::canHideDetails() const {
}
object_ptr<Ui::RpWidget> InnerWidget::setupContent(
RpWidget *parent) const {
RpWidget *parent,
rpl::producer<Wrap> &&wrapValue) const {
auto result = object_ptr<Ui::VerticalLayout>(parent);
auto cover = result->add(object_ptr<Cover>(
result,
@ -93,6 +96,12 @@ object_ptr<Ui::RpWidget> InnerWidget::setupContent(
// setupChannelActions(result, channel);
// }
}
if (_peer->isChat() || _peer->isMegagroup()) {
result->add(object_ptr<Members>(
result,
std::move(wrapValue),
_peer));
}
return std::move(result);
}
@ -361,8 +370,7 @@ object_ptr<Ui::SlideWrap<>> InnerWidget::createSlideSkipWidget(
void InnerWidget::visibleTopBottomUpdated(
int visibleTop,
int visibleBottom) {
_visibleTop = visibleTop;
_visibleBottom = visibleBottom;
setChildVisibleTopBottom(_content, visibleTop, visibleBottom);
}
void InnerWidget::saveState(not_null<Memento*> memento) {

View file

@ -34,6 +34,9 @@ class SlideWrap;
} // namespace Ui
namespace Info {
enum class Wrap;
namespace Profile {
class Memento;
@ -42,6 +45,7 @@ class InnerWidget final : public Ui::RpWidget {
public:
InnerWidget(
QWidget *parent,
rpl::producer<Wrap> &&wrapValue,
not_null<Window::Controller*> controller,
not_null<PeerData*> peer);
@ -64,7 +68,9 @@ protected:
int visibleBottom) override;
private:
object_ptr<RpWidget> setupContent(RpWidget *parent) const;
object_ptr<RpWidget> setupContent(
RpWidget *parent,
rpl::producer<Wrap> &&wrapValue) const;
object_ptr<RpWidget> setupDetails(RpWidget *parent) const;
object_ptr<RpWidget> setupSharedMedia(RpWidget *parent) const;
object_ptr<RpWidget> setupMuteToggle(RpWidget *parent) const;
@ -86,8 +92,6 @@ private:
not_null<Window::Controller*> _controller;
not_null<PeerData*> _peer;
int _visibleTop = 0;
int _visibleBottom = 0;
int _minHeight = 0;
object_ptr<RpWidget> _content;

View file

@ -0,0 +1,281 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "info/profile/info_profile_members.h"
#include <rpl/combine.h>
#include "info/profile/info_profile_values.h"
#include "info/profile/info_profile_icon.h"
#include "info/profile/info_profile_values.h"
#include "info/info_memento.h"
#include "profile/profile_block_group_members.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.h"
#include "styles/style_info.h"
#include "lang/lang_keys.h"
#include "boxes/confirm_box.h"
#include "boxes/peer_list_controllers.h"
namespace Info {
namespace Profile {
namespace {
constexpr auto kEnableSearchMembersAfterCount = 50;
} // namespace
Members::Members(
QWidget *parent,
rpl::producer<Wrap> &&wrapValue,
not_null<PeerData*> peer)
: RpWidget(parent)
, _peer(peer)
, _labelWrap(this)
, _label(setupHeader())
, _addMember(this, st::infoMembersAddMember)
, _searchField(
this,
st::infoMembersSearchField,
langFactory(lng_participant_filter))
, _search(this, st::infoMembersSearch)
, _cancelSearch(this, st::infoMembersCancelSearch)
, _list(setupList(this)) {
setupButtons();
std::move(wrapValue)
| rpl::start([this](Wrap wrap) {
_wrap = wrap;
updateSearchOverrides();
}, lifetime());
}
object_ptr<Ui::FlatLabel> Members::setupHeader() {
auto result = object_ptr<Ui::FlatLabel>(
_labelWrap,
MembersCountValue(_peer)
| rpl::map([](int count) {
return lng_chat_status_members(lt_count, count);
})
| ToUpperValue(),
st::infoBlockHeaderLabel);
result->setAttribute(Qt::WA_TransparentForMouseEvents);
return result;
}
void Members::setupButtons() {
using namespace rpl::mappers;
_searchField->hide();
_cancelSearch->hideFast();
auto addMemberShown = CanAddMemberValue(_peer)
| rpl::start_spawning(lifetime());
widthValue()
| rpl::start([button = _addMember.data()](int newWidth) {
button->moveToRight(
st::infoMembersButtonPosition.x(),
st::infoMembersButtonPosition.y(),
newWidth);
}, _addMember->lifetime());
_addMember->showOn(rpl::duplicate(addMemberShown));
_addMember->clicks() // TODO throttle(ripple duration)
| rpl::start([this](auto&&) {
this->addMember();
}, _addMember->lifetime());
auto searchShown = MembersCountValue(_peer)
| rpl::map($1 >= kEnableSearchMembersAfterCount)
| rpl::distinct_until_changed()
| rpl::start_spawning(lifetime());
_search->showOn(rpl::duplicate(searchShown));
_search->clicks()
| rpl::start([this](auto&&) {
this->showSearch();
}, _search->lifetime());
_cancelSearch->clicks()
| rpl::start([this](auto&&) {
this->cancelSearch();
}, _cancelSearch->lifetime());
rpl::combine(
std::move(addMemberShown),
std::move(searchShown))
| rpl::start([this](auto&&) {
this->resizeToWidth(width());
}, lifetime());
object_ptr<FloatingIcon>(
this,
st::infoIconMembers,
st::infoMembersIconPosition)->lower();
}
object_ptr<Members::ListWidget> Members::setupList(
RpWidget *parent) const {
auto result = object_ptr<ListWidget>(
parent,
_peer,
::Profile::GroupMembersWidget::TitleVisibility::Hidden,
st::infoMembersItem);
result->moveToLeft(0, st::infoMembersHeader);
parent->widthValue()
| rpl::start([list = result.data()](int newWidth) {
list->resizeToWidth(newWidth);
}, result->lifetime());
result->heightValue()
| rpl::start([parent](int listHeight) {
auto newHeight = (listHeight > 0)
? (st::infoMembersHeader + listHeight)
: 0;
parent->resize(parent->width(), newHeight);
}, result->lifetime());
return result;
}
int Members::resizeGetHeight(int newWidth) {
auto availableWidth = newWidth
- st::infoMembersButtonPosition.x();
if (!_addMember->isHidden()) {
availableWidth -= st::infoMembersAddMember.width;
}
auto cancelLeft = availableWidth - _cancelSearch->width();
_cancelSearch->moveToLeft(
cancelLeft,
st::infoMembersButtonPosition.y());
auto searchShownLeft = st::infoMembersIconPosition.x()
- st::infoMembersSearch.iconPosition.x();
auto searchHiddenLeft = availableWidth - _search->width();
auto searchShown = _searchShownAnimation.current(_searchShown ? 1. : 0.);
auto searchCurrentLeft = anim::interpolate(
searchHiddenLeft,
searchShownLeft,
searchShown);
_search->moveToLeft(
searchCurrentLeft,
st::infoMembersButtonPosition.y());
auto fieldLeft = anim::interpolate(
cancelLeft,
st::infoBlockHeaderPosition.x(),
searchShown);
_searchField->setGeometryToLeft(
fieldLeft,
st::infoMembersSearchTop,
cancelLeft - fieldLeft,
_searchField->height());
_labelWrap->resize(
searchCurrentLeft - st::infoBlockHeaderPosition.x(),
_label->height());
_labelWrap->moveToLeft(
st::infoBlockHeaderPosition.x(),
st::infoBlockHeaderPosition.y(),
newWidth);
_label->resizeToWidth(searchHiddenLeft);
_label->moveToLeft(0, 0);
return heightNoMargins();
}
void Members::addMember() {
if (auto chat = _peer->asChat()) {
if (chat->count >= Global::ChatSizeMax() && chat->amCreator()) {
Ui::show(Box<ConvertToSupergroupBox>(chat));
} else {
AddParticipantsBoxController::Start(chat);
}
} else if (auto channel = _peer->asChannel()) {
if (channel->mgInfo) {
auto &participants = channel->mgInfo->lastParticipants;
AddParticipantsBoxController::Start(channel, { participants.cbegin(), participants.cend() });
}
}
}
void Members::showSearch() {
if (!_searchShown) {
toggleSearch();
}
}
void Members::toggleSearch() {
_searchShown = !_searchShown;
_cancelSearch->toggleAnimated(_searchShown);
_searchShownAnimation.start(
[this] { searchAnimationCallback(); },
_searchShown ? 0. : 1.,
_searchShown ? 1. : 0.,
st::slideWrapDuration);
_search->setDisabled(_searchShown);
if (_searchShown) {
_searchField->show();
_searchField->setFocus();
} else {
setFocus();
}
}
void Members::searchAnimationCallback() {
if (!_searchShownAnimation.animating()) {
_searchField->setVisible(_searchShown);
updateSearchOverrides();
_search->setPointerCursor(!_searchShown);
}
resizeToWidth(width());
}
void Members::updateSearchOverrides() {
auto iconOverride = !_searchShown
? nullptr
: (_wrap == Wrap::Layer)
? &st::infoMembersSearchActiveLayer
: &st::infoMembersSearchActive;
_search->setIconOverride(iconOverride, iconOverride);
}
void Members::cancelSearch() {
if (_searchShown) {
if (!_searchField->getLastText().isEmpty()) {
_searchField->setText(QString());
_searchField->updatePlaceholder();
_searchField->setFocus();
applySearch();
} else {
toggleSearch();
}
}
}
void Members::applySearch() {
}
void Members::visibleTopBottomUpdated(
int visibleTop,
int visibleBottom) {
setChildVisibleTopBottom(_list, visibleTop, visibleBottom);
}
} // namespace Profile
} // namespace Info

View file

@ -0,0 +1,89 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "ui/rp_widget.h"
namespace Ui {
class FlatInput;
class CrossButton;
class IconButton;
class FlatLabel;
} // namespace Ui
namespace Profile {
class GroupMembersWidget;
} // namespace Profile
namespace Info {
enum class Wrap;
namespace Profile {
class Members : public Ui::RpWidget {
public:
Members(
QWidget *parent,
rpl::producer<Wrap> &&wrapValue,
not_null<PeerData*> peer);
protected:
void visibleTopBottomUpdated(
int visibleTop,
int visibleBottom) override;
int resizeGetHeight(int newWidth) override;
private:
using ListWidget = ::Profile::GroupMembersWidget;
object_ptr<Ui::FlatLabel> setupHeader();
object_ptr<ListWidget> setupList(
RpWidget *parent) const;
void setupButtons();
void updateSearchOverrides();
void addMember();
void showSearch();
void toggleSearch();
void cancelSearch();
void applySearch();
void searchAnimationCallback();
Wrap _wrap;
not_null<PeerData*> _peer;
object_ptr<Ui::RpWidget> _labelWrap;
object_ptr<Ui::FlatLabel> _label;
object_ptr<Ui::IconButton> _addMember;
object_ptr<Ui::FlatInput> _searchField;
object_ptr<Ui::IconButton> _search;
object_ptr<Ui::CrossButton> _cancelSearch;
object_ptr<ListWidget> _list;
Animation _searchShownAnimation;
bool _searchShown = false;
base::Timer _searchTimer;
};
} // namespace Profile
} // namespace Info

View file

@ -220,6 +220,26 @@ rpl::producer<int> CommonGroupsCountValue(
});
}
rpl::producer<bool> CanAddMemberValue(
not_null<PeerData*> peer) {
if (auto chat = peer->asChat()) {
return PeerUpdateValue(
chat,
Notify::PeerUpdate::Flag::ChatCanEdit)
| rpl::map([chat](auto&&) {
return chat->canEdit();
});
} else if (auto channel = peer->asChannel()) {
return PeerUpdateValue(
channel,
Notify::PeerUpdate::Flag::ChannelRightsChanged)
| rpl::map([channel](auto&&) {
return channel->canAddMembers();
});
}
return rpl::single(false);
}
rpl::producer<bool> MultiLineTracker::atLeastOneShownValue() const {
auto shown = std::vector<rpl::producer<bool>>();
shown.reserve(_widgets.size());

View file

@ -74,6 +74,8 @@ rpl::producer<int> SharedMediaCountValue(
Storage::SharedMediaType type);
rpl::producer<int> CommonGroupsCountValue(
not_null<UserData*> user);
rpl::producer<bool> CanAddMemberValue(
not_null<PeerData*> peer);
class MultiLineTracker {
public:

View file

@ -48,6 +48,7 @@ Widget::Widget(
: ContentWidget(parent, wrap, controller, peer) {
_inner = setInnerWidget(object_ptr<InnerWidget>(
this,
wrapValue(),
controller,
peer));
_inner->move(0, 0);

View file

@ -31,6 +31,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "window/top_bar_widget.h"
#include "data/data_drafts.h"
#include "ui/widgets/dropdown_menu.h"
#include "ui/focus_persister.h"
#include "chat_helpers/message_field.h"
#include "chat_helpers/stickers.h"
#include "observer_peer.h"
@ -3028,6 +3029,7 @@ void MainWidget::checkMainSectionToLayer() {
if (!_mainSection) {
return;
}
Ui::FocusPersister persister(this);
if (auto layer = _mainSection->moveContentToLayer(width())) {
dropMainSection(_mainSection);
_controller->showSpecialLayer(

View file

@ -121,21 +121,15 @@ profileInviteLinkText: FlatLabel(profileBlockTextPart) {
profileLimitReachedSkip: 6px;
profileMemberItem: ProfilePeerListItem {
profileMemberItem: ProfilePeerListItem(defaultProfileMemberItem) {
left: 8px;
bottom: profileBlockMarginBottom;
button: defaultLeftOutlineButton;
statusFg: windowSubTextFg;
maximalWidth: profileBlockWideWidthMax;
statusFgOver: profileStatusFgOver;
statusFgActive: windowActiveTextFg;
}
profileMemberHeight: 58px;
profileMemberPaddingLeft: 16px;
profileMemberPhotoSize: 46px;
profileMemberPhotoPosition: point(12px, 6px);
profileMemberNamePosition: point(68px, 11px);
profileMemberNameFg: windowBoldFg;
profileMemberStatusPosition: point(68px, 31px);
profileMemberCreatorIcon: icon {{ "profile_admin_star", profileAdminStartFg, point(4px, 3px) }};
profileMemberCreatorIconOver: icon {{ "profile_admin_star", profileAdminStarFgOver, point(4px, 3px) }};
profileMemberAdminIcon: icon {{ "profile_admin_star", profileOtherAdminStarFg, point(4px, 3px) }};

View file

@ -35,7 +35,11 @@ namespace Profile {
using UpdateFlag = Notify::PeerUpdate::Flag;
GroupMembersWidget::GroupMembersWidget(QWidget *parent, PeerData *peer, TitleVisibility titleVisibility, const style::ProfilePeerListItem &st)
GroupMembersWidget::GroupMembersWidget(
QWidget *parent,
PeerData *peer,
TitleVisibility titleVisibility,
const style::ProfilePeerListItem &st)
: PeerListWidget(parent
, peer
, (titleVisibility == TitleVisibility::Visible) ? lang(lng_profile_participants_section) : QString()

View file

@ -45,7 +45,7 @@ PeerListWidget::PeerListWidget(QWidget *parent, PeerData *peer, const QString &t
int PeerListWidget::resizeGetHeight(int newWidth) {
auto newHeight = getListTop();
newHeight += _items.size() * st::profileMemberHeight;
newHeight += _items.size() * _st.height;
return newHeight + _st.bottom;
}
@ -67,10 +67,10 @@ void PeerListWidget::paintContents(Painter &p) {
auto top = getListTop();
auto memberRowWidth = rowWidth();
auto from = floorclamp(_visibleTop - top, st::profileMemberHeight, 0, _items.size());
auto to = ceilclamp(_visibleBottom - top, st::profileMemberHeight, 0, _items.size());
auto from = floorclamp(_visibleTop - top, _st.height, 0, _items.size());
auto to = ceilclamp(_visibleBottom - top, _st.height, 0, _items.size());
for (auto i = from; i < to; ++i) {
auto y = top + i * st::profileMemberHeight;
auto y = top + i * _st.height;
auto selected = (_menuRowIndex >= 0) ? (i == _menuRowIndex) : (_pressed >= 0) ? (i == _pressed) : (i == _selected);
auto selectedRemove = selected && _selectedRemove;
if (_pressed >= 0 && !_pressedRemove) {
@ -87,7 +87,7 @@ void PeerListWidget::paintItem(Painter &p, int x, int y, Item *item, bool select
auto memberRowWidth = rowWidth();
if (selected) {
paintOutlinedRect(p, x, y, memberRowWidth, st::profileMemberHeight);
paintOutlinedRect(p, x, y, memberRowWidth, _st.height);
}
if (auto &ripple = item->ripple) {
ripple->paint(p, x + _st.button.outlineWidth, y, width(), ms);
@ -95,16 +95,16 @@ void PeerListWidget::paintItem(Painter &p, int x, int y, Item *item, bool select
ripple.reset();
}
}
int skip = st::profileMemberPhotoPosition.x();
int skip = _st.photoPosition.x();
item->peer->paintUserpicLeft(p, x + st::profileMemberPhotoPosition.x(), y + st::profileMemberPhotoPosition.y(), width(), st::profileMemberPhotoSize);
item->peer->paintUserpicLeft(p, x + _st.photoPosition.x(), y + _st.photoPosition.y(), width(), _st.photoSize);
if (item->name.isEmpty()) {
item->name.setText(st::msgNameStyle, App::peerName(item->peer), _textNameOptions);
}
int nameLeft = x + st::profileMemberNamePosition.x();
int nameTop = y + st::profileMemberNamePosition.y();
int nameWidth = memberRowWidth - st::profileMemberNamePosition.x() - skip;
int nameLeft = x + _st.namePosition.x();
int nameTop = y + _st.namePosition.y();
int nameWidth = memberRowWidth - _st.namePosition.x() - skip;
if (item->hasRemoveLink && selected) {
p.setFont(selectedKick ? st::normalFont->underline() : st::normalFont);
p.setPen(st::windowActiveTextFg);
@ -128,7 +128,7 @@ void PeerListWidget::paintItem(Painter &p, int x, int y, Item *item, bool select
p.setPen(selected ? _st.statusFgOver : _st.statusFg);
}
p.setFont(st::normalFont);
p.drawTextLeft(x + st::profileMemberStatusPosition.x(), y + st::profileMemberStatusPosition.y(), width(), item->statusText);
p.drawTextLeft(x + _st.statusPosition.x(), y + _st.statusPosition.y(), width(), item->statusText);
}
void PeerListWidget::paintOutlinedRect(Painter &p, int x, int y, int w, int h) const {
@ -155,13 +155,13 @@ void PeerListWidget::mousePressEvent(QMouseEvent *e) {
auto item = _items[_pressed];
if (!item->ripple) {
auto memberRowWidth = rowWidth();
auto mask = Ui::RippleAnimation::rectMask(QSize(memberRowWidth - _st.button.outlineWidth, st::profileMemberHeight));
auto mask = Ui::RippleAnimation::rectMask(QSize(memberRowWidth - _st.button.outlineWidth, _st.height));
item->ripple = std::make_unique<Ui::RippleAnimation>(_st.button.ripple, std::move(mask), [this, index = _pressed] {
repaintRow(index);
});
}
auto left = getListLeft() + _st.button.outlineWidth;
auto top = getListTop() + st::profileMemberHeight * _pressed;
auto top = getListTop() + _st.height * _pressed;
item->ripple->add(e->pos() - QPoint(left, top));
}
}
@ -250,14 +250,14 @@ void PeerListWidget::updateSelection() {
auto top = getListTop();
auto memberRowWidth = rowWidth();
if (mouse.x() >= left && mouse.x() < left + memberRowWidth && mouse.y() >= top) {
selected = (mouse.y() - top) / st::profileMemberHeight;
selected = (mouse.y() - top) / _st.height;
if (selected >= _items.size()) {
selected = -1;
} else if (_items[selected]->hasRemoveLink) {
int skip = st::profileMemberPhotoPosition.x();
int nameLeft = left + st::profileMemberNamePosition.x();
int nameTop = top + _selected * st::profileMemberHeight + st::profileMemberNamePosition.y();
int nameWidth = memberRowWidth - st::profileMemberNamePosition.x() - skip;
int skip = _st.photoPosition.x();
int nameLeft = left + _st.namePosition.x();
int nameTop = top + _selected * _st.height + _st.namePosition.y();
int nameWidth = memberRowWidth - _st.namePosition.x() - skip;
if (mouse.x() >= nameLeft + nameWidth - _removeWidth && mouse.x() < nameLeft + nameWidth) {
if (mouse.y() >= nameTop && mouse.y() < nameTop + st::normalFont->height) {
selectedKick = true;
@ -294,7 +294,7 @@ void PeerListWidget::repaintSelectedRow() {
void PeerListWidget::repaintRow(int index) {
if (index >= 0) {
auto left = getListLeft();
rtlupdate(left, getListTop() + index * st::profileMemberHeight, width() - left, st::profileMemberHeight);
rtlupdate(left, getListTop() + index * _st.height, width() - left, _st.height);
}
}
@ -303,14 +303,16 @@ int PeerListWidget::getListLeft() const {
}
int PeerListWidget::rowWidth() const {
return qMin(width() - getListLeft(), st::profileBlockWideWidthMax);
return _st.maximalWidth
? qMin(width() - getListLeft(), _st.maximalWidth)
: width() - getListLeft();
}
void PeerListWidget::preloadPhotos() {
int top = getListTop();
int preloadFor = (_visibleBottom - _visibleTop) * PreloadHeightsCount;
int from = floorclamp(_visibleTop - top, st::profileMemberHeight, 0, _items.size());
int to = ceilclamp(_visibleBottom + preloadFor - top, st::profileMemberHeight, 0, _items.size());
int from = floorclamp(_visibleTop - top, _st.height, 0, _items.size());
int to = ceilclamp(_visibleBottom + preloadFor - top, _st.height, 0, _items.size());
for (int i = from; i < to; ++i) {
_items[i]->peer->loadUserpic();
}

View file

@ -25,7 +25,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
namespace Profile {
BlockWidget::BlockWidget(QWidget *parent, PeerData *peer, const QString &title) : TWidget(parent)
BlockWidget::BlockWidget(
QWidget *parent,
PeerData *peer,
const QString &title) : RpWidget(parent)
, _peer(peer)
, _title(title) {
}

View file

@ -21,12 +21,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#pragma once
#include "base/observer.h"
#include "ui/rp_widget.h"
namespace Profile {
class SectionMemento;
class BlockWidget : public TWidget, protected base::Subscriber {
class BlockWidget : public Ui::RpWidget, protected base::Subscriber {
Q_OBJECT
public:

View file

@ -432,6 +432,45 @@ inline auto operator~(Type &&value) {
std::forward<Type>(value));
}
template <typename ...Mappers>
class tuple_mapper {
template <typename ...Args>
using tuple_result = std::tuple<decltype(
std::declval<wrap_mapper_t<std::decay_t<Mappers>>>()(
std::declval<Args>()...))...>;
public:
template <typename ...OtherMappers>
tuple_mapper(OtherMappers &&...mappers) : _mappers(
std::forward<OtherMappers>(mappers)...) {
}
template <typename ...Args>
constexpr tuple_result<Args...> operator()(
Args &&...args) const {
constexpr auto kArity = sizeof...(Mappers);
return call_helper(
std::make_index_sequence<kArity>(),
std::forward<Args>(args)...);
}
private:
template <typename ...Args, std::size_t ...I>
inline tuple_result<Args...> call_helper(
std::index_sequence<I...>,
Args &&...args) const {
return std::make_tuple(
std::get<I>(_mappers)(std::forward<Args>(args)...)...);
}
std::tuple<wrap_mapper_t<std::decay_t<Mappers>>...> _mappers;
};
template <typename ...Args>
tuple_mapper<Args...> tuple(Args &&...args) {
return tuple_mapper<Args...>(std::forward<Args>(args)...);
}
} // namespace details
namespace mappers {

View file

@ -71,12 +71,15 @@ void AbstractButton::mouseReleaseEvent(QMouseEvent *e) {
onStateChanged(was, StateChangeSource::ByPress);
if (was & StateFlag::Over) {
_modifiers = e->modifiers();
auto test = weak(this);
if (_clickedCallback) {
_clickedCallback();
} else {
emit clicked();
}
_clicks.fire({});
if (test) {
_clicks.fire({});
}
} else {
setOver(false, StateChangeSource::ByHover);
}

View file

@ -0,0 +1,55 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
namespace Ui {
class FocusPersister {
public:
FocusPersister(QWidget *parent, QWidget *steal = nullptr)
: _weak(GrabFocused(parent)) {
if (steal) {
steal->setFocus();
}
}
~FocusPersister() {
if (auto strong = _weak.data()) {
if (auto window = strong->window()) {
if (window->focusWidget() != strong) {
strong->setFocus();
}
}
}
}
private:
static QWidget *GrabFocused(QWidget *parent) {
if (auto window = parent ? parent->window() : nullptr) {
return window->focusWidget();
}
return nullptr;
}
QPointer<QWidget> _weak;
};
} // namespace Ui

View file

@ -26,8 +26,16 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
namespace Ui {
template <typename Parent>
class RpWidgetWrap : public Parent {
template <typename Widget>
using RpWidgetParent = std::conditional_t<
std::is_same_v<Widget, QWidget>,
TWidget,
TWidgetHelper<Widget>>;
template <typename Widget>
class RpWidgetWrap : public RpWidgetParent<Widget> {
using Parent = RpWidgetParent<Widget>;
public:
using Parent::Parent;
@ -77,6 +85,13 @@ public:
return eventStreams().alive.events();
}
void showOn(rpl::producer<bool> &&shown) {
std::move(shown)
| rpl::start([this](bool visible) {
setVisible(visible);
}, lifetime());
}
rpl::lifetime &lifetime() {
return _lifetime.data;
}
@ -110,7 +125,7 @@ protected:
return eventHook(event);
}
virtual bool eventHook(QEvent *event) {
return TWidget::event(event);
return Parent::event(event);
}
private:
@ -139,9 +154,9 @@ private:
};
class RpWidget : public RpWidgetWrap<TWidget> {
class RpWidget : public RpWidgetWrap<QWidget> {
public:
using RpWidgetWrap<TWidget>::RpWidgetWrap;
using RpWidgetWrap<QWidget>::RpWidgetWrap;
};

View file

@ -319,7 +319,8 @@ void SplittedWidgetOther::paintEvent(QPaintEvent *e) {
}
}
ScrollArea::ScrollArea(QWidget *parent, const style::ScrollArea &st, bool handleTouch) : TWidgetHelper<QScrollArea>(parent)
ScrollArea::ScrollArea(QWidget *parent, const style::ScrollArea &st, bool handleTouch)
: RpWidgetWrap<QScrollArea>(parent)
, _st(st)
, _horizontalBar(this, false, &_st)
, _verticalBar(this, true, &_st)

View file

@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#pragma once
#include <rpl/event_stream.h>
#include "ui/rp_widget.h"
#include "styles/style_widgets.h"
namespace Ui {
@ -171,7 +172,7 @@ private:
};
class SplittedWidgetOther;
class ScrollArea : public TWidgetHelper<QScrollArea> {
class ScrollArea : public Ui::RpWidgetWrap<QScrollArea> {
Q_OBJECT
public:

View file

@ -1055,6 +1055,12 @@ MediaPlayerButton {
ProfilePeerListItem {
left: pixels;
bottom: pixels;
height: pixels;
photoPosition: point;
namePosition: point;
statusPosition: point;
photoSize: pixels;
maximalWidth: pixels;
button: OutlineButton;
statusFg: color;
@ -1062,6 +1068,31 @@ ProfilePeerListItem {
statusFgActive: color;
}
defaultProfileMemberItem: ProfilePeerListItem {
height: 58px;
photoPosition: point(12px, 6px);
namePosition: point(68px, 11px);
statusPosition: point(68px, 31px);
photoSize: 46px;
button: OutlineButton {
outlineWidth: 0px;
textBg: windowBg;
textBgOver: windowBgOver;
textFg: windowSubTextFg;
textFgOver: windowSubTextFgOver;
font: normalFont;
padding: margins(11px, 5px, 11px, 5px);
ripple: defaultRippleAnimation;
}
statusFg: windowSubTextFg;
statusFgOver: windowSubTextFgOver;
statusFgActive: windowActiveTextFg;
}
InfoTopBar {
height: pixels;
back: IconButton;

View file

@ -84,11 +84,10 @@ SlideWrap<RpWidget> *SlideWrap<RpWidget>::finishAnimations() {
SlideWrap<RpWidget> *SlideWrap<RpWidget>::toggleOn(
rpl::producer<bool> &&shown) {
_toggleOnLifetime.destroy();
std::move(shown)
| rpl::start([this](bool shown) {
toggleAnimated(shown);
}, _toggleOnLifetime);
}, lifetime());
finishAnimations();
return this;
}

View file

@ -77,7 +77,6 @@ private:
bool _shown = true;
rpl::event_stream<bool> _shownUpdated;
rpl::lifetime _toggleOnLifetime;
Animation _slideAnimation;
int _duration = 0;

View file

@ -263,6 +263,9 @@ void TopBarWidget::paintEvent(QPaintEvent *e) {
if (!_menuToggle->isHidden()) {
decreaseWidth += _menuToggle->width();
}
if (!_infoToggle->isHidden()) {
decreaseWidth += _infoToggle->width() + st::topBarSkip;
}
if (!_search->isHidden()) {
decreaseWidth += _search->width();
}

View file

@ -564,6 +564,7 @@
<(src_loc)/ui/countryinput.h
<(src_loc)/ui/emoji_config.cpp
<(src_loc)/ui/emoji_config.h
<(src_loc)/ui/focus_persister.h
<(src_loc)/ui/images.cpp
<(src_loc)/ui/images.h
<(src_loc)/ui/rp_widget.h