Apply geometry constraints in stories viewer.

This commit is contained in:
John Preston 2023-05-05 14:40:51 +04:00
parent 89ca38ed29
commit 027bd89e5b
15 changed files with 607 additions and 91 deletions

View File

@ -962,6 +962,8 @@ PRIVATE
media/player/media_player_volume_controller.h
media/player/media_player_widget.cpp
media/player/media_player_widget.h
media/stories/media_stories_controller.cpp
media/stories/media_stories_controller.h
media/stories/media_stories_delegate.cpp
media/stories/media_stories_delegate.h
media/stories/media_stories_header.cpp

View File

@ -355,6 +355,7 @@ public:
rpl::producer<QString> title,
rpl::producer<QString> description,
rpl::producer<WebPageData*> page);
void previewUnregister();
[[nodiscard]] bool isDisplayed() const;
[[nodiscard]] bool isEditingMessage() const;
@ -413,6 +414,7 @@ private:
rpl::event_stream<> _replyCancelled;
rpl::event_stream<> _forwardCancelled;
rpl::event_stream<> _previewCancelled;
rpl::lifetime _previewLifetime;
rpl::variable<FullMsgId> _editMsgId;
rpl::variable<FullMsgId> _replyToId;
@ -677,17 +679,18 @@ void FieldHeader::resolveMessageData() {
}
void FieldHeader::previewRequested(
rpl::producer<QString> title,
rpl::producer<QString> description,
rpl::producer<WebPageData*> page) {
rpl::producer<QString> title,
rpl::producer<QString> description,
rpl::producer<WebPageData*> page) {
_previewLifetime.destroy();
std::move(
title
) | rpl::filter([=] {
return !_preview.cancelled;
}) | start_with_next([=](const QString &t) {
}) | rpl::start_with_next([=](const QString &t) {
_title = t;
}, lifetime());
}, _previewLifetime);
std::move(
description
@ -695,7 +698,7 @@ void FieldHeader::previewRequested(
return !_preview.cancelled;
}) | rpl::start_with_next([=](const QString &d) {
_description = d;
}, lifetime());
}, _previewLifetime);
std::move(
page
@ -704,8 +707,11 @@ void FieldHeader::previewRequested(
}) | rpl::start_with_next([=](WebPageData *p) {
_preview.data = p;
updateVisible();
}, lifetime());
}, _previewLifetime);
}
void FieldHeader::previewUnregister() {
_previewLifetime.destroy();
}
void FieldHeader::paintWebPage(Painter &p, not_null<PeerData*> context) {
@ -996,10 +1002,6 @@ Main::Session &ComposeControls::session() const {
}
void ComposeControls::setHistory(SetHistoryArgs &&args) {
// Right now only single non-null set of history is supported.
// Otherwise initWebpageProcess should be updated / rewritten.
Expects(!_history && (*args.history));
_showSlowmodeError = std::move(args.showSlowmodeError);
_sendActionFactory = std::move(args.sendActionFactory);
_slowmodeSecondsLeft = rpl::single(0)
@ -1014,6 +1016,7 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) {
}
unregisterDraftSources();
_history = history;
_historyLifetime.destroy();
_header->setHistory(args);
registerDraftSource();
_selector->setCurrentPeer(history ? history->peer.get() : nullptr);
@ -1025,9 +1028,12 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) {
updateControlsVisibility();
updateFieldPlaceholder();
updateAttachBotsMenu();
//if (!_history) {
// return;
//}
_sendAs = nullptr;
_silent = nullptr;
if (!_history) {
return;
}
const auto peer = _history->peer;
initSendAsButton(peer);
if (peer->isChat() && peer->asChat()->noParticipantInfo()) {
@ -2134,7 +2140,7 @@ void ComposeControls::initSendAsButton(not_null<PeerData*> peer) {
updateControlsGeometry(_wrap->size());
orderControls();
}
}, _wrap->lifetime());
}, _historyLifetime);
updateSendAsButton();
}
@ -2218,7 +2224,7 @@ void ComposeControls::initVoiceRecordBar() {
_voiceRecordBar->setStartRecordingFilter([=] {
const auto error = [&]() -> std::optional<QString> {
const auto peer = _history ? _history->peer.get() : nullptr;
if (!peer) {
if (peer) {
if (const auto error = Data::RestrictionError(
peer,
ChatRestriction::SendVoiceMessages)) {
@ -2448,10 +2454,9 @@ void ComposeControls::updateMessagesTTLShown() {
}
bool ComposeControls::updateSendAsButton() {
Expects(_history != nullptr);
const auto peer = _history->peer;
if (!_regularWindow
const auto peer = _history ? _history->peer.get() : nullptr;
if (!peer
|| !_regularWindow
|| isEditingMessage()
|| !session().sendAsPeers().shouldChoose(peer)) {
if (!_sendAs) {
@ -2467,7 +2472,7 @@ bool ComposeControls::updateSendAsButton() {
st::sendAsButton);
Ui::SetupSendAsButton(
_sendAs.get(),
rpl::single(peer.get()),
rpl::single(peer),
_regularWindow);
return true;
}
@ -2781,15 +2786,18 @@ bool ComposeControls::handleCancelRequest() {
}
void ComposeControls::initWebpageProcess() {
Expects(_history);
if (!_history) {
_preview = nullptr;
_header->previewUnregister();
return;
}
auto &lifetime = _wrap->lifetime();
_preview = std::make_unique<WebpageProcessor>(_history, _field);
_preview->paintRequests(
) | rpl::start_with_next(crl::guard(_header.get(), [=] {
_header->update();
}), lifetime);
}), _historyLifetime);
session().changes().peerUpdates(
Data::PeerUpdate::Flag::Rights
@ -2818,7 +2826,7 @@ void ComposeControls::initWebpageProcess() {
updateControlsGeometry(_wrap->size());
}
}
}, lifetime);
}, _historyLifetime);
_header->previewRequested(
_preview->titleChanges(),

View File

@ -389,10 +389,11 @@ private:
std::unique_ptr<WebpageProcessor> _preview;
rpl::lifetime _uploaderSubscriptions;
Fn<void()> _raiseEmojiSuggestions;
rpl::lifetime _historyLifetime;
rpl::lifetime _uploaderSubscriptions;
};
} // namespace HistoryView

View File

@ -0,0 +1,155 @@
/*
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 "media/stories/media_stories_controller.h"
#include "data/data_stories.h"
#include "media/stories/media_stories_delegate.h"
#include "media/stories/media_stories_header.h"
#include "media/stories/media_stories_slider.h"
#include "media/stories/media_stories_reply.h"
#include "ui/rp_widget.h"
#include "styles/style_media_view.h"
#include "styles/style_widgets.h"
#include "styles/style_boxes.h" // UserpicButton
namespace Media::Stories {
Controller::Controller(not_null<Delegate*> delegate)
: _delegate(delegate)
, _wrap(_delegate->storiesWrap())
, _header(std::make_unique<Header>(this))
, _slider(std::make_unique<Slider>(this))
, _replyArea(std::make_unique<ReplyArea>(this)) {
initLayout();
}
void Controller::initLayout() {
const auto headerHeight = st::storiesHeaderMargin.top()
+ st::storiesHeaderPhoto.photoSize
+ st::storiesHeaderMargin.bottom();
const auto sliderHeight = st::storiesSliderMargin.top()
+ st::storiesSlider.width
+ st::storiesSliderMargin.bottom();
const auto outsideHeaderHeight = headerHeight + sliderHeight;
const auto fieldMinHeight = st::storiesFieldMargin.top()
+ st::storiesAttach.height
+ st::storiesFieldMargin.bottom();
const auto minHeightForOutsideHeader = st::storiesMaxSize.height()
+ outsideHeaderHeight
+ fieldMinHeight;
_layout = _wrap->sizeValue(
) | rpl::map([=](QSize size) {
size = QSize(
std::max(size.width(), st::mediaviewMinWidth),
std::max(size.height(), st::mediaviewMinHeight));
auto layout = Layout();
layout.headerLayout = (size.height() >= minHeightForOutsideHeader)
? HeaderLayout::Outside
: HeaderLayout::Normal;
const auto topSkip = (layout.headerLayout == HeaderLayout::Outside)
? outsideHeaderHeight
: st::storiesFieldMargin.bottom();
const auto bottomSkip = fieldMinHeight;
const auto maxWidth = size.width() - 2 * st::storiesSideSkip;
const auto availableHeight = size.height() - topSkip - bottomSkip;
const auto maxContentHeight = std::min(
availableHeight,
st::storiesMaxSize.height());
const auto nowWidth = maxContentHeight * st::storiesMaxSize.width()
/ st::storiesMaxSize.height();
const auto contentWidth = std::min(nowWidth, maxWidth);
const auto contentHeight = (contentWidth < nowWidth)
? (contentWidth * st::storiesMaxSize.height()
/ st::storiesMaxSize.width())
: maxContentHeight;
const auto addedTopSkip = (availableHeight - contentHeight) / 2;
layout.content = QRect(
(size.width() - contentWidth) / 2,
addedTopSkip + topSkip,
contentWidth,
contentHeight);
if (layout.headerLayout == HeaderLayout::Outside) {
layout.header = QRect(
layout.content.topLeft() - QPoint(0, outsideHeaderHeight),
QSize(contentWidth, outsideHeaderHeight));
layout.slider = QRect(
layout.header.topLeft() + QPoint(0, headerHeight),
QSize(contentWidth, sliderHeight));
} else {
layout.slider = QRect(
layout.content.topLeft(),
QSize(contentWidth, sliderHeight));
layout.header = QRect(
layout.slider.topLeft() + QPoint(0, sliderHeight),
QSize(contentWidth, headerHeight));
}
layout.controlsWidth = std::max(
layout.content.width(),
st::storiesControlsMinWidth);
layout.controlsBottomPosition = QPoint(
(size.width() - layout.controlsWidth) / 2,
(layout.content.y()
+ layout.content.height()
+ fieldMinHeight
- st::storiesFieldMargin.bottom()));
layout.autocompleteRect = QRect(
layout.controlsBottomPosition.x(),
0,
layout.controlsWidth,
layout.controlsBottomPosition.y());
return layout;
});
}
not_null<Ui::RpWidget*> Controller::wrap() const {
return _wrap;
}
Layout Controller::layout() const {
Expects(_layout.current().has_value());
return *_layout.current();
}
rpl::producer<Layout> Controller::layoutValue() const {
return _layout.value() | rpl::filter_optional();
}
std::shared_ptr<ChatHelpers::Show> Controller::uiShow() const {
return _delegate->storiesShow();
}
auto Controller::stickerOrEmojiChosen() const
->rpl::producer<ChatHelpers::FileChosen> {
return _delegate->storiesStickerOrEmojiChosen();
}
void Controller::show(const Data::StoriesList &list, int index) {
Expects(index < list.items.size());
const auto &item = list.items[index];
const auto id = ShownId{
.user = list.user,
.id = item.id,
};
if (_shown == id) {
return;
}
_shown = id;
_header->show({ .user = list.user, .date = item.date });
_slider->show({ .index = index, .total = int(list.items.size()) });
_replyArea->show({ .user = list.user });
}
} // namespace Media::Stories

View File

@ -0,0 +1,91 @@
/*
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
*/
#pragma once
namespace ChatHelpers {
class Show;
struct FileChosen;
} // namespace ChatHelpers
namespace Data {
struct StoriesList;
} // namespace Data
namespace Ui {
class RpWidget;
} // namespace Ui
namespace Media::Stories {
class Header;
class Slider;
class ReplyArea;
class Delegate;
struct ShownId {
UserData *user = nullptr;
StoryId id = 0;
explicit operator bool() const {
return user != nullptr && id != 0;
}
friend inline auto operator<=>(ShownId, ShownId) = default;
friend inline bool operator==(ShownId, ShownId) = default;
};
enum class HeaderLayout {
Normal,
Outside,
};
struct Layout {
QRect content;
QRect header;
QRect slider;
int controlsWidth = 0;
QPoint controlsBottomPosition;
QRect autocompleteRect;
HeaderLayout headerLayout = HeaderLayout::Normal;
friend inline auto operator<=>(Layout, Layout) = default;
friend inline bool operator==(Layout, Layout) = default;
};
class Controller final {
public:
explicit Controller(not_null<Delegate*> delegate);
[[nodiscard]] not_null<Ui::RpWidget*> wrap() const;
[[nodiscard]] Layout layout() const;
[[nodiscard]] rpl::producer<Layout> layoutValue() const;
[[nodiscard]] std::shared_ptr<ChatHelpers::Show> uiShow() const;
[[nodiscard]] auto stickerOrEmojiChosen() const
-> rpl::producer<ChatHelpers::FileChosen>;
void show(const Data::StoriesList &list, int index);
private:
void initLayout();
const not_null<Delegate*> _delegate;
rpl::variable<std::optional<Layout>> _layout;
const not_null<Ui::RpWidget*> _wrap;
const std::unique_ptr<Header> _header;
const std::unique_ptr<Slider> _slider;
const std::unique_ptr<ReplyArea> _replyArea;
ShownId _shown;
rpl::lifetime _lifetime;
};
} // namespace Media::Stories

View File

@ -9,18 +9,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h"
#include "data/data_user.h"
#include "media/stories/media_stories_delegate.h"
#include "media/stories/media_stories_controller.h"
#include "ui/controls/userpic_button.h"
#include "ui/text/format_values.h"
#include "ui/widgets/labels.h"
#include "ui/painter.h"
#include "ui/rp_widget.h"
#include "styles/style_boxes.h" // defaultUserpicButton.
#include "styles/style_media_view.h"
namespace Media::Stories {
Header::Header(not_null<Delegate*> delegate)
: _delegate(delegate) {
Header::Header(not_null<Controller*> controller)
: _controller(controller) {
}
Header::~Header() {
@ -34,32 +34,37 @@ void Header::show(HeaderData data) {
_data = data;
if (userChanged) {
_date = nullptr;
const auto parent = _delegate->storiesWrap();
const auto parent = _controller->wrap();
auto widget = std::make_unique<Ui::RpWidget>(parent);
const auto raw = widget.get();
parent->sizeValue() | rpl::start_with_next([=](QSize size) {
raw->setGeometry(50, 50, 600, 100);
}, raw->lifetime());
raw->setAttribute(Qt::WA_TransparentForMouseEvents);
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
raw,
data.user,
st::defaultUserpicButton);
userpic->move(0, 0);
st::storiesHeaderPhoto);
userpic->show();
userpic->move(
st::storiesHeaderMargin.left(),
st::storiesHeaderMargin.top());
const auto name = Ui::CreateChild<Ui::FlatLabel>(
raw,
data.user->firstName,
st::defaultFlatLabel);
name->move(100, 0);
st::storiesHeaderName);
name->move(st::storiesHeaderNamePosition);
raw->show();
_widget = std::move(widget);
_controller->layoutValue(
) | rpl::start_with_next([=](const Layout &layout) {
raw->setGeometry(layout.header);
}, raw->lifetime());
}
_date = std::make_unique<Ui::FlatLabel>(
_widget.get(),
Ui::FormatDateTime(base::unixtime::parse(data.date)),
st::defaultFlatLabel);
_date->move(100, 50);
st::storiesHeaderDate);
_date->show();
_date->move(st::storiesHeaderDatePosition);
}
} // namespace Media::Stories

View File

@ -16,24 +16,26 @@ class FlatLabel;
namespace Media::Stories {
class Delegate;
class Controller;
struct HeaderData {
not_null<UserData*> user;
TimeId date = 0;
friend inline auto operator<=>(HeaderData, HeaderData) = default;
friend inline bool operator==(HeaderData, HeaderData) = default;
};
class Header final {
public:
explicit Header(not_null<Delegate*> delegate);
explicit Header(not_null<Controller*> controller);
~Header();
void show(HeaderData data);
private:
const not_null<Delegate*> _delegate;
const not_null<Controller*> _controller;
std::unique_ptr<Ui::RpWidget> _widget;
std::unique_ptr<Ui::FlatLabel> _date;
std::optional<HeaderData> _data;

View File

@ -7,42 +7,156 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "media/stories/media_stories_reply.h"
#include "base/call_delayed.h"
#include "chat_helpers/compose/compose_show.h"
#include "chat_helpers/tabbed_selector.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "history/view/controls/compose_controls_common.h"
#include "history/view/controls/history_view_compose_controls.h"
#include "media/stories/media_stories_delegate.h"
#include "inline_bots/inline_bot_result.h"
#include "media/stories/media_stories_controller.h"
#include "menu/menu_send.h"
#include "styles/style_media_view.h"
namespace Media::Stories {
ReplyArea::ReplyArea(not_null<Delegate*> delegate)
: _delegate(delegate)
ReplyArea::ReplyArea(not_null<Controller*> controller)
: _controller(controller)
, _controls(std::make_unique<HistoryView::ComposeControls>(
_delegate->storiesWrap(),
_controller->wrap(),
HistoryView::ComposeControlsDescriptor{
.show = _delegate->storiesShow(),
.show = _controller->uiShow(),
.unavailableEmojiPasted = [=](not_null<DocumentData*> emoji) {
showPremiumToast(emoji);
},
.mode = HistoryView::ComposeControlsMode::Normal,
.sendMenuType = SendMenu::Type::SilentOnly,
.stickerOrEmojiChosen = _delegate->storiesStickerOrEmojiChosen(),
.stickerOrEmojiChosen = _controller->stickerOrEmojiChosen(),
}
)) {
_delegate->storiesWrap()->sizeValue(
) | rpl::start_with_next([=](QSize size) {
_controls->resizeToWidth(size.width() - 200);
_controls->move(100, size.height() - _controls->heightCurrent() - 20);
_controls->setAutocompleteBoundingRect({ QPoint() ,size });
}, _lifetime);
_controls->show();
_controls->showFinished();
initGeometry();
initActions();
}
ReplyArea::~ReplyArea() {
}
void ReplyArea::initGeometry() {
_controller->layoutValue(
) | rpl::start_with_next([=](const Layout &layout) {
_controls->resizeToWidth(layout.content.width());
const auto position = layout.controlsBottomPosition
- QPoint(0, _controls->heightCurrent());
_controls->move(position.x(), position.y());
_controls->setAutocompleteBoundingRect(layout.autocompleteRect);
}, _lifetime);
}
void ReplyArea::send(Api::SendOptions options) {
// #TODO stories
}
void ReplyArea::sendVoice(VoiceToSend &&data) {
// #TODO stories
}
void ReplyArea::chooseAttach(std::optional<bool> overrideCompress) {
// #TODO stories
}
void ReplyArea::initActions() {
_controls->cancelRequests(
) | rpl::start_with_next([=] {
// #TODO stories
}, _lifetime);
_controls->sendRequests(
) | rpl::start_with_next([=](Api::SendOptions options) {
send(options);
}, _lifetime);
_controls->sendVoiceRequests(
) | rpl::start_with_next([=](VoiceToSend &&data) {
sendVoice(std::move(data));
}, _lifetime);
_controls->attachRequests(
) | rpl::filter([=] {
return !_choosingAttach;
}) | rpl::start_with_next([=](std::optional<bool> overrideCompress) {
_choosingAttach = true;
base::call_delayed(
st::storiesAttach.ripple.hideDuration,
this,
[=] { chooseAttach(overrideCompress); });
}, _lifetime);
_controls->fileChosen(
) | rpl::start_with_next([=](ChatHelpers::FileChosen data) {
_controller->uiShow()->hideLayer();
//controller()->sendingAnimation().appendSending(
// data.messageSendingFrom);
//const auto localId = data.messageSendingFrom.localId;
//sendExistingDocument(data.document, data.options, localId);
}, _lifetime);
_controls->photoChosen(
) | rpl::start_with_next([=](ChatHelpers::PhotoChosen chosen) {
//sendExistingPhoto(chosen.photo, chosen.options);
}, _lifetime);
_controls->inlineResultChosen(
) | rpl::start_with_next([=](ChatHelpers::InlineChosen chosen) {
//controller()->sendingAnimation().appendSending(
// chosen.messageSendingFrom);
//const auto localId = chosen.messageSendingFrom.localId;
//sendInlineResult(chosen.result, chosen.bot, chosen.options, localId);
}, _lifetime);
_controls->setMimeDataHook([=](
not_null<const QMimeData*> data,
Ui::InputField::MimeAction action) {
if (action == Ui::InputField::MimeAction::Check) {
return false;// checkSendingFiles(data);
} else if (action == Ui::InputField::MimeAction::Insert) {
return false;/* confirmSendingFiles(
data,
std::nullopt,
Core::ReadMimeText(data));*/
}
Unexpected("action in MimeData hook.");
});
_controls->lockShowStarts(
) | rpl::start_with_next([=] {
}, _lifetime);
_controls->show();
_controls->finishAnimating();
_controls->showFinished();
}
void ReplyArea::show(ReplyAreaData data) {
if (_data == data) {
return;
}
const auto userChanged = (_data.user != data.user);
_data = data;
if (!userChanged) {
if (_data.user) {
_controls->clear();
}
return;
}
const auto user = data.user;
const auto history = user ? user->owner().history(user).get() : nullptr;
_controls->setHistory({
.history = history,
});
_controls->clear();
}
void ReplyArea::showPremiumToast(not_null<DocumentData*> emoji) {
// #TODO stories
}

View File

@ -7,31 +7,57 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/weak_ptr.h"
namespace Api {
struct SendOptions;
} // namespace Api
namespace HistoryView {
class ComposeControls;
} // namespace HistoryView
namespace HistoryView::Controls {
struct VoiceToSend;
} // namespace HistoryView::Controls
namespace Media::Stories {
class Delegate;
class Controller;
struct ReplyAreaData {
not_null<UserData*> user;
UserData *user = nullptr;
StoryId id = 0;
friend inline auto operator<=>(ReplyAreaData, ReplyAreaData) = default;
friend inline bool operator==(ReplyAreaData, ReplyAreaData) = default;
};
class ReplyArea final {
class ReplyArea final : public base::has_weak_ptr {
public:
explicit ReplyArea(not_null<Delegate*> delegate);
explicit ReplyArea(not_null<Controller*> controller);
~ReplyArea();
void show(ReplyAreaData data);
private:
using VoiceToSend = HistoryView::Controls::VoiceToSend;
void initGeometry();
void initActions();
void send(Api::SendOptions options);
void sendVoice(VoiceToSend &&data);
void chooseAttach(std::optional<bool> overrideSendImagesAsPhotos);
void showPremiumToast(not_null<DocumentData*> emoji);
const not_null<Delegate*> _delegate;
const not_null<Controller*> _controller;
const std::unique_ptr<HistoryView::ComposeControls> _controls;
ReplyAreaData _data;
bool _choosingAttach = false;
rpl::lifetime _lifetime;
};

View File

@ -7,12 +7,68 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "media/stories/media_stories_slider.h"
#include "media/stories/media_stories_controller.h"
#include "ui/painter.h"
#include "ui/rp_widget.h"
#include "styles/style_widgets.h"
#include "styles/style_media_view.h"
namespace Media::Stories {
Slider::Slider() {
Slider::Slider(not_null<Controller*> controller)
: _controller(controller) {
}
Slider::~Slider() {
}
void Slider::show(SliderData data) {
if (_data == data) {
return;
}
_data = data;
const auto parent = _controller->wrap();
auto widget = std::make_unique<Ui::RpWidget>(parent);
const auto raw = widget.get();
raw->paintRequest(
) | rpl::filter([=] {
return (raw->width() >= st::storiesSlider.width);
}) | rpl::start_with_next([=](QRect clip) {
auto clipf = QRectF(clip);
auto p = QPainter(raw);
const auto single = st::storiesSlider.width;
const auto skip = st::storiesSliderSkip;
// width() == single * max + skip * (max - 1);
// max == (width() + skip) / (single + skip);
const auto max = (raw->width() + skip) / (single + skip);
Assert(max > 0);
const auto count = std::clamp(_data.total, 1, max);
const auto index = std::clamp(data.index, 0, count - 1);
const auto radius = st::storiesSlider.width / 2.;
const auto width = (raw->width() - (count - 1) * skip)
/ float64(count);
auto hq = PainterHighQualityEnabler(p);
auto left = 0.;
for (auto i = 0; i != count; ++i) {
const auto rect = QRectF(left, 0, width, single);
p.setBrush((i == index) // #TODO stories
? st::mediaviewPipControlsFgOver
: st::mediaviewPipPlaybackInactive);
p.setPen(Qt::NoPen);
p.drawRoundedRect(rect, radius, radius);
left += width + skip;
}
}, raw->lifetime());
raw->show();
_widget = std::move(widget);
_controller->layoutValue(
) | rpl::start_with_next([=](const Layout &layout) {
raw->setGeometry(layout.slider - st::storiesSliderMargin);
}, raw->lifetime());
}
} // namespace Media::Stories

View File

@ -7,13 +7,35 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace Ui {
class RpWidget;
} // namespace Ui
namespace Media::Stories {
class Controller;
struct SliderData {
int index = 0;
int total = 0;
friend inline auto operator<=>(SliderData, SliderData) = default;
friend inline bool operator==(SliderData, SliderData) = default;
};
class Slider final {
public:
Slider();
explicit Slider(not_null<Controller*> controller);
~Slider();
void show(SliderData data);
private:
const not_null<Controller*> _controller;
std::unique_ptr<Ui::RpWidget> _widget;
SliderData _data;
};

View File

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "media/stories/media_stories_view.h"
#include "media/stories/media_stories_controller.h"
#include "media/stories/media_stories_delegate.h"
#include "media/stories/media_stories_header.h"
#include "media/stories/media_stories_slider.h"
@ -15,23 +16,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Media::Stories {
View::View(not_null<Delegate*> delegate)
: _delegate(delegate)
, _wrap(_delegate->storiesWrap())
, _header(std::make_unique<Header>(_delegate))
, _slider(std::make_unique<Slider>())
, _replyArea(std::make_unique<ReplyArea>(_delegate)) {
: _controller(std::make_unique<Controller>(delegate)) {
}
View::~View() = default;
void View::show(const Data::StoriesList &list, int index) {
Expects(index < list.items.size());
_controller->show(list, index);
}
const auto &item = list.items[index];
_header->show({
.user = list.user,
.date = item.date,
});
QRect View::contentGeometry() const {
return _controller->layout().content;
}
} // namespace Media::Stories

View File

@ -7,18 +7,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "data/data_stories.h"
namespace Ui {
class RpWidget;
} // namespace Ui
namespace Data {
struct StoriesList;
} // namespace Data
namespace Media::Stories {
class Header;
class Slider;
class ReplyArea;
class Delegate;
class Controller;
class View final {
public:
@ -26,14 +22,10 @@ public:
~View();
void show(const Data::StoriesList &list, int index);
[[nodiscard]] QRect contentGeometry() const;
private:
const not_null<Delegate*> _delegate;
const not_null<Ui::RpWidget*> _wrap;
std::unique_ptr<Header> _header;
std::unique_ptr<Slider> _slider;
std::unique_ptr<ReplyArea> _replyArea;
const std::unique_ptr<Controller> _controller;
};

View File

@ -10,6 +10,7 @@ using "ui/basic.style";
using "ui/widgets/widgets.style";
using "ui/menu_icons.style";
using "media/player/media_player.style";
using "boxes/boxes.style";
mediaviewOverDuration: 150;
@ -403,3 +404,41 @@ pipVolumeIcon2: icon {{ "player/player_volume_on", mediaviewPipControlsFg }};
pipVolumeIcon2Over: icon {{ "player/player_volume_on", mediaviewPipControlsFgOver }};
speedSliderDividerSize: size(2px, 8px);
storiesMaxSize: size(405px, 720px);
storiesSlider: MediaSlider(mediaviewPlayback) {
width: 2px;
seekSize: size(2px, 2px);
}
storiesSliderMargin: margins(8px, 7px, 8px, 10px);
storiesSliderSkip: 4px;
storiesHeaderMargin: margins(12px, 3px, 12px, 8px);
storiesHeaderPhoto: UserpicButton(defaultUserpicButton) {
size: size(28px, 28px);
photoSize: 28px;
}
storiesHeaderName: FlatLabel(defaultFlatLabel) {
textFg: mediaviewPipControlsFgOver; // #TODO stories
style: semiboldTextStyle;
}
storiesHeaderNamePosition: point(50px, 2px);
storiesHeaderDate: FlatLabel(defaultFlatLabel) {
textFg: mediaviewPipControlsFg; // #TODO stories
}
storiesHeaderDatePosition: point(50px, 19px);
storiesControlsMinWidth: 200px;
storiesFieldMargin: margins(0px, 14px, 0px, 16px);
storiesAttach: IconButton(defaultIconButton) {
width: 44px;
height: 46px;
icon: icon {{ "chat/input_attach", historyComposeIconFg }};
iconOver: icon {{ "chat/input_attach", historyComposeIconFgOver }};
rippleAreaPosition: point(2px, 3px);
rippleAreaSize: 40px;
ripple: RippleAnimation(defaultRippleAnimation) {
color: windowBgOver;
}
}
storiesSideSkip: 145px;

View File

@ -1543,6 +1543,14 @@ void OverlayWidget::recountSkipTop() {
}
void OverlayWidget::resizeContentByScreenSize() {
if (_stories) {
const auto content = _stories->contentGeometry();
_x = content.x();
_y = content.y();
_w = content.width();
_h = content.height();
return;
}
recountSkipTop();
const auto availableWidth = width();
const auto countZoomFor = [&](int outerw, int outerh) {