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