mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-18 23:27:09 +02:00
Implement stories switching, photo "animation".
This commit is contained in:
parent
027bd89e5b
commit
7717de19ab
13 changed files with 581 additions and 131 deletions
|
@ -179,7 +179,6 @@ StoryId Stories::generate(
|
|||
32,
|
||||
32
|
||||
)) | rpl::start_with_next([&](SharedMediaResult &&result) {
|
||||
stories.total = result.count.value_or(1);
|
||||
if (!result.messageIds.contains(itemId)) {
|
||||
result.messageIds.emplace(itemId);
|
||||
}
|
||||
|
@ -214,6 +213,9 @@ StoryId Stories::generate(
|
|||
}
|
||||
}
|
||||
}
|
||||
stories.total = std::max(
|
||||
result.count.value_or(1),
|
||||
int(result.messageIds.size()));
|
||||
const auto i = ranges::find(_all, stories.user, &StoriesList::user);
|
||||
if (i != end(_all)) {
|
||||
*i = std::move(stories);
|
||||
|
|
|
@ -15,10 +15,13 @@ namespace Data {
|
|||
class Session;
|
||||
|
||||
struct StoryPrivacy {
|
||||
friend inline bool operator==(StoryPrivacy, StoryPrivacy) = default;
|
||||
};
|
||||
|
||||
struct StoryMedia {
|
||||
std::variant<not_null<PhotoData*>, not_null<DocumentData*>> data;
|
||||
|
||||
friend inline bool operator==(StoryMedia, StoryMedia) = default;
|
||||
};
|
||||
|
||||
struct StoryItem {
|
||||
|
@ -27,12 +30,27 @@ struct StoryItem {
|
|||
TextWithEntities caption;
|
||||
TimeId date = 0;
|
||||
StoryPrivacy privacy;
|
||||
|
||||
friend inline bool operator==(StoryItem, StoryItem) = default;
|
||||
};
|
||||
|
||||
struct StoriesList {
|
||||
not_null<UserData*> user;
|
||||
std::vector<StoryItem> items;
|
||||
int total = 0;
|
||||
|
||||
friend inline bool operator==(StoriesList, StoriesList) = default;
|
||||
};
|
||||
|
||||
struct FullStoryId {
|
||||
UserData *user = nullptr;
|
||||
StoryId id = 0;
|
||||
|
||||
explicit operator bool() const {
|
||||
return user != nullptr && id != 0;
|
||||
}
|
||||
friend inline auto operator<=>(FullStoryId, FullStoryId) = default;
|
||||
friend inline bool operator==(FullStoryId, FullStoryId) = default;
|
||||
};
|
||||
|
||||
class Stories final {
|
||||
|
|
|
@ -7,17 +7,95 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "media/stories/media_stories_controller.h"
|
||||
|
||||
#include "base/timer.h"
|
||||
#include "base/power_save_blocker.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 "media/audio/media_audio.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 {
|
||||
namespace {
|
||||
|
||||
constexpr auto kPhotoProgressInterval = crl::time(100);
|
||||
constexpr auto kPhotoDuration = 5 * crl::time(1000);
|
||||
|
||||
} // namespace
|
||||
|
||||
class Controller::PhotoPlayback final {
|
||||
public:
|
||||
explicit PhotoPlayback(not_null<Controller*> controller);
|
||||
|
||||
[[nodiscard]] bool paused() const;
|
||||
void togglePaused(bool paused);
|
||||
|
||||
private:
|
||||
void callback();
|
||||
|
||||
const not_null<Controller*> _controller;
|
||||
|
||||
base::Timer _timer;
|
||||
crl::time _started = 0;
|
||||
crl::time _paused = 0;
|
||||
|
||||
};
|
||||
|
||||
Controller::PhotoPlayback::PhotoPlayback(not_null<Controller*> controller)
|
||||
: _controller(controller)
|
||||
, _timer([=] { callback(); })
|
||||
, _started(crl::now())
|
||||
, _paused(_started) {
|
||||
}
|
||||
|
||||
bool Controller::PhotoPlayback::paused() const {
|
||||
return _paused != 0;
|
||||
}
|
||||
|
||||
void Controller::PhotoPlayback::togglePaused(bool paused) {
|
||||
if (!_paused == !paused) {
|
||||
return;
|
||||
} else if (paused) {
|
||||
const auto now = crl::now();
|
||||
if (now - _started >= kPhotoDuration) {
|
||||
return;
|
||||
}
|
||||
_paused = now;
|
||||
_timer.cancel();
|
||||
} else {
|
||||
_started += crl::now() - _paused;
|
||||
_paused = 0;
|
||||
_timer.callEach(kPhotoProgressInterval);
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
void Controller::PhotoPlayback::callback() {
|
||||
const auto now = crl::now();
|
||||
const auto elapsed = now - _started;
|
||||
const auto finished = (now - _started >= kPhotoDuration);
|
||||
if (finished) {
|
||||
_timer.cancel();
|
||||
}
|
||||
using State = Player::State;
|
||||
const auto state = finished
|
||||
? State::StoppedAtEnd
|
||||
: _paused
|
||||
? State::Paused
|
||||
: State::Playing;
|
||||
_controller->updatePhotoPlayback({
|
||||
.state = state,
|
||||
.position = elapsed,
|
||||
.receivedTill = kPhotoDuration,
|
||||
.length = kPhotoDuration,
|
||||
.frequency = 1000,
|
||||
});
|
||||
}
|
||||
|
||||
Controller::Controller(not_null<Delegate*> delegate)
|
||||
: _delegate(delegate)
|
||||
|
@ -28,12 +106,14 @@ Controller::Controller(not_null<Delegate*> delegate)
|
|||
initLayout();
|
||||
}
|
||||
|
||||
Controller::~Controller() = default;
|
||||
|
||||
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::storiesSliderWidth
|
||||
+ st::storiesSliderMargin.bottom();
|
||||
const auto outsideHeaderHeight = headerHeight + sliderHeight;
|
||||
const auto fieldMinHeight = st::storiesFieldMargin.top()
|
||||
|
@ -42,6 +122,7 @@ void Controller::initLayout() {
|
|||
const auto minHeightForOutsideHeader = st::storiesMaxSize.height()
|
||||
+ outsideHeaderHeight
|
||||
+ fieldMinHeight;
|
||||
|
||||
_layout = _wrap->sizeValue(
|
||||
) | rpl::map([=](QSize size) {
|
||||
size = QSize(
|
||||
|
@ -137,8 +218,19 @@ void Controller::show(const Data::StoriesList &list, int index) {
|
|||
Expects(index < list.items.size());
|
||||
|
||||
const auto &item = list.items[index];
|
||||
const auto guard = gsl::finally([&] {
|
||||
if (v::is<not_null<PhotoData*>>(item.media.data)) {
|
||||
_photoPlayback = std::make_unique<PhotoPlayback>(this);
|
||||
} else {
|
||||
_photoPlayback = nullptr;
|
||||
}
|
||||
});
|
||||
if (_list != list) {
|
||||
_list = list;
|
||||
}
|
||||
_index = index;
|
||||
|
||||
const auto id = ShownId{
|
||||
const auto id = Data::FullStoryId{
|
||||
.user = list.user,
|
||||
.id = item.id,
|
||||
};
|
||||
|
@ -152,4 +244,87 @@ void Controller::show(const Data::StoriesList &list, int index) {
|
|||
_replyArea->show({ .user = list.user });
|
||||
}
|
||||
|
||||
void Controller::ready() {
|
||||
if (_photoPlayback) {
|
||||
_photoPlayback->togglePaused(false);
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::updateVideoPlayback(const Player::TrackState &state) {
|
||||
updatePlayback(state);
|
||||
}
|
||||
|
||||
void Controller::updatePhotoPlayback(const Player::TrackState &state) {
|
||||
updatePlayback(state);
|
||||
}
|
||||
|
||||
void Controller::updatePlayback(const Player::TrackState &state) {
|
||||
_slider->updatePlayback(state);
|
||||
updatePowerSaveBlocker(state);
|
||||
if (Player::IsStoppedAtEnd(state.state)) {
|
||||
if (!jumpFor(1)) {
|
||||
_delegate->storiesJumpTo({});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Controller::jumpAvailable(int delta) const {
|
||||
if (delta == -1) {
|
||||
// Always allow to jump back for one.
|
||||
// In case of the first story just jump to the beginning.
|
||||
return _list && !_list->items.empty();
|
||||
}
|
||||
const auto index = _index + delta;
|
||||
return index >= 0 && index < _list->total;
|
||||
}
|
||||
|
||||
bool Controller::jumpFor(int delta) {
|
||||
if (!_index && delta == -1) {
|
||||
if (!_list || _list->items.empty()) {
|
||||
return false;
|
||||
}
|
||||
_delegate->storiesJumpTo({
|
||||
.user = _list->user,
|
||||
.id = _list->items.front().id
|
||||
});
|
||||
return true;
|
||||
}
|
||||
const auto index = _index + delta;
|
||||
if (index < 0 || index >= _list->total) {
|
||||
return false;
|
||||
} else if (index < _list->items.size()) {
|
||||
// #TODO stories load more
|
||||
_delegate->storiesJumpTo({
|
||||
.user = _list->user,
|
||||
.id = _list->items[index].id
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Controller::paused() const {
|
||||
return _photoPlayback
|
||||
? _photoPlayback->paused()
|
||||
: _delegate->storiesPaused();
|
||||
}
|
||||
|
||||
void Controller::togglePaused(bool paused) {
|
||||
if (_photoPlayback) {
|
||||
_photoPlayback->togglePaused(paused);
|
||||
} else {
|
||||
_delegate->storiesTogglePaused(paused);
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::updatePowerSaveBlocker(const Player::TrackState &state) {
|
||||
const auto block = !Player::IsPausedOrPausing(state.state)
|
||||
&& !Player::IsStoppedOrStopping(state.state);
|
||||
base::UpdatePowerSaveBlocker(
|
||||
_powerSaveBlocker,
|
||||
block,
|
||||
base::PowerSaveBlockType::PreventDisplaySleep,
|
||||
[] { return u"Stories playback is active"_q; },
|
||||
[=] { return _wrap->window()->windowHandle(); });
|
||||
}
|
||||
|
||||
} // namespace Media::Stories
|
||||
|
|
|
@ -7,6 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
#include "data/data_stories.h"
|
||||
|
||||
namespace base {
|
||||
class PowerSaveBlocker;
|
||||
} // namespace base
|
||||
|
||||
namespace ChatHelpers {
|
||||
class Show;
|
||||
struct FileChosen;
|
||||
|
@ -20,6 +26,10 @@ namespace Ui {
|
|||
class RpWidget;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Media::Player {
|
||||
struct TrackState;
|
||||
} // namespace Media::Player
|
||||
|
||||
namespace Media::Stories {
|
||||
|
||||
class Header;
|
||||
|
@ -27,17 +37,6 @@ 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,
|
||||
|
@ -59,6 +58,7 @@ struct Layout {
|
|||
class Controller final {
|
||||
public:
|
||||
explicit Controller(not_null<Delegate*> delegate);
|
||||
~Controller();
|
||||
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> wrap() const;
|
||||
[[nodiscard]] Layout layout() const;
|
||||
|
@ -69,9 +69,22 @@ public:
|
|||
-> rpl::producer<ChatHelpers::FileChosen>;
|
||||
|
||||
void show(const Data::StoriesList &list, int index);
|
||||
void ready();
|
||||
|
||||
void updateVideoPlayback(const Player::TrackState &state);
|
||||
|
||||
[[nodiscard]] bool jumpAvailable(int delta) const;
|
||||
[[nodiscard]] bool jumpFor(int delta);
|
||||
[[nodiscard]] bool paused() const;
|
||||
void togglePaused(bool paused);
|
||||
|
||||
private:
|
||||
class PhotoPlayback;
|
||||
|
||||
void initLayout();
|
||||
void updatePhotoPlayback(const Player::TrackState &state);
|
||||
void updatePlayback(const Player::TrackState &state);
|
||||
void updatePowerSaveBlocker(const Player::TrackState &state);
|
||||
|
||||
const not_null<Delegate*> _delegate;
|
||||
|
||||
|
@ -82,7 +95,12 @@ private:
|
|||
const std::unique_ptr<Slider> _slider;
|
||||
const std::unique_ptr<ReplyArea> _replyArea;
|
||||
|
||||
ShownId _shown;
|
||||
Data::FullStoryId _shown;
|
||||
std::optional<Data::StoriesList> _list;
|
||||
int _index = 0;
|
||||
std::unique_ptr<PhotoPlayback> _photoPlayback;
|
||||
|
||||
std::unique_ptr<base::PowerSaveBlocker> _powerSaveBlocker;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
|
|
|
@ -12,12 +12,21 @@ class Show;
|
|||
struct FileChosen;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Data {
|
||||
struct FullStoryId;
|
||||
} // namespace Data
|
||||
|
||||
namespace Ui {
|
||||
class RpWidget;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Media::Stories {
|
||||
|
||||
enum class JumpReason {
|
||||
Finished,
|
||||
User,
|
||||
};
|
||||
|
||||
class Delegate {
|
||||
public:
|
||||
[[nodiscard]] virtual not_null<Ui::RpWidget*> storiesWrap() = 0;
|
||||
|
@ -25,6 +34,9 @@ public:
|
|||
-> std::shared_ptr<ChatHelpers::Show> = 0;
|
||||
[[nodiscard]] virtual auto storiesStickerOrEmojiChosen()
|
||||
-> rpl::producer<ChatHelpers::FileChosen> = 0;
|
||||
virtual void storiesJumpTo(Data::FullStoryId id) = 0;
|
||||
[[nodiscard]] virtual bool storiesPaused() = 0;
|
||||
virtual void storiesTogglePaused(bool paused) = 0;
|
||||
};
|
||||
|
||||
} // namespace Media::Stories
|
||||
|
|
|
@ -18,6 +18,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "styles/style_media_view.h"
|
||||
|
||||
namespace Media::Stories {
|
||||
namespace {
|
||||
|
||||
constexpr auto kNameOpacity = 1.;
|
||||
constexpr auto kDateOpacity = 0.6;
|
||||
|
||||
} // namespace
|
||||
|
||||
Header::Header(not_null<Controller*> controller)
|
||||
: _controller(controller) {
|
||||
|
@ -50,6 +56,7 @@ void Header::show(HeaderData data) {
|
|||
raw,
|
||||
data.user->firstName,
|
||||
st::storiesHeaderName);
|
||||
name->setOpacity(kNameOpacity);
|
||||
name->move(st::storiesHeaderNamePosition);
|
||||
raw->show();
|
||||
_widget = std::move(widget);
|
||||
|
@ -63,6 +70,7 @@ void Header::show(HeaderData data) {
|
|||
_widget.get(),
|
||||
Ui::FormatDateTime(base::unixtime::parse(data.date)),
|
||||
st::storiesHeaderDate);
|
||||
_date->setOpacity(kDateOpacity);
|
||||
_date->show();
|
||||
_date->move(st::storiesHeaderDatePosition);
|
||||
}
|
||||
|
|
|
@ -8,21 +8,34 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "media/stories/media_stories_slider.h"
|
||||
|
||||
#include "media/stories/media_stories_controller.h"
|
||||
#include "media/view/media_view_playback_progress.h"
|
||||
#include "media/audio/media_audio.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rp_widget.h"
|
||||
#include "styles/style_widgets.h"
|
||||
#include "styles/style_media_view.h"
|
||||
|
||||
namespace Media::Stories {
|
||||
namespace {
|
||||
|
||||
constexpr auto kOpacityInactive = 0.4;
|
||||
constexpr auto kOpacityActive = 1.;
|
||||
|
||||
} // namespace
|
||||
|
||||
Slider::Slider(not_null<Controller*> controller)
|
||||
: _controller(controller) {
|
||||
: _controller(controller)
|
||||
, _progress(std::make_unique<View::PlaybackProgress>()) {
|
||||
}
|
||||
|
||||
Slider::~Slider() {
|
||||
}
|
||||
|
||||
void Slider::show(SliderData data) {
|
||||
resetProgress();
|
||||
data.total = std::max(data.total, 1);
|
||||
data.index = std::clamp(data.index, 0, data.total - 1);
|
||||
|
||||
if (_data == data) {
|
||||
return;
|
||||
}
|
||||
|
@ -32,43 +45,101 @@ void Slider::show(SliderData data) {
|
|||
auto widget = std::make_unique<Ui::RpWidget>(parent);
|
||||
const auto raw = widget.get();
|
||||
|
||||
_rects.resize(_data.total);
|
||||
|
||||
raw->widthValue() | rpl::filter([=](int width) {
|
||||
return (width >= st::storiesSliderWidth);
|
||||
}) | rpl::start_with_next([=](int width) {
|
||||
layout(width);
|
||||
}, raw->lifetime());
|
||||
|
||||
raw->paintRequest(
|
||||
) | rpl::filter([=] {
|
||||
return (raw->width() >= st::storiesSlider.width);
|
||||
return (raw->width() >= st::storiesSliderWidth);
|
||||
}) | 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;
|
||||
}
|
||||
paint(QRectF(clip));
|
||||
}, raw->lifetime());
|
||||
|
||||
raw->show();
|
||||
_widget = std::move(widget);
|
||||
|
||||
_progress->setValueChangedCallback([=](float64, float64) {
|
||||
_widget->update(_activeBoundingRect);
|
||||
});
|
||||
|
||||
_controller->layoutValue(
|
||||
) | rpl::start_with_next([=](const Layout &layout) {
|
||||
raw->setGeometry(layout.slider - st::storiesSliderMargin);
|
||||
}, raw->lifetime());
|
||||
}
|
||||
|
||||
void Slider::updatePlayback(const Player::TrackState &state) {
|
||||
_progress->updateState(state);
|
||||
}
|
||||
|
||||
void Slider::resetProgress() {
|
||||
_progress->updateState({});
|
||||
}
|
||||
|
||||
void Slider::layout(int width) {
|
||||
const auto single = st::storiesSliderWidth;
|
||||
const auto skip = st::storiesSliderSkip;
|
||||
// width == single * max + skip * (max - 1);
|
||||
// max == (width + skip) / (single + skip);
|
||||
const auto max = (width + skip) / (single + skip);
|
||||
Assert(max > 0);
|
||||
const auto count = std::clamp(_data.total, 1, max);
|
||||
const auto one = (width - (count - 1) * skip) / float64(count);
|
||||
auto left = 0.;
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
_rects[i] = QRectF(left, 0, one, single);
|
||||
if (i == _data.index) {
|
||||
const auto from = int(std::floor(left));
|
||||
const auto size = int(std::ceil(left + one)) - from;
|
||||
_activeBoundingRect = QRect(from, 0, size, single);
|
||||
}
|
||||
left += one + skip;
|
||||
}
|
||||
for (auto i = count; i != _rects.size(); ++i) {
|
||||
_rects[i] = QRectF();
|
||||
}
|
||||
}
|
||||
|
||||
void Slider::paint(QRectF clip) {
|
||||
auto p = QPainter(_widget.get());
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
|
||||
p.setBrush(st::mediaviewControlFg);
|
||||
p.setPen(Qt::NoPen);
|
||||
const auto radius = st::storiesSliderWidth / 2.;
|
||||
for (auto i = 0; i != int(_rects.size()); ++i) {
|
||||
if (_rects[i].isEmpty()) {
|
||||
break;
|
||||
} else if (!_rects[i].intersects(clip)) {
|
||||
continue;
|
||||
} else if (i == _data.index) {
|
||||
const auto progress = _progress->value();
|
||||
const auto full = _rects[i].width();
|
||||
const auto min = _rects[i].height();
|
||||
const auto activeWidth = std::max(full * progress, min);
|
||||
const auto inactiveWidth = full - activeWidth + min;
|
||||
const auto activeLeft = _rects[i].left();
|
||||
const auto inactiveLeft = activeLeft + activeWidth - min;
|
||||
p.setOpacity(kOpacityInactive);
|
||||
p.drawRoundedRect(
|
||||
QRectF(inactiveLeft, 0, inactiveWidth, min),
|
||||
radius,
|
||||
radius);
|
||||
p.setOpacity(kOpacityActive);
|
||||
p.drawRoundedRect(
|
||||
QRectF(activeLeft, 0, activeWidth, min),
|
||||
radius,
|
||||
radius);
|
||||
} else {
|
||||
p.setOpacity(kOpacityInactive);
|
||||
p.drawRoundedRect(_rects[i], radius, radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Media::Stories
|
||||
|
|
|
@ -11,6 +11,14 @@ namespace Ui {
|
|||
class RpWidget;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Media::View {
|
||||
class PlaybackProgress;
|
||||
} // namespace Media::View
|
||||
|
||||
namespace Media::Player {
|
||||
struct TrackState;
|
||||
} // namespace Media::Player
|
||||
|
||||
namespace Media::Stories {
|
||||
|
||||
class Controller;
|
||||
|
@ -30,13 +38,24 @@ public:
|
|||
|
||||
void show(SliderData data);
|
||||
|
||||
void updatePlayback(const Player::TrackState &state);
|
||||
|
||||
private:
|
||||
void resetProgress();
|
||||
|
||||
void layout(int width);
|
||||
void paint(QRectF clip);
|
||||
|
||||
const not_null<Controller*> _controller;
|
||||
const std::unique_ptr<Media::View::PlaybackProgress> _progress;
|
||||
|
||||
std::unique_ptr<Ui::RpWidget> _widget;
|
||||
std::vector<QRectF> _rects;
|
||||
QRect _activeBoundingRect;
|
||||
|
||||
SliderData _data;
|
||||
|
||||
|
||||
};
|
||||
|
||||
} // namespace Media::Stories
|
||||
|
|
|
@ -25,8 +25,32 @@ void View::show(const Data::StoriesList &list, int index) {
|
|||
_controller->show(list, index);
|
||||
}
|
||||
|
||||
void View::ready() {
|
||||
_controller->ready();
|
||||
}
|
||||
|
||||
QRect View::contentGeometry() const {
|
||||
return _controller->layout().content;
|
||||
}
|
||||
|
||||
void View::updatePlayback(const Player::TrackState &state) {
|
||||
_controller->updateVideoPlayback(state);
|
||||
}
|
||||
|
||||
bool View::jumpAvailable(int delta) const {
|
||||
return _controller->jumpAvailable(delta);
|
||||
}
|
||||
|
||||
bool View::jumpFor(int delta) const {
|
||||
return _controller->jumpFor(delta);
|
||||
}
|
||||
|
||||
bool View::paused() const {
|
||||
return _controller->paused();
|
||||
}
|
||||
|
||||
void View::togglePaused(bool paused) {
|
||||
_controller->togglePaused(paused);
|
||||
}
|
||||
|
||||
} // namespace Media::Stories
|
||||
|
|
|
@ -11,6 +11,10 @@ namespace Data {
|
|||
struct StoriesList;
|
||||
} // namespace Data
|
||||
|
||||
namespace Media::Player {
|
||||
struct TrackState;
|
||||
} // namespace Media::Player
|
||||
|
||||
namespace Media::Stories {
|
||||
|
||||
class Delegate;
|
||||
|
@ -22,8 +26,18 @@ public:
|
|||
~View();
|
||||
|
||||
void show(const Data::StoriesList &list, int index);
|
||||
void ready();
|
||||
|
||||
[[nodiscard]] QRect contentGeometry() const;
|
||||
|
||||
void updatePlayback(const Player::TrackState &state);
|
||||
|
||||
[[nodiscard]] bool jumpAvailable(int delta) const;
|
||||
[[nodiscard]] bool jumpFor(int delta) const;
|
||||
|
||||
[[nodiscard]] bool paused() const;
|
||||
void togglePaused(bool paused);
|
||||
|
||||
private:
|
||||
const std::unique_ptr<Controller> _controller;
|
||||
|
||||
|
|
|
@ -406,11 +406,8 @@ pipVolumeIcon2Over: icon {{ "player/player_volume_on", mediaviewPipControlsFgOve
|
|||
speedSliderDividerSize: size(2px, 8px);
|
||||
|
||||
storiesMaxSize: size(405px, 720px);
|
||||
storiesSlider: MediaSlider(mediaviewPlayback) {
|
||||
width: 2px;
|
||||
seekSize: size(2px, 2px);
|
||||
}
|
||||
storiesSliderMargin: margins(8px, 7px, 8px, 10px);
|
||||
storiesSliderWidth: 2px;
|
||||
storiesSliderMargin: margins(8px, 7px, 8px, 11px);
|
||||
storiesSliderSkip: 4px;
|
||||
storiesHeaderMargin: margins(12px, 3px, 12px, 8px);
|
||||
storiesHeaderPhoto: UserpicButton(defaultUserpicButton) {
|
||||
|
@ -418,14 +415,14 @@ storiesHeaderPhoto: UserpicButton(defaultUserpicButton) {
|
|||
photoSize: 28px;
|
||||
}
|
||||
storiesHeaderName: FlatLabel(defaultFlatLabel) {
|
||||
textFg: mediaviewPipControlsFgOver; // #TODO stories
|
||||
textFg: mediaviewControlFg;
|
||||
style: semiboldTextStyle;
|
||||
}
|
||||
storiesHeaderNamePosition: point(50px, 2px);
|
||||
storiesHeaderNamePosition: point(50px, 0px);
|
||||
storiesHeaderDate: FlatLabel(defaultFlatLabel) {
|
||||
textFg: mediaviewPipControlsFg; // #TODO stories
|
||||
textFg: mediaviewControlFg;
|
||||
}
|
||||
storiesHeaderDatePosition: point(50px, 19px);
|
||||
storiesHeaderDatePosition: point(50px, 16px);
|
||||
storiesControlsMinWidth: 200px;
|
||||
storiesFieldMargin: margins(0px, 14px, 0px, 16px);
|
||||
storiesAttach: IconButton(defaultIconButton) {
|
||||
|
|
|
@ -251,18 +251,14 @@ struct OverlayWidget::Streamed {
|
|||
Streamed(
|
||||
not_null<DocumentData*> document,
|
||||
Data::FileOrigin origin,
|
||||
not_null<QWidget*> controlsParent,
|
||||
not_null<PlaybackControls::Delegate*> controlsDelegate,
|
||||
Fn<void()> waitingCallback);
|
||||
Streamed(
|
||||
not_null<PhotoData*> photo,
|
||||
Data::FileOrigin origin,
|
||||
not_null<QWidget*> controlsParent,
|
||||
not_null<PlaybackControls::Delegate*> controlsDelegate,
|
||||
Fn<void()> waitingCallback);
|
||||
|
||||
Streaming::Instance instance;
|
||||
PlaybackControls controls;
|
||||
std::unique_ptr<PlaybackControls> controls;
|
||||
std::unique_ptr<base::PowerSaveBlocker> powerSaveBlocker;
|
||||
|
||||
bool withSound = false;
|
||||
|
@ -289,21 +285,15 @@ struct OverlayWidget::PipWrap {
|
|||
OverlayWidget::Streamed::Streamed(
|
||||
not_null<DocumentData*> document,
|
||||
Data::FileOrigin origin,
|
||||
not_null<QWidget*> controlsParent,
|
||||
not_null<PlaybackControls::Delegate*> controlsDelegate,
|
||||
Fn<void()> waitingCallback)
|
||||
: instance(document, origin, std::move(waitingCallback))
|
||||
, controls(controlsParent, controlsDelegate) {
|
||||
: instance(document, origin, std::move(waitingCallback)) {
|
||||
}
|
||||
|
||||
OverlayWidget::Streamed::Streamed(
|
||||
not_null<PhotoData*> photo,
|
||||
Data::FileOrigin origin,
|
||||
not_null<QWidget*> controlsParent,
|
||||
not_null<PlaybackControls::Delegate*> controlsDelegate,
|
||||
Fn<void()> waitingCallback)
|
||||
: instance(photo, origin, std::move(waitingCallback))
|
||||
, controls(controlsParent, controlsDelegate) {
|
||||
: instance(photo, origin, std::move(waitingCallback)) {
|
||||
}
|
||||
|
||||
OverlayWidget::PipWrap::PipWrap(
|
||||
|
@ -542,7 +532,9 @@ OverlayWidget::OverlayWidget()
|
|||
Core::App().calls().currentGroupCallValue(),
|
||||
_1 || _2
|
||||
) | rpl::start_with_next([=](bool call) {
|
||||
if (!_streamed || videoIsGifOrUserpic()) {
|
||||
if (!_streamed
|
||||
|| !_document
|
||||
|| (_document->isAnimation() && !_document->isVideoMessage())) {
|
||||
return;
|
||||
} else if (call) {
|
||||
playbackPauseOnCall();
|
||||
|
@ -583,7 +575,10 @@ void OverlayWidget::setupWindow() {
|
|||
return Flag::None | Flag(0);
|
||||
}
|
||||
const auto inControls = (_over != OverNone) && (_over != OverVideo);
|
||||
if (inControls || (_streamed && _streamed->controls.dragging())) {
|
||||
if (inControls
|
||||
|| (_streamed
|
||||
&& _streamed->controls
|
||||
&& _streamed->controls->dragging())) {
|
||||
return Flag::None | Flag(0);
|
||||
} else if ((_w > _widget->width() || _h > _widget->height())
|
||||
&& (widgetPoint.y() > st::mediaviewHeaderTop)
|
||||
|
@ -881,10 +876,10 @@ QSize OverlayWidget::videoSize() const {
|
|||
return flipSizeByRotation(_streamed->instance.info().video.size);
|
||||
}
|
||||
|
||||
bool OverlayWidget::videoIsGifOrUserpic() const {
|
||||
return _streamed
|
||||
&& (!_document
|
||||
|| (_document->isAnimation() && !_document->isVideoMessage()));
|
||||
bool OverlayWidget::streamingRequiresControls() const {
|
||||
return !_stories
|
||||
&& _document
|
||||
&& (!_document->isAnimation() || _document->isVideoMessage());
|
||||
}
|
||||
|
||||
QImage OverlayWidget::videoFrame() const {
|
||||
|
@ -979,13 +974,13 @@ void OverlayWidget::documentUpdated(not_null<DocumentData*> document) {
|
|||
updateDocSize();
|
||||
_widget->update(_docRect);
|
||||
}
|
||||
} else if (_streamed) {
|
||||
} else if (_streamed && _streamed->controls) {
|
||||
const auto ready = _documentMedia->loaded()
|
||||
? _document->size
|
||||
: _document->loading()
|
||||
? std::clamp(_document->loadOffset(), int64(), _document->size)
|
||||
: 0;
|
||||
_streamed->controls.setLoadingProgress(ready, _document->size);
|
||||
_streamed->controls->setLoadingProgress(ready, _document->size);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1013,7 +1008,10 @@ void OverlayWidget::updateDocSize() {
|
|||
}
|
||||
|
||||
void OverlayWidget::refreshNavVisibility() {
|
||||
if (_sharedMediaData) {
|
||||
if (_stories) {
|
||||
_leftNavVisible = _stories->jumpAvailable(-1);
|
||||
_rightNavVisible = _stories->jumpAvailable(1);
|
||||
} else if (_sharedMediaData) {
|
||||
_leftNavVisible = _index && (*_index > 0);
|
||||
_rightNavVisible = _index && (*_index + 1 < _sharedMediaData->size());
|
||||
} else if (_userPhotosData) {
|
||||
|
@ -1029,7 +1027,7 @@ void OverlayWidget::refreshNavVisibility() {
|
|||
}
|
||||
|
||||
bool OverlayWidget::contentCanBeSaved() const {
|
||||
if (hasCopyMediaRestriction()) {
|
||||
if (_stories || hasCopyMediaRestriction()) {
|
||||
return false;
|
||||
} else if (_photo) {
|
||||
return _photo->hasVideo() || _photoMedia->loaded();
|
||||
|
@ -1108,7 +1106,7 @@ void OverlayWidget::updateControls() {
|
|||
QPoint(),
|
||||
QSize(st::mediaviewIconOver, st::mediaviewIconOver));
|
||||
_saveVisible = contentCanBeSaved();
|
||||
_rotateVisible = !_themePreviewShown;
|
||||
_rotateVisible = !_themePreviewShown && !_stories;
|
||||
const auto navRect = [&](int i) {
|
||||
return QRect(width() - st::mediaviewIconSize.width() * i,
|
||||
height() - st::mediaviewIconSize.height(),
|
||||
|
@ -1181,8 +1179,8 @@ void OverlayWidget::refreshCaptionGeometry() {
|
|||
_groupThumbs = nullptr;
|
||||
_groupThumbsRect = QRect();
|
||||
}
|
||||
const auto captionBottom = (_streamed && !videoIsGifOrUserpic())
|
||||
? (_streamed->controls.y() - st::mediaviewCaptionMargin.height())
|
||||
const auto captionBottom = (_streamed && _streamed->controls)
|
||||
? (_streamed->controls->y() - st::mediaviewCaptionMargin.height())
|
||||
: _groupThumbs
|
||||
? _groupThumbsTop
|
||||
: height() - st::mediaviewCaptionMargin.height();
|
||||
|
@ -1523,9 +1521,9 @@ void OverlayWidget::contentSizeChanged() {
|
|||
}
|
||||
|
||||
void OverlayWidget::recountSkipTop() {
|
||||
const auto bottom = (!_streamed || videoIsGifOrUserpic())
|
||||
const auto bottom = (!_streamed || !_streamed->controls)
|
||||
? height()
|
||||
: (_streamed->controls.y() - st::mediaviewCaptionPadding.bottom());
|
||||
: (_streamed->controls->y() - st::mediaviewCaptionPadding.bottom());
|
||||
const auto skipHeightBottom = (height() - bottom);
|
||||
_skipTop = std::min(
|
||||
std::max(
|
||||
|
@ -1869,12 +1867,12 @@ void OverlayWidget::toggleFullScreen(bool fullscreen) {
|
|||
}
|
||||
|
||||
void OverlayWidget::activateControls() {
|
||||
if (!_menu && !_mousePressed) {
|
||||
if (!_menu && !_mousePressed && !_stories) {
|
||||
_controlsHideTimer.callOnce(st::mediaviewWaitHide);
|
||||
}
|
||||
if (_fullScreenVideo) {
|
||||
if (_streamed) {
|
||||
_streamed->controls.showAnimated();
|
||||
if (_streamed && _streamed->controls) {
|
||||
_streamed->controls->showAnimated();
|
||||
}
|
||||
}
|
||||
if (_controlsState == ControlsHiding || _controlsState == ControlsHidden) {
|
||||
|
@ -1888,16 +1886,23 @@ void OverlayWidget::activateControls() {
|
|||
}
|
||||
|
||||
void OverlayWidget::hideControls(bool force) {
|
||||
if (!force) {
|
||||
if (_stories) {
|
||||
_controlsState = ControlsShown;
|
||||
_controlsOpacity = anim::value(1);
|
||||
_helper->setControlsOpacity(1.);
|
||||
return;
|
||||
} else if (!force) {
|
||||
if (!_dropdown->isHidden()
|
||||
|| (_streamed && _streamed->controls.hasMenu())
|
||||
|| (_streamed
|
||||
&& _streamed->controls
|
||||
&& _streamed->controls->hasMenu())
|
||||
|| _menu
|
||||
|| _mousePressed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (_fullScreenVideo) {
|
||||
_streamed->controls.hideAnimated();
|
||||
if (_fullScreenVideo && _streamed && _streamed->controls) {
|
||||
_streamed->controls->hideAnimated();
|
||||
}
|
||||
if (_controlsState == ControlsHiding || _controlsState == ControlsHidden) return;
|
||||
|
||||
|
@ -2959,7 +2964,7 @@ void OverlayWidget::displayPhoto(not_null<PhotoData*> photo) {
|
|||
refreshMediaViewer();
|
||||
|
||||
_staticContent = QImage();
|
||||
if (_photo->videoCanBePlayed()) {
|
||||
if (!_stories && _photo->videoCanBePlayed()) {
|
||||
initStreaming();
|
||||
}
|
||||
|
||||
|
@ -3133,7 +3138,7 @@ void OverlayWidget::displayDocument(
|
|||
}
|
||||
refreshFromLabel();
|
||||
_blurred = false;
|
||||
if (_showAsPip && _streamed && !videoIsGifOrUserpic()) {
|
||||
if (_showAsPip && _streamed && _streamed->controls) {
|
||||
switchToPip();
|
||||
} else {
|
||||
displayFinished();
|
||||
|
@ -3348,20 +3353,12 @@ void OverlayWidget::applyVideoSize() {
|
|||
bool OverlayWidget::createStreamingObjects() {
|
||||
Expects(_photo || _document);
|
||||
|
||||
const auto origin = fileOrigin();
|
||||
const auto callback = [=] { waitingAnimationCallback(); };
|
||||
if (_document) {
|
||||
_streamed = std::make_unique<Streamed>(
|
||||
_document,
|
||||
fileOrigin(),
|
||||
_body,
|
||||
static_cast<PlaybackControls::Delegate*>(this),
|
||||
[=] { waitingAnimationCallback(); });
|
||||
_streamed = std::make_unique<Streamed>(_document, origin, callback);
|
||||
} else {
|
||||
_streamed = std::make_unique<Streamed>(
|
||||
_photo,
|
||||
fileOrigin(),
|
||||
_body,
|
||||
static_cast<PlaybackControls::Delegate*>(this),
|
||||
[=] { waitingAnimationCallback(); });
|
||||
_streamed = std::make_unique<Streamed>(_photo, origin, callback);
|
||||
}
|
||||
if (!_streamed->instance.valid()) {
|
||||
_streamed = nullptr;
|
||||
|
@ -3375,12 +3372,12 @@ bool OverlayWidget::createStreamingObjects() {
|
|||
|| _document->isVideoFile()
|
||||
|| _document->isVoiceMessage()
|
||||
|| _document->isVideoMessage());
|
||||
|
||||
if (videoIsGifOrUserpic()) {
|
||||
_streamed->controls.hide();
|
||||
} else {
|
||||
if (streamingRequiresControls()) {
|
||||
_streamed->controls = std::make_unique<PlaybackControls>(
|
||||
_body,
|
||||
static_cast<PlaybackControls::Delegate*>(this));
|
||||
_streamed->controls->show();
|
||||
refreshClipControllerGeometry();
|
||||
_streamed->controls.show();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -3569,7 +3566,7 @@ void OverlayWidget::initThemePreview() {
|
|||
}
|
||||
|
||||
void OverlayWidget::refreshClipControllerGeometry() {
|
||||
if (!_streamed || videoIsGifOrUserpic()) {
|
||||
if (!_streamed || !_streamed->controls) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -3584,13 +3581,15 @@ void OverlayWidget::refreshClipControllerGeometry() {
|
|||
const auto controllerWidth = std::min(
|
||||
st::mediaviewControllerSize.width(),
|
||||
width() - 2 * skip);
|
||||
_streamed->controls.resize(
|
||||
_streamed->controls->resize(
|
||||
controllerWidth,
|
||||
st::mediaviewControllerSize.height());
|
||||
_streamed->controls.move(
|
||||
_streamed->controls->move(
|
||||
(width() - controllerWidth) / 2,
|
||||
controllerBottom - _streamed->controls.height() - st::mediaviewCaptionPadding.bottom());
|
||||
Ui::SendPendingMoveResizeEvents(&_streamed->controls);
|
||||
(controllerBottom
|
||||
- _streamed->controls->height()
|
||||
- st::mediaviewCaptionPadding.bottom()));
|
||||
Ui::SendPendingMoveResizeEvents(_streamed->controls.get());
|
||||
}
|
||||
|
||||
void OverlayWidget::playbackControlsPlay() {
|
||||
|
@ -3614,7 +3613,7 @@ void OverlayWidget::playbackControlsFromFullScreen() {
|
|||
}
|
||||
|
||||
void OverlayWidget::playbackControlsToPictureInPicture() {
|
||||
if (!videoIsGifOrUserpic()) {
|
||||
if (_streamed && _streamed->controls) {
|
||||
switchToPip();
|
||||
}
|
||||
}
|
||||
|
@ -3775,7 +3774,7 @@ void OverlayWidget::playbackControlsSpeedChanged(float64 speed) {
|
|||
Core::App().settings().setVideoPlaybackSpeed(speed);
|
||||
Core::App().saveSettingsDelayed();
|
||||
}
|
||||
if (_streamed && !videoIsGifOrUserpic()) {
|
||||
if (_streamed && _streamed->controls) {
|
||||
DEBUG_LOG(("Media playback speed: %1 to _streamed.").arg(speed));
|
||||
_streamed->instance.setSpeed(speed);
|
||||
}
|
||||
|
@ -3921,10 +3920,66 @@ auto OverlayWidget::storiesStickerOrEmojiChosen()
|
|||
return _storiesStickerOrEmojiChosen.events();
|
||||
}
|
||||
|
||||
void OverlayWidget::storiesJumpTo(Data::FullStoryId id) {
|
||||
Expects(_stories != nullptr);
|
||||
|
||||
if (!id) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
const auto &all = id.user->owner().stories().all();
|
||||
const auto i = ranges::find(
|
||||
all,
|
||||
not_null(id.user),
|
||||
&Data::StoriesList::user);
|
||||
if (i == end(all)) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
const auto j = ranges::find(i->items, id.id, &Data::StoryItem::id);
|
||||
if (j == end(i->items)) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
setContext(StoriesContext{ i->user, id.id });
|
||||
clearStreaming();
|
||||
_streamingStartPaused = false;
|
||||
const auto &data = j->media.data;
|
||||
if (const auto photo = std::get_if<not_null<PhotoData*>>(&data)) {
|
||||
displayPhoto(*photo);
|
||||
} else {
|
||||
displayDocument(v::get<not_null<DocumentData*>>(data));
|
||||
}
|
||||
}
|
||||
|
||||
bool OverlayWidget::storiesPaused() {
|
||||
return _streamed
|
||||
&& !_streamed->instance.player().failed()
|
||||
&& !_streamed->instance.player().finished()
|
||||
&& _streamed->instance.player().active()
|
||||
&& _streamed->instance.player().paused();
|
||||
}
|
||||
|
||||
void OverlayWidget::storiesTogglePaused(bool paused) {
|
||||
if (!_streamed
|
||||
|| _streamed->instance.player().failed()
|
||||
|| _streamed->instance.player().finished()
|
||||
|| !_streamed->instance.player().active()) {
|
||||
return;
|
||||
} else if (_streamed->instance.player().paused()) {
|
||||
_streamed->instance.resume();
|
||||
updatePlaybackState();
|
||||
playbackPauseMusic();
|
||||
} else {
|
||||
_streamed->instance.pause();
|
||||
updatePlaybackState();
|
||||
}
|
||||
}
|
||||
|
||||
void OverlayWidget::playbackToggleFullScreen() {
|
||||
Expects(_streamed != nullptr);
|
||||
|
||||
if (!videoShown() || (videoIsGifOrUserpic() && !_fullScreenVideo)) {
|
||||
if (!videoShown() || (!_streamed->controls && !_fullScreenVideo)) {
|
||||
return;
|
||||
}
|
||||
_fullScreenVideo = !_fullScreenVideo;
|
||||
|
@ -3936,10 +3991,12 @@ void OverlayWidget::playbackToggleFullScreen() {
|
|||
setZoomLevel(
|
||||
_fullScreenVideo ? kZoomToScreenLevel : _fullScreenZoomCache,
|
||||
true);
|
||||
if (!_fullScreenVideo) {
|
||||
_streamed->controls.showAnimated();
|
||||
if (_streamed->controls) {
|
||||
if (!_fullScreenVideo) {
|
||||
_streamed->controls->showAnimated();
|
||||
}
|
||||
_streamed->controls->setInFullScreen(_fullScreenVideo);
|
||||
}
|
||||
_streamed->controls.setInFullScreen(_fullScreenVideo);
|
||||
_touchbarFullscreenToggled.fire_copy(_fullScreenVideo);
|
||||
updateControls();
|
||||
update();
|
||||
|
@ -3981,14 +4038,19 @@ void OverlayWidget::playbackPauseMusic() {
|
|||
void OverlayWidget::updatePlaybackState() {
|
||||
Expects(_streamed != nullptr);
|
||||
|
||||
if (videoIsGifOrUserpic()) {
|
||||
if (!_streamed->controls && !_stories) {
|
||||
return;
|
||||
}
|
||||
const auto state = _streamed->instance.player().prepareLegacyState();
|
||||
if (state.position != kTimeUnknown && state.length != kTimeUnknown) {
|
||||
_streamed->controls.updatePlayback(state);
|
||||
updatePowerSaveBlocker(state);
|
||||
_touchbarTrackState.fire_copy(state);
|
||||
if (_streamed->controls) {
|
||||
_streamed->controls->updatePlayback(state);
|
||||
_touchbarTrackState.fire_copy(state);
|
||||
updatePowerSaveBlocker(state);
|
||||
}
|
||||
if (_stories) {
|
||||
_stories->updatePlayback(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4050,9 +4112,15 @@ void OverlayWidget::paint(not_null<Renderer*> renderer) {
|
|||
renderer->paintTransformedVideoFrame(contentGeometry());
|
||||
if (_streamed->instance.player().ready()) {
|
||||
_streamed->instance.markFrameShown();
|
||||
if (_stories) {
|
||||
_stories->ready();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
validatePhotoCurrentImage();
|
||||
if (_stories && !_blurred) {
|
||||
_stories->ready();
|
||||
}
|
||||
const auto fillTransparentBackground = (!_document
|
||||
|| (!_document->sticker() && !_document->isVideoMessage()))
|
||||
&& _staticContentTransparent;
|
||||
|
@ -4077,7 +4145,9 @@ void OverlayWidget::paint(not_null<Renderer*> renderer) {
|
|||
const auto opacity = _fullScreenVideo ? 0. : _controlsOpacity.current();
|
||||
if (opacity > 0) {
|
||||
paintControls(renderer, opacity);
|
||||
renderer->paintFooter(footerGeometry(), opacity);
|
||||
if (!_stories) {
|
||||
renderer->paintFooter(footerGeometry(), opacity);
|
||||
}
|
||||
if (!_caption.isEmpty()) {
|
||||
renderer->paintCaption(captionGeometry(), opacity);
|
||||
}
|
||||
|
@ -4510,6 +4580,10 @@ void OverlayWidget::handleKeyPress(not_null<QKeyEvent*> e) {
|
|||
const auto key = e->key();
|
||||
const auto modifiers = e->modifiers();
|
||||
const auto ctrl = modifiers.testFlag(Qt::ControlModifier);
|
||||
if (_stories && key == Qt::Key_Space && _down != OverVideo) {
|
||||
_stories->togglePaused(!_stories->paused());
|
||||
return;
|
||||
}
|
||||
if (_streamed) {
|
||||
// Ctrl + F for full screen toggle is in eventFilter().
|
||||
const auto toggleFull = (modifiers.testFlag(Qt::AltModifier) || ctrl)
|
||||
|
@ -4833,7 +4907,9 @@ void OverlayWidget::setSession(not_null<Main::Session*> session) {
|
|||
}
|
||||
|
||||
bool OverlayWidget::moveToNext(int delta) {
|
||||
if (!_index) {
|
||||
if (_stories) {
|
||||
return _stories->jumpFor(delta);
|
||||
} else if (!_index) {
|
||||
return false;
|
||||
}
|
||||
auto newIndex = *_index + delta;
|
||||
|
@ -4928,6 +5004,9 @@ void OverlayWidget::handleMousePress(
|
|||
|| _over == OverMore
|
||||
|| _over == OverVideo) {
|
||||
_down = _over;
|
||||
if (_over == OverVideo && _stories) {
|
||||
_stories->togglePaused(true);
|
||||
}
|
||||
} else if (!_saveMsg.contains(position) || !isSaveMsgShown()) {
|
||||
_pressed = true;
|
||||
_dragging = 0;
|
||||
|
@ -4950,9 +5029,12 @@ bool OverlayWidget::handleDoubleClick(
|
|||
|
||||
if (_over != OverVideo || !_streamed || button != Qt::LeftButton) {
|
||||
return false;
|
||||
} else if (_stories) {
|
||||
toggleFullScreen(_windowed);
|
||||
} else {
|
||||
playbackToggleFullScreen();
|
||||
playbackPauseResume();
|
||||
}
|
||||
playbackToggleFullScreen();
|
||||
playbackPauseResume();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -5090,11 +5172,11 @@ void OverlayWidget::updateOver(QPoint pos) {
|
|||
updateOverState(OverLeftNav);
|
||||
} else if (_rightNavVisible && _rightNav.contains(pos)) {
|
||||
updateOverState(OverRightNav);
|
||||
} else if (_from && _nameNav.contains(pos)) {
|
||||
} else if (!_stories && _from && _nameNav.contains(pos)) {
|
||||
updateOverState(OverName);
|
||||
} else if (_message && _message->isRegular() && _dateNav.contains(pos)) {
|
||||
} else if (!_stories && _message && _message->isRegular() && _dateNav.contains(pos)) {
|
||||
updateOverState(OverDate);
|
||||
} else if (_headerHasLink && _headerNav.contains(pos)) {
|
||||
} else if (!_stories && _headerHasLink && _headerNav.contains(pos)) {
|
||||
updateOverState(OverHeader);
|
||||
} else if (_saveVisible && _saveNav.contains(pos)) {
|
||||
updateOverState(OverSave);
|
||||
|
@ -5104,10 +5186,14 @@ void OverlayWidget::updateOver(QPoint pos) {
|
|||
updateOverState(OverIcon);
|
||||
} else if (_moreNav.contains(pos)) {
|
||||
updateOverState(OverMore);
|
||||
} else if (documentContentShown() && finalContentRect().contains(pos)) {
|
||||
if ((_document->isVideoFile() || _document->isVideoMessage()) && _streamed) {
|
||||
} else if (contentShown() && finalContentRect().contains(pos)) {
|
||||
if (_stories) {
|
||||
updateOverState(OverVideo);
|
||||
} else if (!_streamed && !_documentMedia->loaded()) {
|
||||
} else if (_streamed
|
||||
&& _document
|
||||
&& (_document->isVideoFile() || _document->isVideoMessage())) {
|
||||
updateOverState(OverVideo);
|
||||
} else if (!_streamed && _document && !_documentMedia->loaded()) {
|
||||
updateOverState(OverIcon);
|
||||
} else if (_over != OverNone) {
|
||||
updateOverState(OverNone);
|
||||
|
@ -5163,7 +5249,9 @@ void OverlayWidget::handleMouseRelease(
|
|||
} else if (_over == OverMore && _down == OverMore) {
|
||||
InvokeQueued(_widget, [=] { showDropdown(); });
|
||||
} else if (_over == OverVideo && _down == OverVideo) {
|
||||
if (_streamed) {
|
||||
if (_stories) {
|
||||
_stories->togglePaused(false);
|
||||
} else if (_streamed) {
|
||||
playbackPauseResume();
|
||||
}
|
||||
} else if (_pressed) {
|
||||
|
|
|
@ -26,6 +26,7 @@ class History;
|
|||
namespace Data {
|
||||
class PhotoMedia;
|
||||
class DocumentMedia;
|
||||
struct FullStoryId;
|
||||
} // namespace Data
|
||||
|
||||
namespace Ui {
|
||||
|
@ -227,6 +228,9 @@ private:
|
|||
std::shared_ptr<ChatHelpers::Show> storiesShow() override;
|
||||
auto storiesStickerOrEmojiChosen()
|
||||
-> rpl::producer<ChatHelpers::FileChosen> override;
|
||||
void storiesJumpTo(Data::FullStoryId id) override;
|
||||
bool storiesPaused() override;
|
||||
void storiesTogglePaused(bool paused) override;
|
||||
|
||||
void hideControls(bool force = false);
|
||||
void subscribeToScreenGeometry();
|
||||
|
@ -458,7 +462,7 @@ private:
|
|||
void applyVideoSize();
|
||||
[[nodiscard]] bool videoShown() const;
|
||||
[[nodiscard]] QSize videoSize() const;
|
||||
[[nodiscard]] bool videoIsGifOrUserpic() const;
|
||||
[[nodiscard]] bool streamingRequiresControls() const;
|
||||
[[nodiscard]] QImage videoFrame() const; // ARGB (changes prepare format)
|
||||
[[nodiscard]] QImage currentVideoFrameImage() const; // RGB (may convert)
|
||||
[[nodiscard]] Streaming::FrameWithInfo videoFrameWithInfo() const; // YUV
|
||||
|
|
Loading…
Add table
Reference in a new issue