Implement custom OpenGL renderer for PiP.
This commit is contained in:
parent
b38f89d69e
commit
fba116f0d5
|
@ -786,6 +786,11 @@ PRIVATE
|
|||
media/view/media_view_overlay_widget.h
|
||||
media/view/media_view_pip.cpp
|
||||
media/view/media_view_pip.h
|
||||
media/view/media_view_pip_opengl.cpp
|
||||
media/view/media_view_pip_opengl.h
|
||||
media/view/media_view_pip_raster.cpp
|
||||
media/view/media_view_pip_raster.h
|
||||
media/view/media_view_pip_renderer.h
|
||||
media/view/media_view_playback_controls.cpp
|
||||
media/view/media_view_playback_controls.h
|
||||
media/view/media_view_playback_progress.cpp
|
||||
|
|
|
@ -176,7 +176,7 @@ FrameWithInfo Instance::frameWithInfo() const {
|
|||
return player().frameWithInfo(this);
|
||||
}
|
||||
|
||||
bool Instance::markFrameShown() {
|
||||
bool Instance::markFrameShown() const {
|
||||
Expects(_shared != nullptr);
|
||||
|
||||
return _shared->player().markFrameShown();
|
||||
|
|
|
@ -70,7 +70,7 @@ public:
|
|||
|
||||
[[nodiscard]] QImage frame(const FrameRequest &request) const;
|
||||
[[nodiscard]] FrameWithInfo frameWithInfo() const;
|
||||
bool markFrameShown();
|
||||
bool markFrameShown() const;
|
||||
|
||||
void lockPlayer();
|
||||
void unlockPlayer();
|
||||
|
|
|
@ -297,9 +297,15 @@ pipPlayIcon: icon {{ "player_pip_play", mediaviewPipControlsFg }};
|
|||
pipPlayIconOver: icon {{ "player_pip_play", mediaviewPipControlsFgOver }};
|
||||
pipPauseIcon: icon {{ "player_pip_pause", mediaviewPipControlsFg }};
|
||||
pipPauseIconOver: icon {{ "player_pip_pause", mediaviewPipControlsFgOver }};
|
||||
pipCloseIcon: icon {{ "player_pip_close", mediaviewPlaybackIconFg }};
|
||||
pipCloseIconOver: icon {{ "player_pip_close", mediaviewPlaybackIconFgOver }};
|
||||
pipEnlargeIcon: icon {{ "player_pip_enlarge", mediaviewPlaybackIconFg }};
|
||||
pipEnlargeIconOver: icon {{ "player_pip_enlarge", mediaviewPlaybackIconFgOver }};
|
||||
pipCloseIcon: icon {{ "player_pip_close", mediaviewPipControlsFg }};
|
||||
pipCloseIconOver: icon {{ "player_pip_close", mediaviewPipControlsFgOver }};
|
||||
pipEnlargeIcon: icon {{ "player_pip_enlarge", mediaviewPipControlsFg }};
|
||||
pipEnlargeIconOver: icon {{ "player_pip_enlarge", mediaviewPipControlsFgOver }};
|
||||
pipVolumeIcon0: icon {{ "player_volume_off", mediaviewPipControlsFg }};
|
||||
pipVolumeIcon0Over: icon {{ "player_volume_off", mediaviewPipControlsFgOver }};
|
||||
pipVolumeIcon1: icon {{ "player_volume_small", mediaviewPipControlsFg }};
|
||||
pipVolumeIcon1Over: icon {{ "player_volume_small", mediaviewPipControlsFgOver }};
|
||||
pipVolumeIcon2: icon {{ "player_volume_on", mediaviewPipControlsFg }};
|
||||
pipVolumeIcon2Over: icon {{ "player_volume_on", mediaviewPipControlsFgOver }};
|
||||
|
||||
speedSliderDividerSize: size(2px, 8px);
|
||||
|
|
|
@ -318,6 +318,7 @@ void OverlayWidget::RendererGL::paintTransformedStaticContent(
|
|||
_rgbaSize = image.size();
|
||||
}
|
||||
}
|
||||
program->setUniformValue("s_texture", GLint(0));
|
||||
|
||||
paintTransformedContent(&*program, geometry);
|
||||
}
|
||||
|
@ -325,12 +326,6 @@ void OverlayWidget::RendererGL::paintTransformedStaticContent(
|
|||
void OverlayWidget::RendererGL::paintTransformedContent(
|
||||
not_null<QOpenGLShaderProgram*> program,
|
||||
ContentGeometry geometry) {
|
||||
auto texCoords = std::array<std::array<GLfloat, 2>, 4> { {
|
||||
{ { 0.f, 1.f } },
|
||||
{ { 1.f, 1.f } },
|
||||
{ { 1.f, 0.f } },
|
||||
{ { 0.f, 0.f } },
|
||||
} };
|
||||
const auto rect = transformRect(geometry.rect);
|
||||
const auto centerx = rect.x() + rect.width() / 2;
|
||||
const auto centery = rect.y() + rect.height() / 2;
|
||||
|
@ -350,22 +345,21 @@ void OverlayWidget::RendererGL::paintTransformedContent(
|
|||
const auto bottomleft = rotated(rect.left(), rect.bottom());
|
||||
const GLfloat coords[] = {
|
||||
topleft[0], topleft[1],
|
||||
texCoords[0][0], texCoords[0][1],
|
||||
0.f, 1.f,
|
||||
|
||||
topright[0], topright[1],
|
||||
texCoords[1][0], texCoords[1][1],
|
||||
1.f, 1.f,
|
||||
|
||||
bottomright[0], bottomright[1],
|
||||
texCoords[2][0], texCoords[2][1],
|
||||
1.f, 0.f,
|
||||
|
||||
bottomleft[0], bottomleft[1],
|
||||
texCoords[3][0], texCoords[3][1],
|
||||
0.f, 0.f,
|
||||
};
|
||||
|
||||
_contentBuffer->write(0, coords, sizeof(coords));
|
||||
|
||||
program->setUniformValue("viewport", _uniformViewport);
|
||||
program->setUniformValue("s_texture", GLint(0));
|
||||
|
||||
toggleBlending(false);
|
||||
FillTexturedRectangle(*_f, &*program);
|
||||
|
|
|
@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#pragma once
|
||||
|
||||
#include "media/view/media_view_overlay_renderer.h"
|
||||
#include "ui/gl/gl_surface.h"
|
||||
#include "ui/gl/gl_image.h"
|
||||
#include "ui/gl/gl_primitives.h"
|
||||
|
||||
|
@ -74,7 +73,7 @@ private:
|
|||
void paintCaption(QRect outer, float64 opacity) override;
|
||||
void paintGroupThumbs(QRect outer, float64 opacity) override;
|
||||
|
||||
void invalidate() override;
|
||||
void invalidate();
|
||||
|
||||
void paintUsingRaster(
|
||||
Ui::GL::Image &image,
|
||||
|
|
|
@ -188,7 +188,4 @@ void OverlayWidget::RendererSW::paintGroupThumbs(
|
|||
}
|
||||
}
|
||||
|
||||
void OverlayWidget::RendererSW::invalidate() {
|
||||
}
|
||||
|
||||
} // namespace Media::View
|
||||
|
|
|
@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#pragma once
|
||||
|
||||
#include "media/view/media_view_overlay_renderer.h"
|
||||
#include "ui/gl/gl_surface.h"
|
||||
|
||||
namespace Media::View {
|
||||
|
||||
|
@ -51,8 +50,6 @@ private:
|
|||
void paintCaption(QRect outer, float64 opacity) override;
|
||||
void paintGroupThumbs(QRect outer, float64 opacity) override;
|
||||
|
||||
void invalidate() override;
|
||||
|
||||
[[nodiscard]] static QRect TransformRect(QRectF geometry, int rotation);
|
||||
|
||||
const not_null<OverlayWidget*> _owner;
|
||||
|
|
|
@ -38,8 +38,6 @@ public:
|
|||
virtual void paintCaption(QRect outer, float64 opacity) = 0;
|
||||
virtual void paintGroupThumbs(QRect outer, float64 opacity) = 0;
|
||||
|
||||
virtual void invalidate() = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Media::View
|
||||
|
|
|
@ -3232,10 +3232,9 @@ Ui::GL::ChosenRenderer OverlayWidget::chooseRenderer(
|
|||
: capabilities.transparency;
|
||||
LOG(("OpenGL: %1 (OverlayWidget)").arg(Logs::b(use)));
|
||||
if (use) {
|
||||
auto renderer = std::make_unique<RendererGL>(this);
|
||||
_opengl = true;
|
||||
return {
|
||||
.renderer = std::move(renderer),
|
||||
.renderer = std::make_unique<RendererGL>(this),
|
||||
.backend = Ui::GL::Backend::OpenGL,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "media/streaming/media_streaming_document.h"
|
||||
#include "media/streaming/media_streaming_utility.h"
|
||||
#include "media/view/media_view_playback_progress.h"
|
||||
#include "media/view/media_view_pip_opengl.h"
|
||||
#include "media/view/media_view_pip_raster.h"
|
||||
#include "media/audio/media_audio.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
|
@ -260,54 +262,6 @@ constexpr auto kMsInSecond = 1000;
|
|||
return result;
|
||||
}
|
||||
|
||||
Streaming::FrameRequest UnrotateRequest(
|
||||
const Streaming::FrameRequest &request,
|
||||
int rotation) {
|
||||
if (!rotation) {
|
||||
return request;
|
||||
}
|
||||
const auto unrotatedCorner = [&](RectPart corner) {
|
||||
if (!(request.corners & corner)) {
|
||||
return RectPart(0);
|
||||
}
|
||||
switch (corner) {
|
||||
case RectPart::TopLeft:
|
||||
return (rotation == 90)
|
||||
? RectPart::BottomLeft
|
||||
: (rotation == 180)
|
||||
? RectPart::BottomRight
|
||||
: RectPart::TopRight;
|
||||
case RectPart::TopRight:
|
||||
return (rotation == 90)
|
||||
? RectPart::TopLeft
|
||||
: (rotation == 180)
|
||||
? RectPart::BottomLeft
|
||||
: RectPart::BottomRight;
|
||||
case RectPart::BottomRight:
|
||||
return (rotation == 90)
|
||||
? RectPart::TopRight
|
||||
: (rotation == 180)
|
||||
? RectPart::TopLeft
|
||||
: RectPart::BottomLeft;
|
||||
case RectPart::BottomLeft:
|
||||
return (rotation == 90)
|
||||
? RectPart::BottomRight
|
||||
: (rotation == 180)
|
||||
? RectPart::TopRight
|
||||
: RectPart::TopLeft;
|
||||
}
|
||||
Unexpected("Corner in rotateCorner.");
|
||||
};
|
||||
auto result = request;
|
||||
result.outer = FlipSizeByRotation(request.outer, rotation);
|
||||
result.resize = FlipSizeByRotation(request.resize, rotation);
|
||||
result.corners = unrotatedCorner(RectPart::TopLeft)
|
||||
| unrotatedCorner(RectPart::TopRight)
|
||||
| unrotatedCorner(RectPart::BottomRight)
|
||||
| unrotatedCorner(RectPart::BottomLeft);
|
||||
return result;
|
||||
}
|
||||
|
||||
Qt::Edges RectPartToQtEdges(RectPart rectPart) {
|
||||
switch (rectPart) {
|
||||
case RectPart::TopLeft:
|
||||
|
@ -373,45 +327,9 @@ QImage RotateFrameImage(QImage image, int rotation) {
|
|||
|
||||
PipPanel::PipPanel(
|
||||
QWidget *parent,
|
||||
Fn<void(QPainter&, FrameRequest, bool)> paint)
|
||||
: _content(Ui::GL::CreateSurface(
|
||||
[=](Ui::GL::Capabilities capabilities) {
|
||||
return chooseRenderer(capabilities);
|
||||
}))
|
||||
, _parent(parent)
|
||||
, _paint(std::move(paint)) {
|
||||
}
|
||||
|
||||
Ui::GL::ChosenRenderer PipPanel::chooseRenderer(
|
||||
Ui::GL::Capabilities capabilities) {
|
||||
class Renderer : public Ui::GL::Renderer {
|
||||
public:
|
||||
Renderer(not_null<PipPanel*> owner) : _owner(owner) {
|
||||
}
|
||||
|
||||
void paintFallback(
|
||||
Painter &&p,
|
||||
const QRegion &clip,
|
||||
Ui::GL::Backend backend) override {
|
||||
_owner->paint(
|
||||
p,
|
||||
clip,
|
||||
backend == Ui::GL::Backend::OpenGL);
|
||||
}
|
||||
|
||||
private:
|
||||
const not_null<PipPanel*> _owner;
|
||||
|
||||
};
|
||||
|
||||
const auto use = Platform::IsMac()
|
||||
? true
|
||||
: capabilities.transparency;
|
||||
LOG(("OpenGL: %1 (PipPanel)").arg(Logs::b(use)));
|
||||
return {
|
||||
.renderer = std::make_unique<Renderer>(this),
|
||||
.backend = (use ? Ui::GL::Backend::OpenGL : Ui::GL::Backend::Raster),
|
||||
};
|
||||
Fn<Ui::GL::ChosenRenderer(Ui::GL::Capabilities)> renderer)
|
||||
: _content(Ui::GL::CreateSurface(std::move(renderer)))
|
||||
, _parent(parent) {
|
||||
}
|
||||
|
||||
void PipPanel::init() {
|
||||
|
@ -480,6 +398,10 @@ RectParts PipPanel::attached() const {
|
|||
return _attached;
|
||||
}
|
||||
|
||||
bool PipPanel::useTransparency() const {
|
||||
return _useTransparency;
|
||||
}
|
||||
|
||||
void PipPanel::setDragDisabled(bool disabled) {
|
||||
_dragDisabled = disabled;
|
||||
if (_dragState) {
|
||||
|
@ -646,39 +568,6 @@ void PipPanel::setGeometry(QRect geometry) {
|
|||
widget()->setGeometry(geometry);
|
||||
}
|
||||
|
||||
void PipPanel::paint(QPainter &p, const QRegion &clip, bool opengl) {
|
||||
if (_useTransparency && opengl) {
|
||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
for (const auto rect : clip) {
|
||||
p.fillRect(rect, Qt::transparent);
|
||||
}
|
||||
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
}
|
||||
|
||||
auto request = FrameRequest();
|
||||
const auto inner = this->inner();
|
||||
request.resize = request.outer = inner.size() * style::DevicePixelRatio();
|
||||
request.corners = RectPart(0)
|
||||
| ((_attached & (RectPart::Left | RectPart::Top))
|
||||
? RectPart(0)
|
||||
: RectPart::TopLeft)
|
||||
| ((_attached & (RectPart::Top | RectPart::Right))
|
||||
? RectPart(0)
|
||||
: RectPart::TopRight)
|
||||
| ((_attached & (RectPart::Right | RectPart::Bottom))
|
||||
? RectPart(0)
|
||||
: RectPart::BottomRight)
|
||||
| ((_attached & (RectPart::Bottom | RectPart::Left))
|
||||
? RectPart(0)
|
||||
: RectPart::BottomLeft);
|
||||
request.radius = ImageRoundRadius::Large;
|
||||
if (_useTransparency) {
|
||||
const auto sides = RectPart::AllSides & ~_attached;
|
||||
Ui::Shadow::paint(p, inner, widget()->width(), st::callShadow);
|
||||
}
|
||||
_paint(p, request, opengl);
|
||||
}
|
||||
|
||||
void PipPanel::handleMousePress(QPoint position, Qt::MouseButton button) {
|
||||
if (button != Qt::LeftButton) {
|
||||
return;
|
||||
|
@ -942,15 +831,14 @@ Pip::Pip(
|
|||
, _instance(std::move(shared), [=] { waitingAnimationCallback(); })
|
||||
, _panel(
|
||||
_delegate->pipParentWidget(),
|
||||
[=](QPainter &p, const FrameRequest &request, bool opengl) {
|
||||
paint(p, request, opengl);
|
||||
[=](Ui::GL::Capabilities capabilities) {
|
||||
return chooseRenderer(capabilities);
|
||||
})
|
||||
, _playbackProgress(std::make_unique<PlaybackProgress>())
|
||||
, _rotation(data->owner().mediaRotation().get(data))
|
||||
, _lastPositiveVolume((Core::App().settings().videoVolume() > 0.)
|
||||
? Core::App().settings().videoVolume()
|
||||
: Core::Settings::kDefaultVolume)
|
||||
, _roundRect(ImageRoundRadius::Large, st::radialBg)
|
||||
, _closeAndContinue(std::move(closeAndContinue))
|
||||
, _destroy(std::move(destroy)) {
|
||||
setupPanel();
|
||||
|
@ -1041,19 +929,19 @@ void Pip::setOverState(OverState state) {
|
|||
if (_over == state) {
|
||||
return;
|
||||
}
|
||||
const auto was = _over;
|
||||
const auto wasShown = ResolveShownOver(_over);
|
||||
_over = state;
|
||||
const auto nowShown = (_over != OverState::None);
|
||||
if ((was != OverState::None) != nowShown) {
|
||||
const auto nowAreShown = (ResolveShownOver(_over) != OverState::None);
|
||||
if ((wasShown != OverState::None) != nowAreShown) {
|
||||
_controlsShown.start(
|
||||
[=] { _panel.update(); },
|
||||
nowShown ? 0. : 1.,
|
||||
nowShown ? 1. : 0.,
|
||||
nowAreShown ? 0. : 1.,
|
||||
nowAreShown ? 1. : 0.,
|
||||
st::fadeWrapDuration,
|
||||
anim::linear);
|
||||
}
|
||||
if (!_pressed) {
|
||||
updateActiveState(was);
|
||||
updateActiveState(wasShown);
|
||||
}
|
||||
_panel.update();
|
||||
}
|
||||
|
@ -1062,27 +950,29 @@ void Pip::setPressedState(std::optional<OverState> state) {
|
|||
if (_pressed == state) {
|
||||
return;
|
||||
}
|
||||
const auto was = activeState();
|
||||
const auto wasShown = shownActiveState();
|
||||
_pressed = state;
|
||||
updateActiveState(was);
|
||||
updateActiveState(wasShown);
|
||||
}
|
||||
|
||||
Pip::OverState Pip::activeState() const {
|
||||
return _pressed.value_or(_over);
|
||||
Pip::OverState Pip::shownActiveState() const {
|
||||
return ResolveShownOver(_pressed.value_or(_over));
|
||||
}
|
||||
|
||||
float64 Pip::activeValue(const Button &button) const {
|
||||
return button.active.value((activeState() == button.state) ? 1. : 0.);
|
||||
const auto shownState = ResolveShownOver(button.state);
|
||||
return button.active.value((shownActiveState() == shownState) ? 1. : 0.);
|
||||
}
|
||||
|
||||
void Pip::updateActiveState(OverState was) {
|
||||
void Pip::updateActiveState(OverState wasShown) {
|
||||
const auto check = [&](Button &button) {
|
||||
const auto now = (activeState() == button.state);
|
||||
if ((was == button.state) != now) {
|
||||
const auto shownState = ResolveShownOver(button.state);
|
||||
const auto nowIsShown = (shownActiveState() == shownState);
|
||||
if ((wasShown == shownState) != nowIsShown) {
|
||||
button.active.start(
|
||||
[=, &button] { _panel.widget()->update(button.icon); },
|
||||
now ? 0. : 1.,
|
||||
now ? 1. : 0.,
|
||||
nowIsShown ? 0. : 1.,
|
||||
nowIsShown ? 1. : 0.,
|
||||
st::fadeWrapDuration,
|
||||
anim::linear);
|
||||
}
|
||||
|
@ -1095,6 +985,12 @@ void Pip::updateActiveState(OverState was) {
|
|||
check(_volumeController);
|
||||
}
|
||||
|
||||
Pip::OverState Pip::ResolveShownOver(OverState state) {
|
||||
return (state == OverState::VolumeController)
|
||||
? OverState::VolumeToggle
|
||||
: state;
|
||||
}
|
||||
|
||||
void Pip::handleMousePress(QPoint position, Qt::MouseButton button) {
|
||||
const auto weak = Ui::MakeWeak(_panel.widget());
|
||||
const auto guard = gsl::finally([&] {
|
||||
|
@ -1256,9 +1152,9 @@ void Pip::setupButtons() {
|
|||
|
||||
const auto volumeSkip = st::pipPlaybackSkip;
|
||||
const auto volumeHeight = 2 * volumeSkip + st::pipPlaybackWide;
|
||||
const auto volumeToggleWidth = st::mediaviewVolumeIcon0.width()
|
||||
const auto volumeToggleWidth = st::pipVolumeIcon0.width()
|
||||
+ 2 * skip;
|
||||
const auto volumeToggleHeight = st::mediaviewVolumeIcon0.height()
|
||||
const auto volumeToggleHeight = st::pipVolumeIcon0.height()
|
||||
+ 2 * skip;
|
||||
const auto volumeWidth = (((st::mediaviewVolumeWidth + 2 * skip)
|
||||
+ _close.area.width()
|
||||
|
@ -1273,7 +1169,7 @@ void Pip::setupButtons() {
|
|||
volumeHeight);
|
||||
_volumeToggle.area = QRect(
|
||||
_volumeController.area.x()
|
||||
- st::mediaviewVolumeIcon0.width()
|
||||
- st::pipVolumeIcon0.width()
|
||||
- skip,
|
||||
rect.y(),
|
||||
volumeToggleWidth,
|
||||
|
@ -1346,83 +1242,70 @@ void Pip::setupStreaming() {
|
|||
updatePlaybackState();
|
||||
}
|
||||
|
||||
void Pip::paint(QPainter &p, FrameRequest request, bool opengl) {
|
||||
const auto image = videoFrameForDirectPaint(
|
||||
UnrotateRequest(request, _rotation));
|
||||
const auto inner = _panel.inner();
|
||||
const auto rect = QRect{
|
||||
inner.topLeft(),
|
||||
request.outer / style::DevicePixelRatio()
|
||||
Ui::GL::ChosenRenderer Pip::chooseRenderer(
|
||||
Ui::GL::Capabilities capabilities) {
|
||||
const auto use = Platform::IsMac()
|
||||
? true
|
||||
: capabilities.transparency;
|
||||
LOG(("OpenGL: %1 (PipPanel)").arg(Logs::b(use)));
|
||||
if (use) {
|
||||
_opengl = true;
|
||||
return {
|
||||
.renderer = std::make_unique<RendererGL>(this),
|
||||
.backend = Ui::GL::Backend::OpenGL,
|
||||
};
|
||||
}
|
||||
return {
|
||||
.renderer = std::make_unique<RendererSW>(this),
|
||||
.backend = Ui::GL::Backend::Raster,
|
||||
};
|
||||
if (UsePainterRotation(_rotation, opengl)) {
|
||||
if (_rotation) {
|
||||
p.save();
|
||||
p.rotate(_rotation);
|
||||
}
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.drawImage(RotatedRect(rect, _rotation), image);
|
||||
if (_rotation) {
|
||||
p.restore();
|
||||
}
|
||||
} else {
|
||||
p.drawImage(rect, RotateFrameImage(image, _rotation));
|
||||
}
|
||||
if (canUseVideoFrame()) {
|
||||
_instance.markFrameShown();
|
||||
}
|
||||
paintRadialLoading(p);
|
||||
paintControls(p);
|
||||
}
|
||||
|
||||
void Pip::paintControls(QPainter &p) const {
|
||||
const auto shown = _controlsShown.value(
|
||||
void Pip::paint(not_null<Renderer*> renderer) const {
|
||||
const auto controlsShown = _controlsShown.value(
|
||||
(_over != OverState::None) ? 1. : 0.);
|
||||
if (!shown) {
|
||||
return;
|
||||
const auto geometry = ContentGeometry{
|
||||
.inner = _panel.inner(),
|
||||
.attached = (_panel.useTransparency()
|
||||
? _panel.attached()
|
||||
: RectPart::AllSides),
|
||||
.fade = controlsShown,
|
||||
.outer = _panel.widget()->size(),
|
||||
.rotation = _rotation,
|
||||
.useTransparency = _panel.useTransparency(),
|
||||
};
|
||||
if (canUseVideoFrame()) {
|
||||
renderer->paintTransformedVideoFrame(geometry);
|
||||
_instance.markFrameShown();
|
||||
} else {
|
||||
renderer->paintTransformedStaticContent(staticContent(), geometry);
|
||||
}
|
||||
if (_instance.waitingShown()) {
|
||||
renderer->paintRadialLoading(countRadialRect(), controlsShown);
|
||||
}
|
||||
if (controlsShown > 0) {
|
||||
paintButtons(renderer, controlsShown);
|
||||
paintPlayback(renderer, controlsShown);
|
||||
paintVolumeController(renderer, controlsShown);
|
||||
}
|
||||
p.setOpacity(shown);
|
||||
paintFade(p);
|
||||
paintButtons(p);
|
||||
paintPlayback(p);
|
||||
paintPlaybackTexts(p);
|
||||
paintVolumeController(p);
|
||||
}
|
||||
|
||||
void Pip::paintFade(QPainter &p) const {
|
||||
using Part = RectPart;
|
||||
const auto sides = _panel.attached();
|
||||
const auto rounded = RectPart(0)
|
||||
| ((sides & (Part::Top | Part::Left)) ? Part(0) : Part::TopLeft)
|
||||
| ((sides & (Part::Top | Part::Right)) ? Part(0) : Part::TopRight)
|
||||
| ((sides & (Part::Bottom | Part::Right))
|
||||
? Part(0)
|
||||
: Part::BottomRight)
|
||||
| ((sides & (Part::Bottom | Part::Left))
|
||||
? Part(0)
|
||||
: Part::BottomLeft);
|
||||
_roundRect.paintSomeRounded(
|
||||
p,
|
||||
_panel.inner(),
|
||||
rounded | Part::NoTopBottom | Part::Top | Part::Bottom);
|
||||
}
|
||||
|
||||
void Pip::paintButtons(QPainter &p) const {
|
||||
const auto opacity = p.opacity();
|
||||
void Pip::paintButtons(not_null<Renderer*> renderer, float64 shown) const {
|
||||
const auto outer = _panel.widget()->width();
|
||||
const auto drawOne = [&](
|
||||
const Button &button,
|
||||
const style::icon &icon,
|
||||
const style::icon &iconOver) {
|
||||
const auto over = activeValue(button);
|
||||
if (over < 1.) {
|
||||
icon.paint(p, button.icon.x(), button.icon.y(), outer);
|
||||
}
|
||||
if (over > 0.) {
|
||||
p.setOpacity(over * opacity);
|
||||
iconOver.paint(p, button.icon.x(), button.icon.y(), outer);
|
||||
p.setOpacity(opacity);
|
||||
}
|
||||
renderer->paintButton(
|
||||
button,
|
||||
outer,
|
||||
shown,
|
||||
activeValue(button),
|
||||
icon,
|
||||
iconOver);
|
||||
};
|
||||
|
||||
renderer->paintButtonsStart();
|
||||
drawOne(
|
||||
_play,
|
||||
_showPause ? st::pipPauseIcon : st::pipPlayIcon,
|
||||
|
@ -1433,47 +1316,73 @@ void Pip::paintButtons(QPainter &p) const {
|
|||
if (volume <= 0.) {
|
||||
drawOne(
|
||||
_volumeToggle,
|
||||
st::mediaviewVolumeIcon0,
|
||||
st::mediaviewVolumeIcon0Over);
|
||||
st::pipVolumeIcon0,
|
||||
st::pipVolumeIcon0Over);
|
||||
} else if (volume < 1 / 2.) {
|
||||
drawOne(
|
||||
_volumeToggle,
|
||||
st::mediaviewVolumeIcon1,
|
||||
st::mediaviewVolumeIcon1Over);
|
||||
st::pipVolumeIcon1,
|
||||
st::pipVolumeIcon1Over);
|
||||
} else {
|
||||
drawOne(
|
||||
_volumeToggle,
|
||||
st::mediaviewVolumeIcon2,
|
||||
st::mediaviewVolumeIcon2Over);
|
||||
st::pipVolumeIcon2,
|
||||
st::pipVolumeIcon2Over);
|
||||
}
|
||||
}
|
||||
|
||||
void Pip::paintPlayback(QPainter &p) const {
|
||||
void Pip::paintPlayback(not_null<Renderer*> renderer, float64 shown) const {
|
||||
const auto outer = QRect(
|
||||
_playback.icon.x(),
|
||||
_playback.icon.y() - st::pipPlaybackFont->height,
|
||||
_playback.icon.width(),
|
||||
st::pipPlaybackFont->height + _playback.icon.height());
|
||||
renderer->paintPlayback(outer, shown);
|
||||
}
|
||||
|
||||
void Pip::paintPlaybackContent(
|
||||
QPainter &p,
|
||||
QRect outer,
|
||||
float64 shown) const {
|
||||
p.setOpacity(shown);
|
||||
paintPlaybackProgress(p, outer);
|
||||
paintPlaybackTexts(p, outer);
|
||||
}
|
||||
|
||||
void Pip::paintPlaybackProgress(QPainter &p, QRect outer) const {
|
||||
const auto radius = _playback.icon.height() / 2;
|
||||
const auto progress = _playbackProgress->value();
|
||||
const auto active = activeValue(_playback);
|
||||
const auto height = anim::interpolate(
|
||||
st::pipPlaybackWidth,
|
||||
_playback.icon.height(),
|
||||
activeValue(_playback));
|
||||
active);
|
||||
const auto rect = QRect(
|
||||
_playback.icon.x(),
|
||||
_playback.icon.y() + _playback.icon.height() - height,
|
||||
_playback.icon.width(),
|
||||
outer.x(),
|
||||
(outer.y()
|
||||
+ st::pipPlaybackFont->height
|
||||
+ _playback.icon.height()
|
||||
- height),
|
||||
outer.width(),
|
||||
height);
|
||||
|
||||
paintProgressBar(p, rect, progress, radius);
|
||||
paintProgressBar(p, rect, progress, radius, active);
|
||||
}
|
||||
|
||||
void Pip::paintProgressBar(
|
||||
QPainter &p,
|
||||
const QRect &rect,
|
||||
float64 progress,
|
||||
int radius) const {
|
||||
int radius,
|
||||
float64 active) const {
|
||||
const auto done = int(std::round(rect.width() * progress));
|
||||
PainterHighQualityEnabler hq(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
if (done > 0) {
|
||||
p.setBrush(st::mediaviewPipPlaybackActive);
|
||||
p.setBrush(anim::brush(
|
||||
st::mediaviewPipControlsFg,
|
||||
st::mediaviewPipPlaybackActive,
|
||||
active));
|
||||
p.setClipRect(rect.x(), rect.y(), done, rect.height());
|
||||
p.drawRoundedRect(
|
||||
rect.x(),
|
||||
|
@ -1502,14 +1411,17 @@ void Pip::paintProgressBar(
|
|||
p.setClipping(false);
|
||||
}
|
||||
|
||||
void Pip::paintPlaybackTexts(QPainter &p) const {
|
||||
const auto left = _playback.area.x() + st::pipPlaybackTextSkip;
|
||||
const auto right = _playback.area.x()
|
||||
void Pip::paintPlaybackTexts(QPainter &p, QRect outer) const {
|
||||
const auto left = outer.x()
|
||||
- _playback.icon.x()
|
||||
+ _playback.area.x()
|
||||
+ st::pipPlaybackTextSkip;
|
||||
const auto right = outer.x()
|
||||
- _playback.icon.x()
|
||||
+ _playback.area.x()
|
||||
+ _playback.area.width()
|
||||
- st::pipPlaybackTextSkip;
|
||||
const auto top = _playback.icon.y()
|
||||
- st::pipPlaybackFont->height
|
||||
+ st::pipPlaybackFont->ascent;
|
||||
const auto top = outer.y() + st::pipPlaybackFont->ascent;
|
||||
|
||||
p.setFont(st::pipPlaybackFont);
|
||||
p.setPen(st::mediaviewPipControlsFgOver);
|
||||
|
@ -1517,23 +1429,35 @@ void Pip::paintPlaybackTexts(QPainter &p) const {
|
|||
p.drawText(right - _timeLeftWidth, top, _timeLeft);
|
||||
}
|
||||
|
||||
void Pip::paintVolumeController(QPainter &p) const {
|
||||
void Pip::paintVolumeController(
|
||||
not_null<Renderer*> renderer,
|
||||
float64 shown) const {
|
||||
if (!_volumeController.icon.width()) {
|
||||
return;
|
||||
}
|
||||
renderer->paintVolumeController(_volumeController.icon, shown);
|
||||
}
|
||||
|
||||
void Pip::paintVolumeControllerContent(
|
||||
QPainter &p,
|
||||
QRect outer,
|
||||
float64 shown) const {
|
||||
p.setOpacity(shown);
|
||||
|
||||
const auto radius = _volumeController.icon.height() / 2;
|
||||
const auto volume = Core::App().settings().videoVolume();
|
||||
const auto active = activeValue(_volumeController);
|
||||
const auto height = anim::interpolate(
|
||||
st::pipPlaybackWidth,
|
||||
_volumeController.icon.height(),
|
||||
activeValue(_volumeController));
|
||||
active);
|
||||
const auto rect = QRect(
|
||||
_volumeController.icon.x(),
|
||||
_volumeController.icon.y() + radius - height / 2,
|
||||
_volumeController.icon.width(),
|
||||
outer.x(),
|
||||
outer.y() + radius - height / 2,
|
||||
outer.width(),
|
||||
height);
|
||||
|
||||
paintProgressBar(p, rect, volume, radius);
|
||||
paintProgressBar(p, rect, volume, radius, active);
|
||||
}
|
||||
|
||||
void Pip::handleStreamingUpdate(Streaming::Update &&update) {
|
||||
|
@ -1649,12 +1573,19 @@ bool Pip::canUseVideoFrame() const {
|
|||
}
|
||||
|
||||
QImage Pip::videoFrame(const FrameRequest &request) const {
|
||||
if (canUseVideoFrame()) {
|
||||
_preparedCoverStorage = QImage();
|
||||
return _instance.frame(request);
|
||||
}
|
||||
const auto &cover = _instance.info().video.cover;
|
||||
Expects(canUseVideoFrame());
|
||||
|
||||
return _instance.frame(request);
|
||||
}
|
||||
|
||||
Streaming::FrameWithInfo Pip::videoFrameWithInfo() const {
|
||||
Expects(canUseVideoFrame());
|
||||
|
||||
return _instance.frameWithInfo();
|
||||
}
|
||||
|
||||
QImage Pip::staticContent() const {
|
||||
const auto &cover = _instance.info().video.cover;
|
||||
const auto media = _data->activeMediaView();
|
||||
const auto use = media
|
||||
? media
|
||||
|
@ -1677,114 +1608,32 @@ QImage Pip::videoFrame(const FrameRequest &request) const {
|
|||
: blurred
|
||||
? ThumbState::Inline
|
||||
: ThumbState::Empty;
|
||||
if (_preparedCoverStorage.isNull()
|
||||
|| _preparedCoverRequest != request
|
||||
|| _preparedCoverState < state) {
|
||||
_preparedCoverRequest = request;
|
||||
_preparedCoverState = state;
|
||||
if (state == ThumbState::Cover) {
|
||||
_preparedCoverStorage = Streaming::PrepareByRequest(
|
||||
_instance.info().video.cover,
|
||||
false,
|
||||
_instance.info().video.rotation,
|
||||
request,
|
||||
if (!_preparedCoverStorage.isNull() && _preparedCoverState >= state) {
|
||||
return _preparedCoverStorage;
|
||||
}
|
||||
_preparedCoverState = state;
|
||||
if (state == ThumbState::Cover) {
|
||||
_preparedCoverStorage = _instance.info().video.cover;
|
||||
} else {
|
||||
_preparedCoverStorage = (good
|
||||
? good
|
||||
: thumb
|
||||
? thumb
|
||||
: blurred
|
||||
? blurred
|
||||
: Image::BlankMedia().get())->original();
|
||||
if (!good) {
|
||||
_preparedCoverStorage = Images::prepareBlur(
|
||||
std::move(_preparedCoverStorage));
|
||||
} else if (!request.resize.isEmpty()) {
|
||||
using Option = Images::Option;
|
||||
const auto options = Option::Smooth
|
||||
| (good ? Option(0) : Option::Blurred)
|
||||
| Option::RoundedLarge
|
||||
| ((request.corners & RectPart::TopLeft)
|
||||
? Option::RoundedTopLeft
|
||||
: Option(0))
|
||||
| ((request.corners & RectPart::TopRight)
|
||||
? Option::RoundedTopRight
|
||||
: Option(0))
|
||||
| ((request.corners & RectPart::BottomRight)
|
||||
? Option::RoundedBottomRight
|
||||
: Option(0))
|
||||
| ((request.corners & RectPart::BottomLeft)
|
||||
? Option::RoundedBottomLeft
|
||||
: Option(0));
|
||||
_preparedCoverStorage = (good
|
||||
? good
|
||||
: thumb
|
||||
? thumb
|
||||
: blurred
|
||||
? blurred
|
||||
: Image::BlankMedia().get())->pixNoCache(
|
||||
request.resize.width(),
|
||||
request.resize.height(),
|
||||
options,
|
||||
request.outer.width(),
|
||||
request.outer.height()).toImage();
|
||||
}
|
||||
}
|
||||
return _preparedCoverStorage;
|
||||
}
|
||||
|
||||
QImage Pip::videoFrameForDirectPaint(const FrameRequest &request) const {
|
||||
const auto result = videoFrame(request);
|
||||
|
||||
#ifdef USE_OPENGL_PIP_WIDGET
|
||||
const auto bytesPerLine = result.bytesPerLine();
|
||||
if (bytesPerLine == result.width() * 4) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// On macOS 10.8+ we use QOpenGLWidget as OverlayWidget base class.
|
||||
// The OpenGL painter can't paint textures where byte data is with strides.
|
||||
// So in that case we prepare a compact copy of the frame to render.
|
||||
//
|
||||
// See Qt commit ed557c037847e343caa010562952b398f806adcd
|
||||
//
|
||||
auto &cache = _frameForDirectPaint;
|
||||
if (cache.size() != result.size()) {
|
||||
cache = QImage(result.size(), result.format());
|
||||
}
|
||||
const auto height = result.height();
|
||||
const auto line = cache.bytesPerLine();
|
||||
Assert(line == result.width() * 4);
|
||||
Assert(line < bytesPerLine);
|
||||
|
||||
auto from = result.bits();
|
||||
auto to = cache.bits();
|
||||
for (auto y = 0; y != height; ++y) {
|
||||
memcpy(to, from, line);
|
||||
to += line;
|
||||
from += bytesPerLine;
|
||||
}
|
||||
return cache;
|
||||
#endif // USE_OPENGL_PIP_WIDGET
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void Pip::paintRadialLoading(QPainter &p) const {
|
||||
const auto inner = countRadialRect();
|
||||
#ifdef USE_OPENGL_PIP_WIDGET
|
||||
{
|
||||
if (_radialCache.size() != inner.size() * cIntRetinaFactor()) {
|
||||
_radialCache = QImage(
|
||||
inner.size() * cIntRetinaFactor(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
_radialCache.setDevicePixelRatio(cRetinaFactor());
|
||||
}
|
||||
_radialCache.fill(Qt::transparent);
|
||||
|
||||
Painter q(&_radialCache);
|
||||
paintRadialLoadingContent(q, inner.translated(-inner.topLeft()));
|
||||
}
|
||||
p.drawImage(inner.topLeft(), _radialCache);
|
||||
#else // USE_OPENGL_PIP_WIDGET
|
||||
paintRadialLoadingContent(p, inner);
|
||||
#endif // USE_OPENGL_PIP_WIDGET
|
||||
}
|
||||
|
||||
void Pip::paintRadialLoadingContent(QPainter &p, const QRect &inner) const {
|
||||
if (!_instance.waitingShown()) {
|
||||
return;
|
||||
}
|
||||
void Pip::paintRadialLoadingContent(
|
||||
QPainter &p,
|
||||
const QRect &inner,
|
||||
QColor fg) const {
|
||||
const auto arc = inner.marginsRemoved(QMargins(
|
||||
st::radialLine,
|
||||
st::radialLine,
|
||||
|
@ -1804,7 +1653,7 @@ void Pip::paintRadialLoadingContent(QPainter &p, const QRect &inner) const {
|
|||
arc.topLeft(),
|
||||
arc.size(),
|
||||
_panel.widget()->width(),
|
||||
st::radialFg,
|
||||
fg,
|
||||
st::radialLine);
|
||||
}
|
||||
|
||||
|
|
|
@ -50,11 +50,10 @@ public:
|
|||
QRect geometry;
|
||||
QRect screen;
|
||||
};
|
||||
using FrameRequest = Streaming::FrameRequest;
|
||||
|
||||
PipPanel(
|
||||
QWidget *parent,
|
||||
Fn<void(QPainter&, FrameRequest, bool)> paint);
|
||||
Fn<Ui::GL::ChosenRenderer(Ui::GL::Capabilities)> renderer);
|
||||
void init();
|
||||
|
||||
[[nodiscard]] not_null<QWidget*> widget() const;
|
||||
|
@ -68,6 +67,8 @@ public:
|
|||
void setPosition(Position position);
|
||||
[[nodiscard]] QRect inner() const;
|
||||
[[nodiscard]] RectParts attached() const;
|
||||
[[nodiscard]] bool useTransparency() const;
|
||||
|
||||
void setDragDisabled(bool disabled);
|
||||
[[nodiscard]] bool dragging() const;
|
||||
|
||||
|
@ -78,8 +79,6 @@ public:
|
|||
[[nodiscard]] rpl::producer<> saveGeometryRequests() const;
|
||||
|
||||
private:
|
||||
void paint(QPainter &p, const QRegion &clip, bool opengl);
|
||||
|
||||
void setPositionDefault();
|
||||
void setPositionOnScreen(Position position, QRect available);
|
||||
|
||||
|
@ -92,12 +91,8 @@ private:
|
|||
void moveAnimated(QPoint to);
|
||||
void updateDecorations();
|
||||
|
||||
[[nodiscard]] Ui::GL::ChosenRenderer chooseRenderer(
|
||||
Ui::GL::Capabilities capabilities);
|
||||
|
||||
const std::unique_ptr<Ui::RpWidgetWrap> _content;
|
||||
const QPointer<QWidget> _parent;
|
||||
Fn<void(QPainter&, FrameRequest, bool)> _paint;
|
||||
RectParts _attached = RectParts();
|
||||
RectParts _snapped = RectParts();
|
||||
QSize _ratio;
|
||||
|
@ -161,12 +156,26 @@ private:
|
|||
OverState state = OverState::None;
|
||||
Ui::Animations::Simple active;
|
||||
};
|
||||
struct ContentGeometry {
|
||||
QRect inner;
|
||||
RectParts attached = RectParts();
|
||||
float64 fade = 0.;
|
||||
QSize outer;
|
||||
int rotation = 0;
|
||||
bool useTransparency = false;
|
||||
};
|
||||
struct StaticContent {
|
||||
QImage image;
|
||||
bool blurred = false;
|
||||
};
|
||||
using FrameRequest = Streaming::FrameRequest;
|
||||
class Renderer;
|
||||
class RendererGL;
|
||||
class RendererSW;
|
||||
|
||||
void setupPanel();
|
||||
void setupButtons();
|
||||
void setupStreaming();
|
||||
void paint(QPainter &p, FrameRequest request, bool opengl);
|
||||
void playbackPauseResume();
|
||||
void volumeChanged(float64 volume);
|
||||
void volumeToggled();
|
||||
|
@ -182,16 +191,22 @@ private:
|
|||
|
||||
[[nodiscard]] bool canUseVideoFrame() const;
|
||||
[[nodiscard]] QImage videoFrame(const FrameRequest &request) const;
|
||||
[[nodiscard]] QImage videoFrameForDirectPaint(
|
||||
const FrameRequest &request) const;
|
||||
[[nodiscard]] Streaming::FrameWithInfo videoFrameWithInfo() const; // YUV
|
||||
[[nodiscard]] QImage staticContent() const;
|
||||
[[nodiscard]] OverState computeState(QPoint position) const;
|
||||
void setOverState(OverState state);
|
||||
void setPressedState(std::optional<OverState> state);
|
||||
[[nodiscard]] OverState activeState() const;
|
||||
[[nodiscard]] OverState shownActiveState() const;
|
||||
[[nodiscard]] float64 activeValue(const Button &button) const;
|
||||
void updateActiveState(OverState was);
|
||||
void updatePlaybackTexts(int64 position, int64 length, int64 frequency);
|
||||
|
||||
[[nodiscard]] static OverState ResolveShownOver(OverState state);
|
||||
|
||||
[[nodiscard]] Ui::GL::ChosenRenderer chooseRenderer(
|
||||
Ui::GL::Capabilities capabilities);
|
||||
void paint(not_null<Renderer*> renderer) const;
|
||||
|
||||
void handleMouseMove(QPoint position);
|
||||
void handleMousePress(QPoint position, Qt::MouseButton button);
|
||||
void handleMouseRelease(QPoint position, Qt::MouseButton button);
|
||||
|
@ -199,19 +214,28 @@ private:
|
|||
void handleLeave();
|
||||
void handleClose();
|
||||
|
||||
void paintControls(QPainter &p) const;
|
||||
void paintFade(QPainter &p) const;
|
||||
void paintButtons(QPainter &p) const;
|
||||
void paintPlayback(QPainter &p) const;
|
||||
void paintRadialLoadingContent(
|
||||
QPainter &p,
|
||||
const QRect &inner,
|
||||
QColor fg) const;
|
||||
void paintButtons(not_null<Renderer*> renderer, float64 shown) const;
|
||||
void paintPlayback(not_null<Renderer*> renderer, float64 shown) const;
|
||||
void paintPlaybackContent(QPainter &p, QRect outer, float64 shown) const;
|
||||
void paintPlaybackProgress(QPainter &p, QRect outer) const;
|
||||
void paintProgressBar(
|
||||
QPainter &p,
|
||||
const QRect &rect,
|
||||
float64 progress,
|
||||
int radius) const;
|
||||
void paintPlaybackTexts(QPainter &p) const;
|
||||
void paintVolumeController(QPainter &p) const;
|
||||
void paintRadialLoading(QPainter &p) const;
|
||||
void paintRadialLoadingContent(QPainter &p, const QRect &inner) const;
|
||||
int radius,
|
||||
float64 active) const;
|
||||
void paintPlaybackTexts(QPainter &p, QRect outer) const;
|
||||
void paintVolumeController(
|
||||
not_null<Renderer*> renderer,
|
||||
float64 shown) const;
|
||||
void paintVolumeControllerContent(
|
||||
QPainter &p,
|
||||
QRect outer,
|
||||
float64 shown) const;
|
||||
[[nodiscard]] QRect countRadialRect() const;
|
||||
|
||||
void seekUpdate(QPoint position);
|
||||
|
@ -222,6 +246,7 @@ private:
|
|||
not_null<DocumentData*> _data;
|
||||
FullMsgId _contextId;
|
||||
Streaming::Instance _instance;
|
||||
bool _opengl = false;
|
||||
PipPanel _panel;
|
||||
QSize _size;
|
||||
std::unique_ptr<PlaybackProgress> _playbackProgress;
|
||||
|
@ -246,19 +271,12 @@ private:
|
|||
Button _volumeToggle;
|
||||
Button _volumeController;
|
||||
Ui::Animations::Simple _controlsShown;
|
||||
Ui::RoundRect _roundRect;
|
||||
|
||||
FnMut<void()> _closeAndContinue;
|
||||
FnMut<void()> _destroy;
|
||||
|
||||
#if USE_OPENGL_PIP_WIDGET
|
||||
mutable QImage _frameForDirectPaint;
|
||||
mutable QImage _radialCache;
|
||||
#endif // USE_OPENGL_PIP_WIDGET
|
||||
|
||||
mutable QImage _preparedCoverStorage;
|
||||
mutable FrameRequest _preparedCoverRequest;
|
||||
mutable ThumbState _preparedCoverState;
|
||||
mutable ThumbState _preparedCoverState = ThumbState::Empty;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,781 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "media/view/media_view_pip_opengl.h"
|
||||
|
||||
#include "ui/gl/gl_shader.h"
|
||||
#include "ui/gl/gl_primitives.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "media/streaming/media_streaming_common.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "styles/style_media_view.h"
|
||||
#include "styles/style_calls.h" // st::callShadow.
|
||||
|
||||
namespace Media::View {
|
||||
namespace {
|
||||
|
||||
using namespace Ui::GL;
|
||||
|
||||
constexpr auto kRadialLoadingOffset = 4;
|
||||
constexpr auto kPlaybackOffset = kRadialLoadingOffset + 4;
|
||||
constexpr auto kVolumeControllerOffset = kPlaybackOffset + 4;
|
||||
constexpr auto kControlsOffset = kVolumeControllerOffset + 4;
|
||||
constexpr auto kControlValues = 4 * 4 + 2 * 4;
|
||||
|
||||
[[nodiscard]] ShaderPart FragmentAddControlOver() {
|
||||
return {
|
||||
.header = R"(
|
||||
varying vec2 o_texcoord;
|
||||
uniform float o_opacity;
|
||||
)",
|
||||
.body = R"(
|
||||
vec4 over = texture2D(s_texture, o_texcoord);
|
||||
result = result * (1. - o_opacity)
|
||||
+ vec4(over.b, over.g, over.r, over.a) * o_opacity;
|
||||
)",
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] ShaderPart FragmentApplyFade() {
|
||||
return {
|
||||
.header = R"(
|
||||
uniform vec4 fadeColor; // Premultiplied.
|
||||
)",
|
||||
.body = R"(
|
||||
result = result * (1. - fadeColor.a) + fadeColor;
|
||||
)",
|
||||
};
|
||||
}
|
||||
|
||||
ShaderPart FragmentSampleShadow() {
|
||||
return {
|
||||
.header = R"(
|
||||
uniform sampler2D h_texture;
|
||||
uniform vec2 h_size;
|
||||
uniform vec4 h_extend;
|
||||
uniform vec4 h_components;
|
||||
)",
|
||||
.body = R"(
|
||||
vec4 extended = vec4( // Left-Bottom-Width-Height rectangle.
|
||||
roundRect.xy - h_extend.xw,
|
||||
roundRect.zw + h_extend.xw + h_extend.zy);
|
||||
vec2 inside = (gl_FragCoord.xy - extended.xy);
|
||||
vec2 insideOtherCorner = (inside + h_size - extended.zw);
|
||||
vec4 outsideCorners = step(
|
||||
vec4(h_components.xy, inside),
|
||||
vec4(inside, extended.zw - h_components.xy));
|
||||
vec4 insideCorners = vec4(1.) - outsideCorners;
|
||||
vec2 linear = outsideCorners.xy * outsideCorners.zw;
|
||||
vec2 h_size_half = 0.5 * h_size;
|
||||
|
||||
vec2 bottomleft = inside * insideCorners.x * insideCorners.y;
|
||||
vec2 bottomright = vec2(insideOtherCorner.x, inside.y)
|
||||
* insideCorners.z
|
||||
* insideCorners.y;
|
||||
vec2 topright = insideOtherCorner * insideCorners.z * insideCorners.w;
|
||||
vec2 topleft = vec2(inside.x, insideOtherCorner.y)
|
||||
* insideCorners.x
|
||||
* insideCorners.w;
|
||||
|
||||
vec2 left = vec2(inside.x, h_size_half.y)
|
||||
* step(inside.x, h_components.z)
|
||||
* linear.y;
|
||||
vec2 bottom = vec2(h_size_half.x, inside.y)
|
||||
* step(inside.y, h_components.w)
|
||||
* linear.x;
|
||||
vec2 right = vec2(insideOtherCorner.x, h_size_half.y)
|
||||
* step(h_size.x - h_components.z, insideOtherCorner.x)
|
||||
* linear.y;
|
||||
vec2 top = vec2(h_size_half.x, insideOtherCorner.y)
|
||||
* step(h_size.y - h_components.w, insideOtherCorner.y)
|
||||
* linear.x;
|
||||
|
||||
vec2 uv = bottomleft
|
||||
+ bottomright
|
||||
+ topleft
|
||||
+ topright
|
||||
+ left
|
||||
+ bottom
|
||||
+ right
|
||||
+ top;
|
||||
result = texture2D(h_texture, uv / h_size);
|
||||
)",
|
||||
};
|
||||
}
|
||||
|
||||
ShaderPart FragmentRoundToShadow() {
|
||||
const auto shadow = FragmentSampleShadow();
|
||||
return {
|
||||
.header = R"(
|
||||
uniform vec4 roundRect;
|
||||
uniform float roundRadius;
|
||||
)" + shadow.header + R"(
|
||||
|
||||
float roundedCorner() {
|
||||
vec2 rectHalf = roundRect.zw / 2;
|
||||
vec2 rectCenter = roundRect.xy + rectHalf;
|
||||
vec2 fromRectCenter = abs(gl_FragCoord.xy - rectCenter);
|
||||
vec2 vectorRadius = vec2(roundRadius + 0.5, roundRadius + 0.5);
|
||||
vec2 fromCenterWithRadius = fromRectCenter + vectorRadius;
|
||||
vec2 fromRoundingCenter = max(fromCenterWithRadius, rectHalf)
|
||||
- rectHalf;
|
||||
float rounded = length(fromRoundingCenter) - roundRadius;
|
||||
|
||||
return 1. - smoothstep(0., 1., rounded);
|
||||
}
|
||||
|
||||
vec4 shadow() {
|
||||
vec4 result;
|
||||
|
||||
)" + shadow.body + R"(
|
||||
|
||||
return result;
|
||||
}
|
||||
)",
|
||||
.body = R"(
|
||||
float round = roundedCorner();
|
||||
result = result * round + shadow() * (1. - round);
|
||||
)",
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Pip::RendererGL::RendererGL(not_null<Pip*> owner)
|
||||
: _owner(owner) {
|
||||
style::PaletteChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
_radialImage.invalidate();
|
||||
_playbackImage.invalidate();
|
||||
_volumeControllerImage.invalidate();
|
||||
invalidateControls();
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void Pip::RendererGL::init(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
QOpenGLFunctions &f) {
|
||||
constexpr auto kQuads = 8;
|
||||
constexpr auto kQuadVertices = kQuads * 4;
|
||||
constexpr auto kQuadValues = kQuadVertices * 4;
|
||||
constexpr auto kControlsValues = kControlsCount * kControlValues;
|
||||
constexpr auto kValues = kQuadValues + kControlsValues;
|
||||
|
||||
_contentBuffer.emplace();
|
||||
_contentBuffer->setUsagePattern(QOpenGLBuffer::DynamicDraw);
|
||||
_contentBuffer->create();
|
||||
_contentBuffer->bind();
|
||||
_contentBuffer->allocate(kValues * sizeof(GLfloat));
|
||||
|
||||
_textures.ensureCreated(f);
|
||||
|
||||
_argb32Program.emplace();
|
||||
_texturedVertexShader = LinkProgram(
|
||||
&*_argb32Program,
|
||||
VertexShader({
|
||||
VertexPassTextureCoord(),
|
||||
}),
|
||||
FragmentShader({
|
||||
FragmentSampleARGB32Texture(),
|
||||
FragmentApplyFade(),
|
||||
FragmentRoundToShadow(),
|
||||
})).vertex;
|
||||
|
||||
_yuv420Program.emplace();
|
||||
LinkProgram(
|
||||
&*_yuv420Program,
|
||||
_texturedVertexShader,
|
||||
FragmentShader({
|
||||
FragmentSampleYUV420Texture(),
|
||||
FragmentApplyFade(),
|
||||
FragmentRoundToShadow(),
|
||||
}));
|
||||
|
||||
_imageProgram.emplace();
|
||||
LinkProgram(
|
||||
&*_imageProgram,
|
||||
VertexShader({
|
||||
VertexViewportTransform(),
|
||||
VertexPassTextureCoord(),
|
||||
}),
|
||||
FragmentShader({
|
||||
FragmentSampleARGB32Texture(),
|
||||
}));
|
||||
|
||||
_controlsProgram.emplace();
|
||||
LinkProgram(
|
||||
&*_controlsProgram,
|
||||
VertexShader({
|
||||
VertexViewportTransform(),
|
||||
VertexPassTextureCoord(),
|
||||
VertexPassTextureCoord('o'),
|
||||
}),
|
||||
FragmentShader({
|
||||
FragmentSampleARGB32Texture(),
|
||||
FragmentAddControlOver(),
|
||||
FragmentGlobalOpacity(),
|
||||
}));
|
||||
|
||||
createShadowTexture();
|
||||
}
|
||||
|
||||
void Pip::RendererGL::deinit(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
QOpenGLFunctions &f) {
|
||||
_textures.destroy(f);
|
||||
_imageProgram = std::nullopt;
|
||||
_texturedVertexShader = nullptr;
|
||||
_argb32Program = std::nullopt;
|
||||
_yuv420Program = std::nullopt;
|
||||
_controlsProgram = std::nullopt;
|
||||
_contentBuffer = std::nullopt;
|
||||
}
|
||||
|
||||
void Pip::RendererGL::resize(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
QOpenGLFunctions &f,
|
||||
int w,
|
||||
int h) {
|
||||
const auto factor = widget->devicePixelRatio();
|
||||
if (_factor != factor) {
|
||||
_factor = factor;
|
||||
_controlsImage.invalidate();
|
||||
}
|
||||
_viewport = QSize{ w, h };
|
||||
_uniformViewport = QVector2D(
|
||||
_viewport.width() * _factor,
|
||||
_viewport.height() * _factor);
|
||||
setDefaultViewport(f);
|
||||
}
|
||||
|
||||
void Pip::RendererGL::setDefaultViewport(QOpenGLFunctions &f) {
|
||||
f.glViewport(0, 0, _uniformViewport.x(), _uniformViewport.y());
|
||||
}
|
||||
|
||||
void Pip::RendererGL::createShadowTexture() {
|
||||
const auto &shadow = st::callShadow;
|
||||
const auto size = 2 * st::callShadow.topLeft.size()
|
||||
+ QSize(st::roundRadiusLarge, st::roundRadiusLarge);
|
||||
auto image = QImage(
|
||||
size * cIntRetinaFactor(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
image.setDevicePixelRatio(cRetinaFactor());
|
||||
image.fill(Qt::transparent);
|
||||
{
|
||||
auto p = QPainter(&image);
|
||||
Ui::Shadow::paint(
|
||||
p,
|
||||
QRect(QPoint(), size).marginsRemoved(shadow.extend),
|
||||
size.width(),
|
||||
shadow);
|
||||
}
|
||||
image.save("C:\\Tmp\\shadow.png");
|
||||
_shadowImage.setImage(std::move(image));
|
||||
}
|
||||
|
||||
void Pip::RendererGL::paint(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
QOpenGLFunctions &f) {
|
||||
_f = &f;
|
||||
_owner->paint(this);
|
||||
_f = nullptr;
|
||||
}
|
||||
|
||||
void Pip::RendererGL::paintTransformedVideoFrame(
|
||||
ContentGeometry geometry) {
|
||||
const auto data = _owner->videoFrameWithInfo();
|
||||
if (data.format == Streaming::FrameFormat::None) {
|
||||
return;
|
||||
}
|
||||
if (data.format == Streaming::FrameFormat::ARGB32) {
|
||||
Assert(!data.original.isNull());
|
||||
paintTransformedStaticContent(data.original, geometry);
|
||||
return;
|
||||
}
|
||||
Assert(data.format == Streaming::FrameFormat::YUV420);
|
||||
Assert(!data.yuv420->size.isEmpty());
|
||||
const auto yuv = data.yuv420;
|
||||
_yuv420Program->bind();
|
||||
|
||||
const auto upload = (_trackFrameIndex != data.index);
|
||||
_trackFrameIndex = data.index;
|
||||
|
||||
_f->glActiveTexture(GL_TEXTURE0);
|
||||
_textures.bind(*_f, 1);
|
||||
if (upload) {
|
||||
_f->glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
uploadTexture(
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
yuv->size,
|
||||
_lumaSize,
|
||||
yuv->y.stride,
|
||||
yuv->y.data);
|
||||
_lumaSize = yuv->size;
|
||||
}
|
||||
_f->glActiveTexture(GL_TEXTURE1);
|
||||
_textures.bind(*_f, 2);
|
||||
if (upload) {
|
||||
uploadTexture(
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
yuv->chromaSize,
|
||||
_chromaSize,
|
||||
yuv->u.stride,
|
||||
yuv->u.data);
|
||||
}
|
||||
_f->glActiveTexture(GL_TEXTURE2);
|
||||
_textures.bind(*_f, 3);
|
||||
if (upload) {
|
||||
uploadTexture(
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
yuv->chromaSize,
|
||||
_chromaSize,
|
||||
yuv->v.stride,
|
||||
yuv->v.data);
|
||||
_chromaSize = yuv->chromaSize;
|
||||
_f->glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
}
|
||||
_yuv420Program->setUniformValue("y_texture", GLint(0));
|
||||
_yuv420Program->setUniformValue("u_texture", GLint(1));
|
||||
_yuv420Program->setUniformValue("v_texture", GLint(2));
|
||||
|
||||
paintTransformedContent(&*_yuv420Program, geometry);
|
||||
}
|
||||
|
||||
void Pip::RendererGL::paintTransformedStaticContent(
|
||||
const QImage &image,
|
||||
ContentGeometry geometry) {
|
||||
_argb32Program->bind();
|
||||
|
||||
_f->glActiveTexture(GL_TEXTURE0);
|
||||
_textures.bind(*_f, 0);
|
||||
const auto cacheKey = image.cacheKey();
|
||||
const auto upload = (_cacheKey != cacheKey);
|
||||
if (upload) {
|
||||
_cacheKey = cacheKey;
|
||||
const auto stride = image.bytesPerLine() / 4;
|
||||
const auto data = image.constBits();
|
||||
uploadTexture(
|
||||
GL_RGBA,
|
||||
GL_RGBA,
|
||||
image.size(),
|
||||
_rgbaSize,
|
||||
stride,
|
||||
data);
|
||||
_rgbaSize = image.size();
|
||||
}
|
||||
_argb32Program->setUniformValue("s_texture", GLint(0));
|
||||
|
||||
paintTransformedContent(&*_argb32Program, geometry);
|
||||
}
|
||||
|
||||
void Pip::RendererGL::paintTransformedContent(
|
||||
not_null<QOpenGLShaderProgram*> program,
|
||||
ContentGeometry geometry) {
|
||||
const auto rect = transformRect(geometry.inner);
|
||||
const auto xscale = 1.f / geometry.inner.width();
|
||||
const auto yscale = 1.f / geometry.inner.height();
|
||||
const GLfloat coords[] = {
|
||||
-1.f, 1.f,
|
||||
-geometry.inner.x() * xscale,
|
||||
-geometry.inner.y() * yscale,
|
||||
|
||||
1.f, 1.f,
|
||||
(geometry.outer.width() - geometry.inner.x()) * xscale,
|
||||
-geometry.inner.y() * yscale,
|
||||
|
||||
1.f, -1.f,
|
||||
(geometry.outer.width() - geometry.inner.x()) * xscale,
|
||||
(geometry.outer.height() - geometry.inner.y()) * yscale,
|
||||
|
||||
-1.f, -1.f,
|
||||
-geometry.inner.x() * xscale,
|
||||
(geometry.outer.height() - geometry.inner.y()) * yscale,
|
||||
};
|
||||
|
||||
_contentBuffer->write(0, coords, sizeof(coords));
|
||||
|
||||
const auto rgbaFrame = _chromaSize.isEmpty();
|
||||
_f->glActiveTexture(rgbaFrame ? GL_TEXTURE1 : GL_TEXTURE3);
|
||||
_shadowImage.bind(*_f);
|
||||
|
||||
const auto fadeAlpha = st::radialBg->c.alphaF() * geometry.fade;
|
||||
const auto roundRect = transformRect(RoundingRect(geometry));
|
||||
program->setUniformValue("roundRect", Uniform(roundRect));
|
||||
program->setUniformValue("h_texture", GLint(rgbaFrame ? 1 : 3));
|
||||
program->setUniformValue("h_size", QSizeF(_shadowImage.image().size()));
|
||||
program->setUniformValue("h_extend", QVector4D(
|
||||
st::callShadow.extend.left(),
|
||||
st::callShadow.extend.top(),
|
||||
st::callShadow.extend.right(),
|
||||
st::callShadow.extend.bottom()));
|
||||
program->setUniformValue("h_components", QVector4D(
|
||||
float(st::callShadow.topLeft.width()),
|
||||
float(st::callShadow.topLeft.height()),
|
||||
float(st::callShadow.left.width()),
|
||||
float(st::callShadow.top.height())));
|
||||
program->setUniformValue(
|
||||
"roundRadius",
|
||||
GLfloat(st::roundRadiusLarge * _factor));
|
||||
program->setUniformValue("fadeColor", QVector4D(
|
||||
float(st::radialBg->c.redF() * fadeAlpha),
|
||||
float(st::radialBg->c.greenF() * fadeAlpha),
|
||||
float(st::radialBg->c.blueF() * fadeAlpha),
|
||||
float(fadeAlpha)));
|
||||
|
||||
toggleBlending(true);
|
||||
FillTexturedRectangle(*_f, &*program);
|
||||
}
|
||||
|
||||
void Pip::RendererGL::uploadTexture(
|
||||
GLint internalformat,
|
||||
GLint format,
|
||||
QSize size,
|
||||
QSize hasSize,
|
||||
int stride,
|
||||
const void *data) const {
|
||||
_f->glPixelStorei(GL_UNPACK_ROW_LENGTH, stride);
|
||||
if (hasSize != size) {
|
||||
_f->glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
internalformat,
|
||||
size.width(),
|
||||
size.height(),
|
||||
0,
|
||||
format,
|
||||
GL_UNSIGNED_BYTE,
|
||||
data);
|
||||
} else {
|
||||
_f->glTexSubImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
size.width(),
|
||||
size.height(),
|
||||
format,
|
||||
GL_UNSIGNED_BYTE,
|
||||
data);
|
||||
}
|
||||
_f->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
}
|
||||
|
||||
void Pip::RendererGL::paintRadialLoading(
|
||||
QRect inner,
|
||||
float64 controlsShown) {
|
||||
paintUsingRaster(_radialImage, inner, [&](Painter &&p) {
|
||||
// Raster renderer paints content, then radial loading, then fade.
|
||||
// Here we paint fade together with the content, so we should emulate
|
||||
// radial loading being under the fade.
|
||||
//
|
||||
// The loading background is the same color as the fade (radialBg),
|
||||
// so nothing should be done with it. But the fade should be added
|
||||
// to the radial loading line color (radialFg).
|
||||
const auto newInner = QRect(QPoint(), inner.size());
|
||||
const auto fg = st::radialFg->c;
|
||||
const auto fade = st::radialBg->c;
|
||||
const auto fadeAlpha = controlsShown * fade.alphaF();
|
||||
const auto fgAlpha = 1. - fadeAlpha;
|
||||
const auto color = (fadeAlpha == 0.) ? fg : QColor(
|
||||
int(std::round(fg.red() * fgAlpha + fade.red() * fadeAlpha)),
|
||||
int(std::round(fg.green() * fgAlpha + fade.green() * fadeAlpha)),
|
||||
int(std::round(fg.blue() * fgAlpha + fade.blue() * fadeAlpha)),
|
||||
fg.alphaF());
|
||||
|
||||
_owner->paintRadialLoadingContent(p, newInner, color);
|
||||
}, kRadialLoadingOffset, true);
|
||||
}
|
||||
|
||||
void Pip::RendererGL::paintPlayback(QRect outer, float64 shown) {
|
||||
paintUsingRaster(_playbackImage, outer, [&](Painter &&p) {
|
||||
const auto newOuter = QRect(QPoint(), outer.size());
|
||||
_owner->paintPlaybackContent(p, newOuter, shown);
|
||||
}, kPlaybackOffset, true);
|
||||
}
|
||||
|
||||
void Pip::RendererGL::paintVolumeController(QRect outer, float64 shown) {
|
||||
paintUsingRaster(_volumeControllerImage, outer, [&](Painter &&p) {
|
||||
const auto newOuter = QRect(QPoint(), outer.size());
|
||||
_owner->paintVolumeControllerContent(p, newOuter, shown);
|
||||
}, kVolumeControllerOffset, true);
|
||||
}
|
||||
|
||||
void Pip::RendererGL::paintButtonsStart() {
|
||||
validateControls();
|
||||
_f->glActiveTexture(GL_TEXTURE0);
|
||||
_controlsImage.bind(*_f);
|
||||
toggleBlending(true);
|
||||
}
|
||||
|
||||
void Pip::RendererGL::paintButton(
|
||||
const Button &button,
|
||||
int outerWidth,
|
||||
float64 shown,
|
||||
float64 over,
|
||||
const style::icon &icon,
|
||||
const style::icon &iconOver) {
|
||||
const auto tryIndex = [&](int stateIndex) -> std::optional<Control> {
|
||||
const auto result = ControlMeta(button.state, stateIndex);
|
||||
return (result.icon == &icon && result.iconOver == &iconOver)
|
||||
? std::make_optional(result)
|
||||
: std::nullopt;
|
||||
};
|
||||
const auto meta = tryIndex(0)
|
||||
? *tryIndex(0)
|
||||
: tryIndex(1)
|
||||
? *tryIndex(1)
|
||||
: *tryIndex(2);
|
||||
Assert(meta.icon == &icon && meta.iconOver == &iconOver);
|
||||
|
||||
const auto offset = kControlsOffset + (meta.index * kControlValues) / 4;
|
||||
const auto iconRect = _controlsImage.texturedRect(
|
||||
button.icon,
|
||||
_controlsTextures[meta.index * 2 + 0]);
|
||||
const auto iconOverRect = _controlsImage.texturedRect(
|
||||
button.icon,
|
||||
_controlsTextures[meta.index * 2 + 1]);
|
||||
const auto iconGeometry = transformRect(iconRect.geometry);
|
||||
const GLfloat coords[] = {
|
||||
iconGeometry.left(), iconGeometry.top(),
|
||||
iconRect.texture.left(), iconRect.texture.bottom(),
|
||||
|
||||
iconGeometry.right(), iconGeometry.top(),
|
||||
iconRect.texture.right(), iconRect.texture.bottom(),
|
||||
|
||||
iconGeometry.right(), iconGeometry.bottom(),
|
||||
iconRect.texture.right(), iconRect.texture.top(),
|
||||
|
||||
iconGeometry.left(), iconGeometry.bottom(),
|
||||
iconRect.texture.left(), iconRect.texture.top(),
|
||||
|
||||
iconOverRect.texture.left(), iconOverRect.texture.bottom(),
|
||||
iconOverRect.texture.right(), iconOverRect.texture.bottom(),
|
||||
iconOverRect.texture.right(), iconOverRect.texture.top(),
|
||||
iconOverRect.texture.left(), iconOverRect.texture.top(),
|
||||
};
|
||||
_contentBuffer->write(
|
||||
offset * 4 * sizeof(GLfloat),
|
||||
coords,
|
||||
sizeof(coords));
|
||||
_controlsProgram->bind();
|
||||
_controlsProgram->setUniformValue("o_opacity", GLfloat(over));
|
||||
_controlsProgram->setUniformValue("g_opacity", GLfloat(shown));
|
||||
_controlsProgram->setUniformValue("viewport", _uniformViewport);
|
||||
|
||||
GLint overTexcoord = _controlsProgram->attributeLocation("o_texcoordIn");
|
||||
_f->glVertexAttribPointer(
|
||||
overTexcoord,
|
||||
2,
|
||||
GL_FLOAT,
|
||||
GL_FALSE,
|
||||
2 * sizeof(GLfloat),
|
||||
reinterpret_cast<const void*>((offset + 4) * 4 * sizeof(GLfloat)));
|
||||
_f->glEnableVertexAttribArray(overTexcoord);
|
||||
FillTexturedRectangle(*_f, &*_controlsProgram, offset);
|
||||
_f->glDisableVertexAttribArray(overTexcoord);
|
||||
}
|
||||
|
||||
auto Pip::RendererGL::ControlMeta(OverState control, int index)
|
||||
-> Control {
|
||||
Expects(index >= 0);
|
||||
|
||||
switch (control) {
|
||||
case OverState::Close: Assert(index < 1); return {
|
||||
0,
|
||||
&st::pipCloseIcon,
|
||||
&st::pipCloseIconOver,
|
||||
};
|
||||
case OverState::Enlarge: Assert(index < 1); return {
|
||||
1,
|
||||
&st::pipEnlargeIcon,
|
||||
&st::pipEnlargeIconOver,
|
||||
};
|
||||
case OverState::VolumeToggle: Assert(index < 3); return {
|
||||
(2 + index),
|
||||
(index == 0
|
||||
? &st::pipVolumeIcon0
|
||||
: (index == 1)
|
||||
? &st::pipVolumeIcon1
|
||||
: &st::pipVolumeIcon2),
|
||||
(index == 0
|
||||
? &st::pipVolumeIcon0Over
|
||||
: (index == 1)
|
||||
? &st::pipVolumeIcon1Over
|
||||
: &st::pipVolumeIcon2Over),
|
||||
};
|
||||
case OverState::Other: Assert(index < 2); return {
|
||||
(5 + index),
|
||||
(index ? &st::pipPauseIcon : &st::pipPlayIcon),
|
||||
(index ? &st::pipPauseIconOver : &st::pipPlayIconOver),
|
||||
};
|
||||
}
|
||||
Unexpected("Control value in Pip::RendererGL::ControlIndex.");
|
||||
}
|
||||
|
||||
void Pip::RendererGL::validateControls() {
|
||||
if (!_controlsImage.image().isNull()) {
|
||||
return;
|
||||
}
|
||||
const auto metas = {
|
||||
ControlMeta(OverState::Close),
|
||||
ControlMeta(OverState::Enlarge),
|
||||
ControlMeta(OverState::VolumeToggle),
|
||||
ControlMeta(OverState::VolumeToggle, 1),
|
||||
ControlMeta(OverState::VolumeToggle, 2),
|
||||
ControlMeta(OverState::Other),
|
||||
ControlMeta(OverState::Other, 1),
|
||||
};
|
||||
auto maxWidth = 0;
|
||||
auto fullHeight = 0;
|
||||
for (const auto meta : metas) {
|
||||
Assert(meta.icon->size() == meta.iconOver->size());
|
||||
maxWidth = std::max(meta.icon->width(), maxWidth);
|
||||
fullHeight += 2 * meta.icon->height();
|
||||
}
|
||||
auto image = QImage(
|
||||
QSize(maxWidth, fullHeight) * _factor,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
image.fill(Qt::transparent);
|
||||
image.setDevicePixelRatio(_factor);
|
||||
{
|
||||
auto p = QPainter(&image);
|
||||
auto index = 0;
|
||||
auto height = 0;
|
||||
const auto paint = [&](not_null<const style::icon*> icon) {
|
||||
icon->paint(p, 0, height, maxWidth);
|
||||
_controlsTextures[index++] = QRect(
|
||||
QPoint(0, height) * _factor,
|
||||
icon->size() * _factor);
|
||||
height += icon->height();
|
||||
};
|
||||
for (const auto meta : metas) {
|
||||
paint(meta.icon);
|
||||
paint(meta.iconOver);
|
||||
}
|
||||
}
|
||||
_controlsImage.setImage(std::move(image));
|
||||
}
|
||||
|
||||
void Pip::RendererGL::invalidateControls() {
|
||||
_controlsImage.invalidate();
|
||||
ranges::fill(_controlsTextures, QRect());
|
||||
}
|
||||
|
||||
void Pip::RendererGL::paintUsingRaster(
|
||||
Ui::GL::Image &image,
|
||||
QRect rect,
|
||||
Fn<void(Painter&&)> method,
|
||||
int bufferOffset,
|
||||
bool transparent) {
|
||||
auto raster = image.takeImage();
|
||||
const auto size = rect.size() * _factor;
|
||||
if (raster.width() < size.width() || raster.height() < size.height()) {
|
||||
raster = QImage(size, QImage::Format_ARGB32_Premultiplied);
|
||||
raster.setDevicePixelRatio(_factor);
|
||||
if (!transparent
|
||||
&& (raster.width() > size.width()
|
||||
|| raster.height() > size.height())) {
|
||||
raster.fill(Qt::transparent);
|
||||
}
|
||||
} else if (raster.devicePixelRatio() != _factor) {
|
||||
raster.setDevicePixelRatio(_factor);
|
||||
}
|
||||
|
||||
if (transparent) {
|
||||
raster.fill(Qt::transparent);
|
||||
}
|
||||
method(Painter(&raster));
|
||||
|
||||
image.setImage(std::move(raster));
|
||||
image.bind(*_f, size);
|
||||
|
||||
const auto textured = image.texturedRect(rect, QRect(QPoint(), size));
|
||||
const auto geometry = transformRect(textured.geometry);
|
||||
const GLfloat coords[] = {
|
||||
geometry.left(), geometry.top(),
|
||||
textured.texture.left(), textured.texture.bottom(),
|
||||
|
||||
geometry.right(), geometry.top(),
|
||||
textured.texture.right(), textured.texture.bottom(),
|
||||
|
||||
geometry.right(), geometry.bottom(),
|
||||
textured.texture.right(), textured.texture.top(),
|
||||
|
||||
geometry.left(), geometry.bottom(),
|
||||
textured.texture.left(), textured.texture.top(),
|
||||
};
|
||||
_contentBuffer->write(
|
||||
bufferOffset * 4 * sizeof(GLfloat),
|
||||
coords,
|
||||
sizeof(coords));
|
||||
|
||||
_imageProgram->bind();
|
||||
_imageProgram->setUniformValue("viewport", _uniformViewport);
|
||||
_imageProgram->setUniformValue("s_texture", GLint(0));
|
||||
_imageProgram->setUniformValue("g_opacity", GLfloat(1));
|
||||
|
||||
_f->glActiveTexture(GL_TEXTURE0);
|
||||
image.bind(*_f, size);
|
||||
|
||||
toggleBlending(transparent);
|
||||
FillTexturedRectangle(*_f, &*_imageProgram, bufferOffset);
|
||||
}
|
||||
|
||||
void Pip::RendererGL::toggleBlending(bool enabled) {
|
||||
if (_blendingEnabled == enabled) {
|
||||
return;
|
||||
} else if (enabled) {
|
||||
_f->glEnable(GL_BLEND);
|
||||
_f->glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
} else {
|
||||
_f->glDisable(GL_BLEND);
|
||||
}
|
||||
_blendingEnabled = enabled;
|
||||
}
|
||||
|
||||
QRect Pip::RendererGL::RoundingRect(ContentGeometry geometry) {
|
||||
const auto inner = geometry.inner;
|
||||
const auto attached = geometry.attached;
|
||||
const auto added = std::max({
|
||||
st::roundRadiusLarge,
|
||||
inner.x(),
|
||||
inner.y(),
|
||||
geometry.outer.width() - inner.x() - inner.width(),
|
||||
geometry.outer.height() - inner.y() - inner.height(),
|
||||
st::callShadow.topLeft.width(),
|
||||
st::callShadow.topLeft.height(),
|
||||
st::callShadow.topRight.width(),
|
||||
st::callShadow.topRight.height(),
|
||||
st::callShadow.bottomRight.width(),
|
||||
st::callShadow.bottomRight.height(),
|
||||
st::callShadow.bottomLeft.width(),
|
||||
st::callShadow.bottomLeft.height(),
|
||||
});
|
||||
return geometry.inner.marginsAdded({
|
||||
(attached & RectPart::Left) ? added : 0,
|
||||
(attached & RectPart::Top) ? added : 0,
|
||||
(attached & RectPart::Right) ? added : 0,
|
||||
(attached & RectPart::Bottom) ? added : 0,
|
||||
});
|
||||
}
|
||||
|
||||
Rect Pip::RendererGL::transformRect(const Rect &raster) const {
|
||||
return TransformRect(raster, _viewport, _factor);
|
||||
}
|
||||
|
||||
Rect Pip::RendererGL::transformRect(const QRectF &raster) const {
|
||||
return TransformRect(raster, _viewport, _factor);
|
||||
}
|
||||
|
||||
Rect Pip::RendererGL::transformRect(const QRect &raster) const {
|
||||
return TransformRect(Rect(raster), _viewport, _factor);
|
||||
}
|
||||
|
||||
} // namespace Media::View
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "media/view/media_view_pip_renderer.h"
|
||||
#include "ui/gl/gl_image.h"
|
||||
#include "ui/gl/gl_primitives.h"
|
||||
|
||||
#include <QtGui/QOpenGLBuffer>
|
||||
|
||||
namespace Media::View {
|
||||
|
||||
class Pip::RendererGL final : public Pip::Renderer {
|
||||
public:
|
||||
explicit RendererGL(not_null<Pip*> owner);
|
||||
|
||||
void init(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
QOpenGLFunctions &f) override;
|
||||
|
||||
void deinit(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
QOpenGLFunctions &f) override;
|
||||
|
||||
void resize(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
QOpenGLFunctions &f,
|
||||
int w,
|
||||
int h);
|
||||
|
||||
void paint(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
QOpenGLFunctions &f) override;
|
||||
|
||||
private:
|
||||
struct Control {
|
||||
int index = -1;
|
||||
not_null<const style::icon*> icon;
|
||||
not_null<const style::icon*> iconOver;
|
||||
};
|
||||
void setDefaultViewport(QOpenGLFunctions &f);
|
||||
void createShadowTexture();
|
||||
|
||||
void paintTransformedVideoFrame(ContentGeometry geometry) override;
|
||||
void paintTransformedStaticContent(
|
||||
const QImage &image,
|
||||
ContentGeometry geometry) override;
|
||||
void paintTransformedContent(
|
||||
not_null<QOpenGLShaderProgram*> program,
|
||||
ContentGeometry geometry);
|
||||
void paintRadialLoading(
|
||||
QRect inner,
|
||||
float64 controlsShown) override;
|
||||
void paintButtonsStart() override;
|
||||
void paintButton(
|
||||
const Button &button,
|
||||
int outerWidth,
|
||||
float64 shown,
|
||||
float64 over,
|
||||
const style::icon &icon,
|
||||
const style::icon &iconOver) override;
|
||||
void paintPlayback(QRect outer, float64 shown) override;
|
||||
void paintVolumeController(QRect outer, float64 shown) override;
|
||||
|
||||
void paintUsingRaster(
|
||||
Ui::GL::Image &image,
|
||||
QRect rect,
|
||||
Fn<void(Painter&&)> method,
|
||||
int bufferOffset,
|
||||
bool transparent = false);
|
||||
|
||||
void validateControls();
|
||||
void invalidateControls();
|
||||
void toggleBlending(bool enabled);
|
||||
|
||||
[[nodiscard]] QRect RoundingRect(ContentGeometry geometry);
|
||||
|
||||
[[nodiscard]] Ui::GL::Rect transformRect(const QRect &raster) const;
|
||||
[[nodiscard]] Ui::GL::Rect transformRect(const QRectF &raster) const;
|
||||
[[nodiscard]] Ui::GL::Rect transformRect(
|
||||
const Ui::GL::Rect &raster) const;
|
||||
|
||||
void uploadTexture(
|
||||
GLint internalformat,
|
||||
GLint format,
|
||||
QSize size,
|
||||
QSize hasSize,
|
||||
int stride,
|
||||
const void *data) const;
|
||||
|
||||
const not_null<Pip*> _owner;
|
||||
|
||||
QOpenGLFunctions *_f = nullptr;
|
||||
QSize _viewport;
|
||||
float _factor = 1.;
|
||||
QVector2D _uniformViewport;
|
||||
|
||||
std::optional<QOpenGLBuffer> _contentBuffer;
|
||||
std::optional<QOpenGLShaderProgram> _imageProgram;
|
||||
std::optional<QOpenGLShaderProgram> _controlsProgram;
|
||||
QOpenGLShader *_texturedVertexShader = nullptr;
|
||||
std::optional<QOpenGLShaderProgram> _argb32Program;
|
||||
std::optional<QOpenGLShaderProgram> _yuv420Program;
|
||||
Ui::GL::Textures<4> _textures;
|
||||
QSize _rgbaSize;
|
||||
QSize _lumaSize;
|
||||
QSize _chromaSize;
|
||||
quint64 _cacheKey = 0;
|
||||
int _trackFrameIndex = 0;
|
||||
|
||||
Ui::GL::Image _radialImage;
|
||||
Ui::GL::Image _controlsImage;
|
||||
Ui::GL::Image _playbackImage;
|
||||
Ui::GL::Image _volumeControllerImage;
|
||||
Ui::GL::Image _shadowImage;
|
||||
|
||||
static constexpr auto kControlsCount = 7;
|
||||
[[nodiscard]] static Control ControlMeta(
|
||||
OverState control,
|
||||
int index = 0);
|
||||
std::array<QRect, kControlsCount * 2> _controlsTextures;
|
||||
|
||||
bool _blendingEnabled = false;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Media::View
|
|
@ -0,0 +1,246 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "media/view/media_view_pip_raster.h"
|
||||
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "styles/style_calls.h" // st::callShadow.
|
||||
|
||||
namespace Media::View {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] Streaming::FrameRequest UnrotateRequest(
|
||||
const Streaming::FrameRequest &request,
|
||||
int rotation) {
|
||||
if (!rotation) {
|
||||
return request;
|
||||
}
|
||||
const auto unrotatedCorner = [&](RectPart corner) {
|
||||
if (!(request.corners & corner)) {
|
||||
return RectPart(0);
|
||||
}
|
||||
switch (corner) {
|
||||
case RectPart::TopLeft:
|
||||
return (rotation == 90)
|
||||
? RectPart::BottomLeft
|
||||
: (rotation == 180)
|
||||
? RectPart::BottomRight
|
||||
: RectPart::TopRight;
|
||||
case RectPart::TopRight:
|
||||
return (rotation == 90)
|
||||
? RectPart::TopLeft
|
||||
: (rotation == 180)
|
||||
? RectPart::BottomLeft
|
||||
: RectPart::BottomRight;
|
||||
case RectPart::BottomRight:
|
||||
return (rotation == 90)
|
||||
? RectPart::TopRight
|
||||
: (rotation == 180)
|
||||
? RectPart::TopLeft
|
||||
: RectPart::BottomLeft;
|
||||
case RectPart::BottomLeft:
|
||||
return (rotation == 90)
|
||||
? RectPart::BottomRight
|
||||
: (rotation == 180)
|
||||
? RectPart::TopRight
|
||||
: RectPart::TopLeft;
|
||||
}
|
||||
Unexpected("Corner in rotateCorner.");
|
||||
};
|
||||
auto result = request;
|
||||
result.outer = FlipSizeByRotation(request.outer, rotation);
|
||||
result.resize = FlipSizeByRotation(request.resize, rotation);
|
||||
result.corners = unrotatedCorner(RectPart::TopLeft)
|
||||
| unrotatedCorner(RectPart::TopRight)
|
||||
| unrotatedCorner(RectPart::BottomRight)
|
||||
| unrotatedCorner(RectPart::BottomLeft);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Pip::RendererSW::RendererSW(not_null<Pip*> owner)
|
||||
: _owner(owner)
|
||||
, _roundRect(ImageRoundRadius::Large, st::radialBg) {
|
||||
}
|
||||
|
||||
void Pip::RendererSW::paintFallback(
|
||||
Painter &&p,
|
||||
const QRegion &clip,
|
||||
Ui::GL::Backend backend) {
|
||||
_p = &p;
|
||||
_clip = &clip;
|
||||
_clipOuter = clip.boundingRect();
|
||||
_owner->paint(this);
|
||||
_p = nullptr;
|
||||
_clip = nullptr;
|
||||
}
|
||||
|
||||
void Pip::RendererSW::paintTransformedVideoFrame(
|
||||
ContentGeometry geometry) {
|
||||
paintTransformedImage(
|
||||
_owner->videoFrame(frameRequest(geometry)),
|
||||
geometry);
|
||||
}
|
||||
|
||||
void Pip::RendererSW::paintTransformedStaticContent(
|
||||
const QImage &image,
|
||||
ContentGeometry geometry) {
|
||||
paintTransformedImage(
|
||||
staticContentByRequest(image, frameRequest(geometry)),
|
||||
geometry);
|
||||
}
|
||||
|
||||
void Pip::RendererSW::paintFade(ContentGeometry geometry) const {
|
||||
using Part = RectPart;
|
||||
const auto sides = geometry.attached;
|
||||
const auto rounded = RectPart(0)
|
||||
| ((sides & (Part::Top | Part::Left)) ? Part(0) : Part::TopLeft)
|
||||
| ((sides & (Part::Top | Part::Right)) ? Part(0) : Part::TopRight)
|
||||
| ((sides & (Part::Bottom | Part::Right))
|
||||
? Part(0)
|
||||
: Part::BottomRight)
|
||||
| ((sides & (Part::Bottom | Part::Left))
|
||||
? Part(0)
|
||||
: Part::BottomLeft);
|
||||
_roundRect.paintSomeRounded(
|
||||
*_p,
|
||||
geometry.inner,
|
||||
rounded | Part::NoTopBottom | Part::Top | Part::Bottom);
|
||||
}
|
||||
|
||||
void Pip::RendererSW::paintButtonsStart() {
|
||||
}
|
||||
|
||||
void Pip::RendererSW::paintButton(
|
||||
const Button &button,
|
||||
int outerWidth,
|
||||
float64 shown,
|
||||
float64 over,
|
||||
const style::icon &icon,
|
||||
const style::icon &iconOver) {
|
||||
if (over < 1.) {
|
||||
_p->setOpacity(shown);
|
||||
icon.paint(*_p, button.icon.x(), button.icon.y(), outerWidth);
|
||||
}
|
||||
if (over > 0.) {
|
||||
_p->setOpacity(over * shown);
|
||||
iconOver.paint(*_p, button.icon.x(), button.icon.y(), outerWidth);
|
||||
}
|
||||
}
|
||||
|
||||
Pip::FrameRequest Pip::RendererSW::frameRequest(
|
||||
ContentGeometry geometry) const {
|
||||
auto result = FrameRequest();
|
||||
result.outer = geometry.inner.size() * style::DevicePixelRatio();
|
||||
result.resize = result.outer;
|
||||
result.corners = RectPart(0)
|
||||
| ((geometry.attached & (RectPart::Left | RectPart::Top))
|
||||
? RectPart(0)
|
||||
: RectPart::TopLeft)
|
||||
| ((geometry.attached & (RectPart::Top | RectPart::Right))
|
||||
? RectPart(0)
|
||||
: RectPart::TopRight)
|
||||
| ((geometry.attached & (RectPart::Right | RectPart::Bottom))
|
||||
? RectPart(0)
|
||||
: RectPart::BottomRight)
|
||||
| ((geometry.attached & (RectPart::Bottom | RectPart::Left))
|
||||
? RectPart(0)
|
||||
: RectPart::BottomLeft);
|
||||
result.radius = ImageRoundRadius::Large;
|
||||
return UnrotateRequest(result, geometry.rotation);
|
||||
}
|
||||
|
||||
QImage Pip::RendererSW::staticContentByRequest(
|
||||
const QImage &image,
|
||||
const FrameRequest &request) {
|
||||
if (request.resize.isEmpty()) {
|
||||
return QImage();
|
||||
} else if (!_preparedStaticContent.isNull()
|
||||
&& _preparedStaticRequest == request
|
||||
&& image.cacheKey() == _preparedStaticKey) {
|
||||
return _preparedStaticContent;
|
||||
}
|
||||
_preparedStaticKey = image.cacheKey();
|
||||
_preparedStaticRequest = request;
|
||||
//_preparedCoverStorage = Streaming::PrepareByRequest(
|
||||
// _instance.info().video.cover,
|
||||
// false,
|
||||
// _instance.info().video.rotation,
|
||||
// request,
|
||||
// std::move(_preparedCoverStorage));
|
||||
using Option = Images::Option;
|
||||
const auto options = Option::Smooth
|
||||
| Option::RoundedLarge
|
||||
| ((request.corners & RectPart::TopLeft)
|
||||
? Option::RoundedTopLeft
|
||||
: Option(0))
|
||||
| ((request.corners & RectPart::TopRight)
|
||||
? Option::RoundedTopRight
|
||||
: Option(0))
|
||||
| ((request.corners & RectPart::BottomRight)
|
||||
? Option::RoundedBottomRight
|
||||
: Option(0))
|
||||
| ((request.corners & RectPart::BottomLeft)
|
||||
? Option::RoundedBottomLeft
|
||||
: Option(0));
|
||||
_preparedStaticContent = Images::prepare(
|
||||
image,
|
||||
request.resize.width(),
|
||||
request.resize.height(),
|
||||
options,
|
||||
request.outer.width(),
|
||||
request.outer.height());
|
||||
return _preparedStaticContent;
|
||||
}
|
||||
|
||||
void Pip::RendererSW::paintTransformedImage(
|
||||
const QImage &image,
|
||||
ContentGeometry geometry) {
|
||||
const auto rect = geometry.inner;
|
||||
const auto rotation = geometry.rotation;
|
||||
if (geometry.useTransparency) {
|
||||
const auto sides = RectPart::AllSides & ~geometry.attached;
|
||||
Ui::Shadow::paint(*_p, rect, geometry.outer.width(), st::callShadow);
|
||||
}
|
||||
|
||||
if (UsePainterRotation(rotation, false)) {
|
||||
if (rotation) {
|
||||
_p->save();
|
||||
_p->rotate(rotation);
|
||||
}
|
||||
PainterHighQualityEnabler hq(*_p);
|
||||
_p->drawImage(RotatedRect(rect, rotation), image);
|
||||
if (rotation) {
|
||||
_p->restore();
|
||||
}
|
||||
} else {
|
||||
_p->drawImage(rect, RotateFrameImage(image, rotation));
|
||||
}
|
||||
|
||||
if (geometry.fade > 0) {
|
||||
_p->setOpacity(geometry.fade);
|
||||
paintFade(geometry);
|
||||
}
|
||||
}
|
||||
|
||||
void Pip::RendererSW::paintRadialLoading(
|
||||
QRect inner,
|
||||
float64 controlsShown) {
|
||||
_owner->paintRadialLoadingContent(*_p, inner, st::radialFg->c);
|
||||
}
|
||||
|
||||
void Pip::RendererSW::paintPlayback(QRect outer, float64 shown) {
|
||||
_owner->paintPlaybackContent(*_p, outer, shown);
|
||||
}
|
||||
|
||||
void Pip::RendererSW::paintVolumeController(QRect outer, float64 shown) {
|
||||
_owner->paintVolumeControllerContent(*_p, outer, shown);
|
||||
}
|
||||
|
||||
} // namespace Media::View
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "media/view/media_view_pip_renderer.h"
|
||||
|
||||
namespace Media::View {
|
||||
|
||||
class Pip::RendererSW final : public Pip::Renderer {
|
||||
public:
|
||||
explicit RendererSW(not_null<Pip*> owner);
|
||||
|
||||
void paintFallback(
|
||||
Painter &&p,
|
||||
const QRegion &clip,
|
||||
Ui::GL::Backend backend) override;
|
||||
|
||||
private:
|
||||
void paintTransformedVideoFrame(ContentGeometry geometry) override;
|
||||
void paintTransformedStaticContent(
|
||||
const QImage &image,
|
||||
ContentGeometry geometry) override;
|
||||
void paintTransformedImage(
|
||||
const QImage &image,
|
||||
ContentGeometry geometry);
|
||||
void paintRadialLoading(
|
||||
QRect inner,
|
||||
float64 controlsShown) override;
|
||||
void paintButtonsStart() override;
|
||||
void paintButton(
|
||||
const Button &button,
|
||||
int outerWidth,
|
||||
float64 shown,
|
||||
float64 over,
|
||||
const style::icon &icon,
|
||||
const style::icon &iconOver) override;
|
||||
void paintPlayback(QRect outer, float64 shown) override;
|
||||
void paintVolumeController(QRect outer, float64 shown) override;
|
||||
|
||||
void paintFade(ContentGeometry geometry) const;
|
||||
|
||||
[[nodiscard]] FrameRequest frameRequest(ContentGeometry geometry) const;
|
||||
[[nodiscard]] QImage staticContentByRequest(
|
||||
const QImage &image,
|
||||
const FrameRequest &request);
|
||||
|
||||
const not_null<Pip*> _owner;
|
||||
|
||||
Painter *_p = nullptr;
|
||||
const QRegion *_clip = nullptr;
|
||||
QRect _clipOuter;
|
||||
|
||||
Ui::RoundRect _roundRect;
|
||||
|
||||
QImage _preparedStaticContent;
|
||||
FrameRequest _preparedStaticRequest;
|
||||
qint64 _preparedStaticKey = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Media::View
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "media/view/media_view_pip.h"
|
||||
#include "ui/gl/gl_surface.h"
|
||||
|
||||
namespace Media::View {
|
||||
|
||||
class Pip::Renderer : public Ui::GL::Renderer {
|
||||
public:
|
||||
virtual void paintTransformedVideoFrame(ContentGeometry geometry) = 0;
|
||||
virtual void paintTransformedStaticContent(
|
||||
const QImage &image,
|
||||
ContentGeometry geometry) = 0;
|
||||
virtual void paintRadialLoading(
|
||||
QRect inner,
|
||||
float64 controlsShown) = 0;
|
||||
virtual void paintButtonsStart() = 0;
|
||||
virtual void paintButton(
|
||||
const Button &button,
|
||||
int outerWidth,
|
||||
float64 shown,
|
||||
float64 over,
|
||||
const style::icon &icon,
|
||||
const style::icon &iconOver) = 0;
|
||||
virtual void paintPlayback(QRect outer, float64 shown) = 0;
|
||||
virtual void paintVolumeController(QRect outer, float64 shown) = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Media::View
|
Loading…
Reference in New Issue