Display full caption if it doesn't fit.

This commit is contained in:
John Preston 2023-05-12 21:10:00 +04:00
parent 0331955ce7
commit a745c9ff75
10 changed files with 246 additions and 15 deletions

View file

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

View file

@ -0,0 +1,81 @@
/*
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_caption_full_view.h"
#include "core/ui_integration.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/labels.h"
#include "styles/style_media_view.h"
namespace Media::Stories {
CaptionFullView::CaptionFullView(
not_null<Ui::RpWidget*> parent,
not_null<Main::Session*> session,
const TextWithEntities &text,
Fn<void()> close)
: RpWidget(parent)
, _scroll(std::make_unique<Ui::ScrollArea>((RpWidget*)this))
, _text(_scroll->setOwnedWidget(
object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>(
_scroll.get(),
object_ptr<Ui::FlatLabel>(_scroll.get(), st::storiesCaptionFull),
st::mediaviewCaptionPadding))->entity())
, _close(std::move(close))
, _background(st::storiesRadius, st::mediaviewCaptionBg) {
_text->setMarkedText(text, Core::MarkedTextContext{
.session = session,
.customEmojiRepaint = [=] { _text->update(); },
});
parent->sizeValue() | rpl::start_with_next([=](QSize size) {
setGeometry(QRect(QPoint(), size));
}, lifetime());
show();
setFocus();
}
CaptionFullView::~CaptionFullView() = default;
void CaptionFullView::paintEvent(QPaintEvent *e) {
auto p = QPainter(this);
_background.paint(p, _scroll->geometry());
_background.paint(p, _scroll->geometry());
}
void CaptionFullView::resizeEvent(QResizeEvent *e) {
const auto wanted = _text->naturalWidth();
const auto padding = st::mediaviewCaptionPadding;
const auto margin = st::mediaviewCaptionMargin * 2;
const auto available = (rect() - padding).width()
- (margin.width() * 2);
const auto use = std::min(wanted, available);
_text->resizeToWidth(use);
const auto fullw = use + padding.left() + padding.right();
const auto fullh = std::min(
_text->height() + padding.top() + padding.bottom(),
height() - (margin.height() * 2));
const auto left = (width() - fullw) / 2;
const auto top = (height() - fullh) / 2;
_scroll->setGeometry(left, top, fullw, fullh);
}
void CaptionFullView::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Escape) {
_close();
}
}
void CaptionFullView::mousePressEvent(QMouseEvent *e) {
if (e->button() == Qt::LeftButton) {
_close();
}
}
} // namespace Media::Stories

View file

@ -0,0 +1,46 @@
/*
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
#include "ui/rp_widget.h"
#include "ui/round_rect.h"
namespace Main {
class Session;
} // namespace Main
namespace Ui {
class FlatLabel;
class ScrollArea;
} // namespace Ui
namespace Media::Stories {
class CaptionFullView final : private Ui::RpWidget {
public:
CaptionFullView(
not_null<Ui::RpWidget*> parent,
not_null<Main::Session*> session,
const TextWithEntities &text,
Fn<void()> close);
~CaptionFullView();
private:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
std::unique_ptr<Ui::ScrollArea> _scroll;
const not_null<Ui::FlatLabel*> _text;
Fn<void()> _close;
Ui::RoundRect _background;
};
} // namespace Media::Stories

View file

@ -9,8 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer.h" #include "base/timer.h"
#include "base/power_save_blocker.h" #include "base/power_save_blocker.h"
#include "chat_helpers/compose/compose_show.h"
#include "data/data_stories.h" #include "data/data_stories.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "media/stories/media_stories_caption_full_view.h"
#include "media/stories/media_stories_delegate.h" #include "media/stories/media_stories_delegate.h"
#include "media/stories/media_stories_header.h" #include "media/stories/media_stories_header.h"
#include "media/stories/media_stories_sibling.h" #include "media/stories/media_stories_sibling.h"
@ -115,6 +117,9 @@ Controller::Controller(not_null<Delegate*> delegate)
_replyArea->focusedValue( _replyArea->focusedValue(
) | rpl::start_with_next([=](bool focused) { ) | rpl::start_with_next([=](bool focused) {
if (focused) {
_captionFullView = nullptr;
}
_contentFaded = focused; _contentFaded = focused;
_contentFadeAnimation.start( _contentFadeAnimation.start(
[=] { _delegate->storiesRepaint(); }, [=] { _delegate->storiesRepaint(); },
@ -292,6 +297,18 @@ TextWithEntities Controller::captionText() const {
return _captionText; return _captionText;
} }
void Controller::showFullCaption() {
if (_captionText.empty()) {
return;
}
togglePaused(true);
_captionFullView = std::make_unique<CaptionFullView>(
wrap(),
&_delegate->storiesShow()->session(),
_captionText,
[=] { togglePaused(false); });
}
std::shared_ptr<ChatHelpers::Show> Controller::uiShow() const { std::shared_ptr<ChatHelpers::Show> Controller::uiShow() const {
return _delegate->storiesShow(); return _delegate->storiesShow();
} }
@ -334,10 +351,15 @@ void Controller::show(
} }
_shown = id; _shown = id;
_captionText = item.caption; _captionText = item.caption;
_captionFullView = nullptr;
_header->show({ .user = list.user, .date = item.date }); _header->show({ .user = list.user, .date = item.date });
_slider->show({ .index = _index, .total = list.total }); _slider->show({ .index = _index, .total = list.total });
_replyArea->show({ .user = list.user }); _replyArea->show({ .user = list.user });
if (_contentFaded) {
togglePaused(true);
}
} }
void Controller::showSiblings( void Controller::showSiblings(
@ -447,6 +469,9 @@ bool Controller::paused() const {
} }
void Controller::togglePaused(bool paused) { void Controller::togglePaused(bool paused) {
if (!paused) {
_captionFullView = nullptr;
}
if (_photoPlayback) { if (_photoPlayback) {
_photoPlayback->togglePaused(paused); _photoPlayback->togglePaused(paused);
} else { } else {

View file

@ -41,6 +41,7 @@ class Delegate;
struct SiblingView; struct SiblingView;
enum class SiblingType; enum class SiblingType;
struct ContentLayout; struct ContentLayout;
class CaptionFullView;
enum class HeaderLayout { enum class HeaderLayout {
Normal, Normal,
@ -78,6 +79,7 @@ public:
[[nodiscard]] rpl::producer<Layout> layoutValue() const; [[nodiscard]] rpl::producer<Layout> layoutValue() const;
[[nodiscard]] ContentLayout contentLayout() const; [[nodiscard]] ContentLayout contentLayout() const;
[[nodiscard]] TextWithEntities captionText() const; [[nodiscard]] TextWithEntities captionText() const;
void showFullCaption();
[[nodiscard]] std::shared_ptr<ChatHelpers::Show> uiShow() const; [[nodiscard]] std::shared_ptr<ChatHelpers::Show> uiShow() const;
[[nodiscard]] auto stickerOrEmojiChosen() const [[nodiscard]] auto stickerOrEmojiChosen() const
@ -130,6 +132,7 @@ private:
const std::unique_ptr<Slider> _slider; const std::unique_ptr<Slider> _slider;
const std::unique_ptr<ReplyArea> _replyArea; const std::unique_ptr<ReplyArea> _replyArea;
std::unique_ptr<PhotoPlayback> _photoPlayback; std::unique_ptr<PhotoPlayback> _photoPlayback;
std::unique_ptr<CaptionFullView> _captionFullView;
Ui::Animations::Simple _contentFadeAnimation; Ui::Animations::Simple _contentFadeAnimation;
bool _contentFaded = false; bool _contentFaded = false;

View file

@ -83,6 +83,10 @@ TextWithEntities View::captionText() const {
return _controller->captionText(); return _controller->captionText();
} }
void View::showFullCaption() {
_controller->showFullCaption();
}
rpl::lifetime &View::lifetime() { rpl::lifetime &View::lifetime() {
return _controller->lifetime(); return _controller->lifetime();
} }

View file

@ -63,6 +63,7 @@ public:
[[nodiscard]] ContentLayout contentLayout() const; [[nodiscard]] ContentLayout contentLayout() const;
[[nodiscard]] SiblingView sibling(SiblingType type) const; [[nodiscard]] SiblingView sibling(SiblingType type) const;
[[nodiscard]] TextWithEntities captionText() const; [[nodiscard]] TextWithEntities captionText() const;
void showFullCaption();
void updatePlayback(const Player::TrackState &state); void updatePlayback(const Player::TrackState &state);

View file

@ -448,3 +448,8 @@ storiesAttach: IconButton(defaultIconButton) {
} }
} }
storiesSideSkip: 145px; storiesSideSkip: 145px;
storiesCaptionFull: FlatLabel(defaultFlatLabel) {
style: mediaviewCaptionStyle;
textFg: mediaviewCaptionFg;
minWidth: 360px;
}

View file

@ -1217,14 +1217,32 @@ void OverlayWidget::refreshCaptionGeometry() {
- st::mediaviewCaptionPadding.left() - st::mediaviewCaptionPadding.left()
- st::mediaviewCaptionPadding.right()), - st::mediaviewCaptionPadding.right()),
_caption.maxWidth()); _caption.maxWidth());
const auto maxHeight = (_stories ? (_h / 3) : (height() / 4)) const auto maxExpandedOuterHeight = (_stories
? (_h - st::storiesShadowTop.height())
: height());
const auto maxCollapsedOuterHeight = !_stories
? (height() / 4)
: (_h / 3);
const auto maxExpandedHeight = maxExpandedOuterHeight
- st::mediaviewCaptionPadding.top() - st::mediaviewCaptionPadding.top()
- st::mediaviewCaptionPadding.bottom() - st::mediaviewCaptionPadding.bottom();
- (_stories ? 0 : (2 * st::mediaviewCaptionMargin.height())); const auto maxCollapsedHeight = maxCollapsedOuterHeight
- st::mediaviewCaptionPadding.top()
- st::mediaviewCaptionPadding.bottom();
const auto lineHeight = st::mediaviewCaptionStyle.font->height; const auto lineHeight = st::mediaviewCaptionStyle.font->height;
const auto wantedHeight = _caption.countHeight(captionWidth);
const auto maxHeight = _captionExpanded
? maxExpandedHeight
: maxCollapsedHeight;
const auto captionHeight = std::min( const auto captionHeight = std::min(
_caption.countHeight(captionWidth), wantedHeight,
(maxHeight / lineHeight) * lineHeight); (maxHeight / lineHeight) * lineHeight);
_captionFitsIfExpanded = _stories
&& (wantedHeight <= maxExpandedHeight);
_captionShownFull = (wantedHeight <= maxCollapsedHeight);
if (_captionShownFull) {
_captionExpanded = false;
}
_captionRect = QRect( _captionRect = QRect(
(width() - captionWidth) / 2, (width() - captionWidth) / 2,
(captionBottom (captionBottom
@ -3000,6 +3018,7 @@ void OverlayWidget::show(OpenRequest request) {
_streamingStartPaused = false; _streamingStartPaused = false;
displayDocument( displayDocument(
document, document,
anim::activation::normal,
request.cloudTheme() request.cloudTheme()
? *request.cloudTheme() ? *request.cloudTheme()
: Data::CloudTheme(), : Data::CloudTheme(),
@ -3014,9 +3033,11 @@ void OverlayWidget::show(OpenRequest request) {
} }
} }
void OverlayWidget::displayPhoto(not_null<PhotoData*> photo) { void OverlayWidget::displayPhoto(
not_null<PhotoData*> photo,
anim::activation activation) {
if (photo->isNull()) { if (photo->isNull()) {
displayDocument(nullptr); displayDocument(nullptr, activation);
return; return;
} }
_touchbarDisplay.fire(TouchBarItemType::Photo); _touchbarDisplay.fire(TouchBarItemType::Photo);
@ -3055,7 +3076,7 @@ void OverlayWidget::displayPhoto(not_null<PhotoData*> photo) {
} }
contentSizeChanged(); contentSizeChanged();
refreshFromLabel(); refreshFromLabel();
displayFinished(); displayFinished(activation);
} }
void OverlayWidget::destroyThemePreview() { void OverlayWidget::destroyThemePreview() {
@ -3071,15 +3092,16 @@ void OverlayWidget::redisplayContent() {
if (isHidden() || !_session) { if (isHidden() || !_session) {
return; return;
} else if (_photo) { } else if (_photo) {
displayPhoto(_photo); displayPhoto(_photo, anim::activation::background);
} else { } else {
displayDocument(_document); displayDocument(_document, anim::activation::background);
} }
} }
// Empty messages shown as docs: doc can be nullptr. // Empty messages shown as docs: doc can be nullptr.
void OverlayWidget::displayDocument( void OverlayWidget::displayDocument(
DocumentData *doc, DocumentData *doc,
anim::activation activation,
const Data::CloudTheme &cloud, const Data::CloudTheme &cloud,
const StartStreaming &startStreaming) { const StartStreaming &startStreaming) {
_fullScreenVideo = false; _fullScreenVideo = false;
@ -3209,7 +3231,7 @@ void OverlayWidget::displayDocument(
if (_showAsPip && _streamed && _streamed->controls) { if (_showAsPip && _streamed && _streamed->controls) {
switchToPip(); switchToPip();
} else { } else {
displayFinished(); displayFinished(activation);
} }
} }
@ -3236,7 +3258,8 @@ void OverlayWidget::updateThemePreviewGeometry() {
} }
} }
void OverlayWidget::displayFinished() { void OverlayWidget::displayFinished(anim::activation activation) {
_captionExpanded = _captionFitsIfExpanded = _captionShownFull = false;
updateControls(); updateControls();
if (isHidden()) { if (isHidden()) {
_helper->beforeShow(_fullscreen); _helper->beforeShow(_fullscreen);
@ -3247,6 +3270,8 @@ void OverlayWidget::displayFinished() {
//setAttribute(Qt::WA_DontShowOnScreen, false); //setAttribute(Qt::WA_DontShowOnScreen, false);
//Ui::Platform::UpdateOverlayed(_window); //Ui::Platform::UpdateOverlayed(_window);
showAndActivate(); showAndActivate();
} else if (activation == anim::activation::background) {
return;
} else if (isMinimized()) { } else if (isMinimized()) {
_helper->beforeShow(_fullscreen); _helper->beforeShow(_fullscreen);
showAndActivate(); showAndActivate();
@ -4015,10 +4040,11 @@ void OverlayWidget::storiesJumpTo(Data::FullStoryId id) {
clearStreaming(); clearStreaming();
_streamingStartPaused = false; _streamingStartPaused = false;
const auto &data = j->media.data; const auto &data = j->media.data;
const auto activation = anim::activation::background;
if (const auto photo = std::get_if<not_null<PhotoData*>>(&data)) { if (const auto photo = std::get_if<not_null<PhotoData*>>(&data)) {
displayPhoto(*photo); displayPhoto(*photo, activation);
} else { } else {
displayDocument(v::get<not_null<DocumentData*>>(data)); displayDocument(v::get<not_null<DocumentData*>>(data), activation);
} }
} }
@ -5302,6 +5328,9 @@ void OverlayWidget::updateOver(QPoint pos) {
} else if (_captionRect.contains(pos)) { } else if (_captionRect.contains(pos)) {
auto textState = _caption.getState(pos - _captionRect.topLeft(), _captionRect.width()); auto textState = _caption.getState(pos - _captionRect.topLeft(), _captionRect.width());
lnk = textState.link; lnk = textState.link;
if (_stories && !_captionShownFull && !lnk) {
lnk = ensureCaptionExpandLink();
}
lnkhost = this; lnkhost = this;
} else if (_groupThumbs && _groupThumbsRect.contains(pos)) { } else if (_groupThumbs && _groupThumbsRect.contains(pos)) {
const auto point = pos - QPoint(_groupThumbsLeft, _groupThumbsTop); const auto point = pos - QPoint(_groupThumbsLeft, _groupThumbsTop);
@ -5373,6 +5402,28 @@ void OverlayWidget::updateOver(QPoint pos) {
} }
} }
ClickHandlerPtr OverlayWidget::ensureCaptionExpandLink() {
if (!_captionExpandLink) {
const auto toggle = crl::guard(_widget, [=] {
if (!_stories) {
return;
} else if (_captionExpanded) {
_captionExpanded = false;
refreshCaptionGeometry();
update();
} else if (_captionFitsIfExpanded) {
_captionExpanded = true;
refreshCaptionGeometry();
update();
} else {
_stories->showFullCaption();
}
});
_captionExpandLink = std::make_shared<LambdaClickHandler>(toggle);
}
return _captionExpandLink;
}
void OverlayWidget::handleMouseRelease( void OverlayWidget::handleMouseRelease(
QPoint position, QPoint position,
Qt::MouseButton button) { Qt::MouseButton button) {

View file

@ -23,6 +23,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class History; class History;
namespace anim {
enum class activation : uchar;
} // namespace anim
namespace Data { namespace Data {
class PhotoMedia; class PhotoMedia;
class DocumentMedia; class DocumentMedia;
@ -148,6 +152,7 @@ private:
OverMore, OverMore,
OverIcon, OverIcon,
OverVideo, OverVideo,
OverCaption,
}; };
struct Entity { struct Entity {
std::variant< std::variant<
@ -356,12 +361,15 @@ private:
void resizeContentByScreenSize(); void resizeContentByScreenSize();
void recountSkipTop(); void recountSkipTop();
void displayPhoto(not_null<PhotoData*> photo); void displayPhoto(
not_null<PhotoData*> photo,
anim::activation activation = anim::activation::normal);
void displayDocument( void displayDocument(
DocumentData *document, DocumentData *document,
anim::activation activation = anim::activation::normal,
const Data::CloudTheme &cloud = Data::CloudTheme(), const Data::CloudTheme &cloud = Data::CloudTheme(),
const StartStreaming &startStreaming = StartStreaming()); const StartStreaming &startStreaming = StartStreaming());
void displayFinished(); void displayFinished(anim::activation activation);
void redisplayContent(); void redisplayContent();
void findCurrent(); void findCurrent();
@ -496,6 +504,7 @@ private:
[[nodiscard]] bool topShadowOnTheRight() const; [[nodiscard]] bool topShadowOnTheRight() const;
void applyHideWindowWorkaround(); void applyHideWindowWorkaround();
[[nodiscard]] ClickHandlerPtr ensureCaptionExpandLink();
Window::SessionController *findWindow(bool switchTo = true) const; Window::SessionController *findWindow(bool switchTo = true) const;
@ -558,6 +567,10 @@ private:
int _groupThumbsTop = 0; int _groupThumbsTop = 0;
Ui::Text::String _caption; Ui::Text::String _caption;
QRect _captionRect; QRect _captionRect;
ClickHandlerPtr _captionExpandLink;
bool _captionShownFull = false;
bool _captionFitsIfExpanded = false;
bool _captionExpanded = false;
int _width = 0; int _width = 0;
int _height = 0; int _height = 0;