init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,370 @@
|
||||
/*
|
||||
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 "calls/group/ui/calls_group_recording_box.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "styles/style_basic.h"
|
||||
#include "styles/style_calls.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
#include <QSvgRenderer>
|
||||
|
||||
namespace Calls::Group {
|
||||
namespace {
|
||||
|
||||
constexpr auto kRoundRadius = 9;
|
||||
constexpr auto kMaxGroupCallLength = 40;
|
||||
constexpr auto kSwitchDuration = 200;
|
||||
|
||||
class GraphicButton final : public Ui::AbstractButton {
|
||||
public:
|
||||
GraphicButton(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const QString &filename,
|
||||
int selectWidth = 0);
|
||||
|
||||
void setToggled(bool value);
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e);
|
||||
|
||||
private:
|
||||
const style::margins _margins;
|
||||
QSvgRenderer _renderer;
|
||||
Ui::RoundRect _roundRect;
|
||||
Ui::RoundRect _roundRectSelect;
|
||||
Ui::Animations::Simple _animation;
|
||||
bool _toggled = false;
|
||||
};
|
||||
|
||||
class RecordingInfo final : public Ui::RpWidget {
|
||||
public:
|
||||
RecordingInfo(not_null<Ui::RpWidget*> parent);
|
||||
|
||||
void prepareAudio();
|
||||
void prepareVideo();
|
||||
|
||||
RecordingType type() const;
|
||||
|
||||
private:
|
||||
void setLabel(const QString &text);
|
||||
|
||||
const object_ptr<Ui::VerticalLayout> _container;
|
||||
RecordingType _type = RecordingType::AudioOnly;
|
||||
};
|
||||
|
||||
class Switcher final : public Ui::RpWidget {
|
||||
public:
|
||||
Switcher(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
rpl::producer<bool> &&toggled);
|
||||
|
||||
RecordingType type() const;
|
||||
|
||||
private:
|
||||
const object_ptr<Ui::BoxContentDivider> _background;
|
||||
const object_ptr<RecordingInfo> _audio;
|
||||
const object_ptr<RecordingInfo> _video;
|
||||
bool _toggled = false;
|
||||
|
||||
Ui::Animations::Simple _animation;
|
||||
};
|
||||
|
||||
GraphicButton::GraphicButton(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const QString &filename,
|
||||
int selectWidth)
|
||||
: AbstractButton(parent)
|
||||
, _margins(selectWidth, selectWidth, selectWidth, selectWidth)
|
||||
, _renderer(u":/gui/recording/%1.svg"_q.arg(filename))
|
||||
, _roundRect(kRoundRadius, st::groupCallMembersBg)
|
||||
, _roundRectSelect(kRoundRadius, st::groupCallActiveFg) {
|
||||
const auto size = style::ConvertScale(_renderer.defaultSize());
|
||||
resize((QRect(QPoint(), size) + _margins).size());
|
||||
}
|
||||
|
||||
void GraphicButton::setToggled(bool value) {
|
||||
if (_toggled == value) {
|
||||
return;
|
||||
}
|
||||
_toggled = value;
|
||||
_animation.start(
|
||||
[=] { update(); },
|
||||
_toggled ? 0. : 1.,
|
||||
_toggled ? 1. : 0.,
|
||||
st::universalDuration);
|
||||
}
|
||||
|
||||
void GraphicButton::paintEvent(QPaintEvent *e) {
|
||||
auto p = QPainter(this);
|
||||
const auto progress = _animation.value(_toggled ? 1. : 0.);
|
||||
p.setOpacity(progress);
|
||||
_roundRectSelect.paint(p, rect());
|
||||
p.setOpacity(1.);
|
||||
const auto r = rect() - _margins;
|
||||
_roundRect.paint(p, r);
|
||||
_renderer.render(&p, r);
|
||||
}
|
||||
|
||||
RecordingInfo::RecordingInfo(not_null<Ui::RpWidget*> parent)
|
||||
: RpWidget(parent)
|
||||
, _container(this) {
|
||||
sizeValue(
|
||||
) | rpl::on_next([=](const QSize &size) {
|
||||
_container->resizeToWidth(size.width());
|
||||
}, _container->lifetime());
|
||||
}
|
||||
|
||||
void RecordingInfo::prepareAudio() {
|
||||
_type = RecordingType::AudioOnly;
|
||||
setLabel(tr::lng_group_call_recording_start_audio_subtitle(tr::now));
|
||||
|
||||
const auto wrap = _container->add(
|
||||
object_ptr<Ui::RpWidget>(_container),
|
||||
style::margins(0, st::groupCallRecordingAudioSkip, 0, 0));
|
||||
const auto audioIcon = Ui::CreateChild<GraphicButton>(
|
||||
wrap,
|
||||
"info_audio");
|
||||
wrap->resize(width(), audioIcon->height());
|
||||
audioIcon->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
sizeValue(
|
||||
) | rpl::on_next([=](const QSize &size) {
|
||||
audioIcon->moveToLeft((size.width() - audioIcon->width()) / 2, 0);
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void RecordingInfo::prepareVideo() {
|
||||
setLabel(tr::lng_group_call_recording_start_video_subtitle(tr::now));
|
||||
|
||||
const auto wrap = _container->add(
|
||||
object_ptr<Ui::RpWidget>(_container),
|
||||
style::margins());
|
||||
|
||||
const auto landscapeIcon = Ui::CreateChild<GraphicButton>(
|
||||
wrap,
|
||||
"info_video_landscape",
|
||||
st::groupCallRecordingSelectWidth);
|
||||
const auto portraitIcon = Ui::CreateChild<GraphicButton>(
|
||||
wrap,
|
||||
"info_video_portrait",
|
||||
st::groupCallRecordingSelectWidth);
|
||||
wrap->resize(width(), portraitIcon->height());
|
||||
|
||||
landscapeIcon->setToggled(true);
|
||||
_type = RecordingType::VideoLandscape;
|
||||
|
||||
const auto icons = std::vector<GraphicButton*>{
|
||||
landscapeIcon,
|
||||
portraitIcon,
|
||||
};
|
||||
const auto types = std::map<GraphicButton*, RecordingType>{
|
||||
{ landscapeIcon, RecordingType::VideoLandscape },
|
||||
{ portraitIcon, RecordingType::VideoPortrait },
|
||||
};
|
||||
for (const auto icon : icons) {
|
||||
icon->clicks(
|
||||
) | rpl::on_next([=] {
|
||||
for (const auto &i : icons) {
|
||||
i->setToggled(icon == i);
|
||||
}
|
||||
_type = types.at(icon);
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
wrap->sizeValue(
|
||||
) | rpl::on_next([=](const QSize &size) {
|
||||
const auto wHalf = size.width() / icons.size();
|
||||
for (auto i = 0; i < icons.size(); i++) {
|
||||
const auto &icon = icons[i];
|
||||
icon->moveToLeft(
|
||||
wHalf * i + (wHalf - icon->width()) / 2,
|
||||
(size.height() - icon->height()) / 2);
|
||||
}
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void RecordingInfo::setLabel(const QString &text) {
|
||||
_container->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
_container,
|
||||
text,
|
||||
st::groupCallRecordingSubLabel),
|
||||
st::groupCallRecordingSubLabelMargins,
|
||||
style::al_top);
|
||||
}
|
||||
|
||||
RecordingType RecordingInfo::type() const {
|
||||
return _type;
|
||||
}
|
||||
|
||||
Switcher::Switcher(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
rpl::producer<bool> &&toggled)
|
||||
: RpWidget(parent)
|
||||
, _background(
|
||||
this,
|
||||
st::groupCallRecordingInfoHeight,
|
||||
st::groupCallDividerBar)
|
||||
, _audio(this)
|
||||
, _video(this) {
|
||||
_audio->prepareAudio();
|
||||
_video->prepareVideo();
|
||||
|
||||
resize(0, st::groupCallRecordingInfoHeight);
|
||||
|
||||
const auto updatePositions = [=](float64 progress) {
|
||||
_audio->moveToLeft(-width() * progress, 0);
|
||||
_video->moveToLeft(_audio->x() + _audio->width(), 0);
|
||||
};
|
||||
|
||||
sizeValue(
|
||||
) | rpl::on_next([=](const QSize &size) {
|
||||
_audio->resize(size.width(), size.height());
|
||||
_video->resize(size.width(), size.height());
|
||||
|
||||
updatePositions(_toggled ? 1. : 0.);
|
||||
|
||||
_background->lower();
|
||||
_background->setGeometry(QRect(QPoint(), size));
|
||||
}, lifetime());
|
||||
|
||||
std::move(
|
||||
toggled
|
||||
) | rpl::on_next([=](bool toggled) {
|
||||
_toggled = toggled;
|
||||
_animation.start(
|
||||
updatePositions,
|
||||
toggled ? 0. : 1.,
|
||||
toggled ? 1. : 0.,
|
||||
kSwitchDuration);
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
RecordingType Switcher::type() const {
|
||||
return _toggled ? _video->type() : _audio->type();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void EditGroupCallTitleBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
const QString &placeholder,
|
||||
const QString &title,
|
||||
bool livestream,
|
||||
Fn<void(QString)> done) {
|
||||
box->setTitle(livestream
|
||||
? tr::lng_group_call_edit_title_channel()
|
||||
: tr::lng_group_call_edit_title());
|
||||
const auto input = box->addRow(object_ptr<Ui::InputField>(
|
||||
box,
|
||||
st::groupCallField,
|
||||
rpl::single(placeholder),
|
||||
title));
|
||||
input->setMaxLength(kMaxGroupCallLength);
|
||||
box->setFocusCallback([=] {
|
||||
input->setFocusFast();
|
||||
});
|
||||
const auto submit = [=] {
|
||||
const auto result = input->getLastText().trimmed();
|
||||
box->closeBox();
|
||||
done(result);
|
||||
};
|
||||
input->submits() | rpl::on_next(submit, input->lifetime());
|
||||
box->addButton(tr::lng_settings_save(), submit);
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
}
|
||||
|
||||
void StartGroupCallRecordingBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
Fn<void(RecordingType)> done) {
|
||||
box->setTitle(tr::lng_group_call_recording_start());
|
||||
|
||||
box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box.get(),
|
||||
tr::lng_group_call_recording_start_sure(),
|
||||
st::groupCallBoxLabel));
|
||||
|
||||
const auto checkbox = box->addRow(
|
||||
object_ptr<Ui::Checkbox>(
|
||||
box,
|
||||
tr::lng_group_call_recording_start_checkbox(),
|
||||
false,
|
||||
st::groupCallCheckbox),
|
||||
style::margins(
|
||||
st::boxRowPadding.left(),
|
||||
st::boxRowPadding.left(),
|
||||
st::boxRowPadding.right(),
|
||||
st::boxRowPadding.bottom()));
|
||||
|
||||
const auto switcher = box->addRow(
|
||||
object_ptr<Switcher>(box, checkbox->checkedChanges()),
|
||||
st::groupCallRecordingInfoMargins);
|
||||
|
||||
box->addButton(tr::lng_continue(), [=] {
|
||||
const auto type = switcher->type();
|
||||
box->closeBox();
|
||||
done(type);
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
}
|
||||
|
||||
void AddTitleGroupCallRecordingBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
const QString &title,
|
||||
Fn<void(QString)> done) {
|
||||
box->setTitle(tr::lng_group_call_recording_start_title());
|
||||
|
||||
const auto input = box->addRow(object_ptr<Ui::InputField>(
|
||||
box,
|
||||
st::groupCallField,
|
||||
tr::lng_group_call_recording_start_field(),
|
||||
title));
|
||||
box->setFocusCallback([=] {
|
||||
input->setFocusFast();
|
||||
});
|
||||
const auto submit = [=] {
|
||||
const auto result = input->getLastText().trimmed();
|
||||
box->closeBox();
|
||||
done(result);
|
||||
};
|
||||
input->submits() | rpl::on_next(submit, input->lifetime());
|
||||
box->addButton(tr::lng_group_call_recording_start_button(), submit);
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
}
|
||||
|
||||
|
||||
void StopGroupCallRecordingBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
Fn<void(QString)> done) {
|
||||
box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box.get(),
|
||||
tr::lng_group_call_recording_stop_sure(),
|
||||
st::groupCallBoxLabel),
|
||||
style::margins(
|
||||
st::boxRowPadding.left(),
|
||||
st::boxPadding.top(),
|
||||
st::boxRowPadding.right(),
|
||||
st::boxPadding.bottom()));
|
||||
|
||||
box->addButton(tr::lng_box_ok(), [=] {
|
||||
box->closeBox();
|
||||
done(QString());
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
}
|
||||
|
||||
} // namespace Calls::Group
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Ui {
|
||||
class GenericBox;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Calls::Group {
|
||||
|
||||
enum class RecordingType {
|
||||
AudioOnly,
|
||||
VideoLandscape,
|
||||
VideoPortrait,
|
||||
};
|
||||
|
||||
void EditGroupCallTitleBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
const QString &placeholder,
|
||||
const QString &title,
|
||||
bool livestream,
|
||||
Fn<void(QString)> done);
|
||||
|
||||
void StartGroupCallRecordingBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
Fn<void(RecordingType)> done);
|
||||
|
||||
void AddTitleGroupCallRecordingBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
const QString &title,
|
||||
Fn<void(QString)> done);
|
||||
|
||||
void StopGroupCallRecordingBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
Fn<void(QString)> done);
|
||||
|
||||
} // namespace Calls::Group
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
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 "calls/group/ui/calls_group_scheduled_labels.h"
|
||||
|
||||
#include "ui/rp_widget.h"
|
||||
#include "ui/painter.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/timer_rpl.h"
|
||||
#include "styles/style_calls.h"
|
||||
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QLocale>
|
||||
|
||||
namespace Calls::Group::Ui {
|
||||
|
||||
rpl::producer<QString> StartsWhenText(rpl::producer<TimeId> date) {
|
||||
return std::move(
|
||||
date
|
||||
) | rpl::map([](TimeId date) -> rpl::producer<QString> {
|
||||
const auto parsedDate = base::unixtime::parse(date);
|
||||
const auto dateDay = QDateTime(parsedDate.date(), QTime(0, 0));
|
||||
const auto previousDay = QDateTime(
|
||||
parsedDate.date().addDays(-1),
|
||||
QTime(0, 0));
|
||||
const auto now = QDateTime::currentDateTime();
|
||||
const auto kDay = int64(24 * 60 * 60);
|
||||
const auto tillTomorrow = int64(now.secsTo(previousDay));
|
||||
const auto tillToday = tillTomorrow + kDay;
|
||||
const auto tillAfter = tillToday + kDay;
|
||||
|
||||
const auto time = QLocale().toString(
|
||||
parsedDate.time(),
|
||||
QLocale::ShortFormat);
|
||||
auto exact = tr::lng_group_call_starts_short_date(
|
||||
lt_date,
|
||||
rpl::single(langDayOfMonthFull(dateDay.date())),
|
||||
lt_time,
|
||||
rpl::single(time)
|
||||
) | rpl::type_erased;
|
||||
auto tomorrow = tr::lng_group_call_starts_short_tomorrow(
|
||||
lt_time,
|
||||
rpl::single(time));
|
||||
auto today = tr::lng_group_call_starts_short_today(
|
||||
lt_time,
|
||||
rpl::single(time));
|
||||
|
||||
auto todayAndAfter = rpl::single(
|
||||
std::move(today)
|
||||
) | rpl::then(base::timer_once(
|
||||
std::min(tillAfter, kDay) * crl::time(1000)
|
||||
) | rpl::map([=] {
|
||||
return rpl::duplicate(exact);
|
||||
})) | rpl::flatten_latest() | rpl::type_erased;
|
||||
|
||||
auto tomorrowAndAfter = rpl::single(
|
||||
std::move(tomorrow)
|
||||
) | rpl::then(base::timer_once(
|
||||
std::min(tillToday, kDay) * crl::time(1000)
|
||||
) | rpl::map([=] {
|
||||
return rpl::duplicate(todayAndAfter);
|
||||
})) | rpl::flatten_latest() | rpl::type_erased;
|
||||
|
||||
auto full = rpl::single(
|
||||
rpl::duplicate(exact)
|
||||
) | rpl::then(base::timer_once(
|
||||
tillTomorrow * crl::time(1000)
|
||||
) | rpl::map([=] {
|
||||
return rpl::duplicate(tomorrowAndAfter);
|
||||
})) | rpl::flatten_latest() | rpl::type_erased;
|
||||
|
||||
if (tillTomorrow > 0) {
|
||||
return full;
|
||||
} else if (tillToday > 0) {
|
||||
return tomorrowAndAfter;
|
||||
} else if (tillAfter > 0) {
|
||||
return todayAndAfter;
|
||||
} else {
|
||||
return exact;
|
||||
}
|
||||
}) | rpl::flatten_latest();
|
||||
}
|
||||
|
||||
object_ptr<Ui::RpWidget> CreateGradientLabel(
|
||||
QWidget *parent,
|
||||
rpl::producer<QString> text) {
|
||||
struct State {
|
||||
QBrush brush;
|
||||
QPainterPath path;
|
||||
};
|
||||
auto result = object_ptr<Ui::RpWidget>(parent);
|
||||
const auto raw = result.data();
|
||||
const auto state = raw->lifetime().make_state<State>();
|
||||
|
||||
std::move(
|
||||
text
|
||||
) | rpl::on_next([=](const QString &text) {
|
||||
state->path = QPainterPath();
|
||||
const auto &font = st::groupCallCountdownFont;
|
||||
state->path.addText(0, font->ascent, font->f, text);
|
||||
const auto width = font->width(text);
|
||||
raw->resize(width, font->height);
|
||||
auto gradient = QLinearGradient(QPoint(width, 0), QPoint());
|
||||
gradient.setStops(QGradientStops{
|
||||
{ 0.0, st::groupCallForceMutedBar1->c },
|
||||
{ .7, st::groupCallForceMutedBar2->c },
|
||||
{ 1.0, st::groupCallForceMutedBar3->c }
|
||||
});
|
||||
state->brush = QBrush(std::move(gradient));
|
||||
raw->update();
|
||||
}, raw->lifetime());
|
||||
|
||||
raw->paintRequest(
|
||||
) | rpl::on_next([=] {
|
||||
auto p = QPainter(raw);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto skip = st::groupCallWidth / 20;
|
||||
const auto available = parent->width() - 2 * skip;
|
||||
const auto full = raw->width();
|
||||
if (available > 0 && full > available) {
|
||||
const auto scale = available / float64(full);
|
||||
const auto shift = raw->rect().center();
|
||||
p.translate(shift);
|
||||
p.scale(scale, scale);
|
||||
p.translate(-shift);
|
||||
}
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(state->brush);
|
||||
p.drawPath(state->path);
|
||||
}, raw->lifetime());
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Calls::Group::Ui
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
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 "base/object_ptr.h"
|
||||
|
||||
namespace Ui {
|
||||
class RpWidget;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Calls::Group::Ui {
|
||||
|
||||
using namespace ::Ui;
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> StartsWhenText(
|
||||
rpl::producer<TimeId> date);
|
||||
|
||||
[[nodiscard]] object_ptr<RpWidget> CreateGradientLabel(
|
||||
QWidget *parent,
|
||||
rpl::producer<QString> text);
|
||||
|
||||
} // namespace Calls::Group::Ui
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
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 "calls/group/ui/calls_group_stars_coloring.h"
|
||||
|
||||
#include "base/object_ptr.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "payments/ui/payments_reaction_box.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/emoji_config.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rp_widget.h"
|
||||
#include "styles/style_credits.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_premium.h"
|
||||
|
||||
namespace Calls::Group::Ui {
|
||||
|
||||
StarsColoring StarsColoringForCount(
|
||||
const std::vector<StarsColoring> &colorings,
|
||||
int stars) {
|
||||
for (auto i = begin(colorings), e = end(colorings); i != e; ++i) {
|
||||
if (i->fromStars > stars) {
|
||||
Assert(i != begin(colorings));
|
||||
return *(std::prev(i));
|
||||
}
|
||||
}
|
||||
return colorings.back();
|
||||
}
|
||||
|
||||
int StarsRequiredForMessage(
|
||||
const std::vector<StarsColoring> &colorings,
|
||||
const TextWithTags &text) {
|
||||
Expects(!colorings.empty());
|
||||
|
||||
auto emojis = 0;
|
||||
auto outLength = 0;
|
||||
auto view = QStringView(text.text);
|
||||
const auto length = int(view.size());
|
||||
while (!view.isEmpty()) {
|
||||
if (Emoji::Find(view, &outLength)) {
|
||||
view = view.mid(outLength);
|
||||
++emojis;
|
||||
} else {
|
||||
view = view.mid(1);
|
||||
}
|
||||
}
|
||||
for (const auto &entry : colorings) {
|
||||
if (emojis <= entry.emojiLimit && length <= entry.charactersMax) {
|
||||
return entry.fromStars;
|
||||
}
|
||||
}
|
||||
return colorings.back().fromStars + 1;
|
||||
}
|
||||
|
||||
object_ptr<RpWidget> VideoStreamStarsLevel(
|
||||
not_null<RpWidget*> box,
|
||||
const std::vector<StarsColoring> &colorings,
|
||||
rpl::producer<int> starsValue) {
|
||||
struct State {
|
||||
rpl::variable<int> stars;
|
||||
rpl::variable<StarsColoring> coloring;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
state->stars = std::move(starsValue);
|
||||
state->coloring = state->stars.value(
|
||||
) | rpl::map([=](int stars) {
|
||||
return StarsColoringForCount(colorings, stars);
|
||||
});
|
||||
|
||||
auto pinTitle = state->coloring.value(
|
||||
) | rpl::map([=](const StarsColoring &value) {
|
||||
const auto seconds = value.secondsPin;
|
||||
return (seconds >= 3600)
|
||||
? tr::lng_hours_tiny(tr::now, lt_count, seconds / 3600)
|
||||
: (seconds >= 60)
|
||||
? tr::lng_minutes_tiny(tr::now, lt_count, seconds / 60)
|
||||
: tr::lng_seconds_tiny(tr::now, lt_count, seconds);
|
||||
});
|
||||
auto limitTitle = state->coloring.value(
|
||||
) | rpl::map([=](const StarsColoring &value) {
|
||||
return QString::number(value.charactersMax);
|
||||
});
|
||||
auto limitSubtext = state->coloring.value(
|
||||
) | rpl::map([=](const StarsColoring &value) {
|
||||
return tr::lng_paid_comment_limit_about(
|
||||
tr::now,
|
||||
lt_count,
|
||||
value.charactersMax);
|
||||
});
|
||||
auto emojiTitle = state->coloring.value(
|
||||
) | rpl::map([=](const StarsColoring &value) {
|
||||
return QString::number(value.emojiLimit);
|
||||
});
|
||||
auto emojiSubtext = state->coloring.value(
|
||||
) | rpl::map([=](const StarsColoring &value) {
|
||||
return tr::lng_paid_comment_emoji_about(
|
||||
tr::now,
|
||||
lt_count,
|
||||
value.emojiLimit);
|
||||
});
|
||||
return MakeStarSelectInfoBlocks(box, {
|
||||
{
|
||||
.title = std::move(pinTitle) | rpl::map(tr::marked),
|
||||
.subtext = tr::lng_paid_comment_pin_about(),
|
||||
},
|
||||
{
|
||||
.title = std::move(limitTitle) | rpl::map(tr::marked),
|
||||
.subtext = std::move(limitSubtext),
|
||||
},
|
||||
{
|
||||
.title = std::move(emojiTitle) | rpl::map(tr::marked),
|
||||
.subtext = std::move(emojiSubtext),
|
||||
},
|
||||
}, {}, true);
|
||||
}
|
||||
|
||||
} // namespace Calls::Group::Ui
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Ui {
|
||||
class RpWidget;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Calls::Group::Ui {
|
||||
|
||||
using namespace ::Ui;
|
||||
|
||||
struct StarsColoring {
|
||||
int bgLight = 0;
|
||||
int bgDark = 0;
|
||||
int fromStars = 0;
|
||||
TimeId secondsPin = 0;
|
||||
int charactersMax = 0;
|
||||
int emojiLimit = 0;
|
||||
|
||||
friend inline auto operator<=>(
|
||||
const StarsColoring &,
|
||||
const StarsColoring &) = default;
|
||||
friend inline bool operator==(
|
||||
const StarsColoring &,
|
||||
const StarsColoring &) = default;
|
||||
};
|
||||
|
||||
[[nodiscard]] StarsColoring StarsColoringForCount(
|
||||
const std::vector<StarsColoring> &colorings,
|
||||
int stars);
|
||||
|
||||
[[nodiscard]] int StarsRequiredForMessage(
|
||||
const std::vector<StarsColoring> &colorings,
|
||||
const TextWithTags &text);
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> VideoStreamStarsLevel(
|
||||
not_null<Ui::RpWidget*> box,
|
||||
const std::vector<StarsColoring> &colorings,
|
||||
rpl::producer<int> starsValue);
|
||||
|
||||
} // namespace Calls::Group::Ui
|
||||
@@ -0,0 +1,618 @@
|
||||
/*
|
||||
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 "calls/group/ui/desktop_capture_choose_source.h"
|
||||
|
||||
#include "ui/widgets/rp_window.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/platform/ui_platform_window_title.h"
|
||||
#include "ui/painter.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "webrtc/webrtc_video_track.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "styles/style_calls.h"
|
||||
|
||||
#include <tgcalls/desktop_capturer/DesktopCaptureSourceManager.h>
|
||||
#include <tgcalls/desktop_capturer/DesktopCaptureSourceHelper.h>
|
||||
#include <QtGui/QWindow>
|
||||
|
||||
namespace Calls::Group::Ui::DesktopCapture {
|
||||
namespace {
|
||||
|
||||
constexpr auto kColumns = 3;
|
||||
constexpr auto kRows = 2;
|
||||
|
||||
struct Preview {
|
||||
explicit Preview(tgcalls::DesktopCaptureSource source);
|
||||
|
||||
tgcalls::DesktopCaptureSourceHelper helper;
|
||||
Webrtc::VideoTrack track;
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
|
||||
class SourceButton final : public RippleButton {
|
||||
public:
|
||||
using RippleButton::RippleButton;
|
||||
|
||||
private:
|
||||
QImage prepareRippleMask() const override;
|
||||
|
||||
};
|
||||
|
||||
QImage SourceButton::prepareRippleMask() const {
|
||||
return RippleAnimation::RoundRectMask(size(), st::roundRadiusLarge);
|
||||
}
|
||||
|
||||
class Source final {
|
||||
public:
|
||||
Source(
|
||||
not_null<QWidget*> parent,
|
||||
tgcalls::DesktopCaptureSource source,
|
||||
const QString &title);
|
||||
|
||||
void setGeometry(QRect geometry);
|
||||
void clearHelper();
|
||||
|
||||
[[nodiscard]] rpl::producer<> activations() const;
|
||||
void setActive(bool active);
|
||||
[[nodiscard]] QString deviceIdKey() const;
|
||||
[[nodiscard]] rpl::lifetime &lifetime();
|
||||
|
||||
private:
|
||||
void paint();
|
||||
void setupPreview();
|
||||
|
||||
SourceButton _widget;
|
||||
FlatLabel _label;
|
||||
Ui::RoundRect _selectedRect;
|
||||
Ui::RoundRect _activeRect;
|
||||
tgcalls::DesktopCaptureSource _source;
|
||||
std::unique_ptr<Preview> _preview;
|
||||
rpl::event_stream<> _activations;
|
||||
QImage _frame;
|
||||
bool _active = false;
|
||||
|
||||
};
|
||||
|
||||
class ChooseSourceProcess final {
|
||||
public:
|
||||
static void Start(not_null<ChooseSourceDelegate*> delegate);
|
||||
|
||||
explicit ChooseSourceProcess(not_null<ChooseSourceDelegate*> delegate);
|
||||
|
||||
void activate();
|
||||
|
||||
private:
|
||||
void setupPanel();
|
||||
void setupSources();
|
||||
void setupGeometryWithParent(not_null<QWidget*> parent);
|
||||
void fillSources();
|
||||
void setupSourcesGeometry();
|
||||
void updateButtonsVisibility();
|
||||
void destroy();
|
||||
|
||||
static base::flat_map<
|
||||
not_null<ChooseSourceDelegate*>,
|
||||
std::unique_ptr<ChooseSourceProcess>> &Map();
|
||||
|
||||
const not_null<ChooseSourceDelegate*> _delegate;
|
||||
const std::unique_ptr<RpWindow> _window;
|
||||
const std::unique_ptr<ScrollArea> _scroll;
|
||||
const not_null<RpWidget*> _inner;
|
||||
const not_null<RpWidget*> _bottom;
|
||||
const not_null<RoundButton*> _submit;
|
||||
const not_null<RoundButton*> _finish;
|
||||
const not_null<Checkbox*> _withAudio;
|
||||
|
||||
QSize _fixedSize;
|
||||
std::vector<std::unique_ptr<Source>> _sources;
|
||||
Source *_selected = nullptr;
|
||||
QString _selectedId;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] tgcalls::DesktopCaptureSourceData SourceData() {
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto size = st::desktopCaptureSourceSize * factor;
|
||||
return {
|
||||
.aspectSize = { size.width(), size.height() },
|
||||
.fps = 1,
|
||||
.captureMouse = false,
|
||||
};
|
||||
}
|
||||
|
||||
Preview::Preview(tgcalls::DesktopCaptureSource source)
|
||||
: helper(source, SourceData())
|
||||
, track(Webrtc::VideoState::Active) {
|
||||
helper.setOutput(track.sink());
|
||||
helper.start();
|
||||
}
|
||||
|
||||
Source::Source(
|
||||
not_null<QWidget*> parent,
|
||||
tgcalls::DesktopCaptureSource source,
|
||||
const QString &title)
|
||||
: _widget(parent, st::groupCallRipple)
|
||||
, _label(&_widget, title, st::desktopCaptureLabel)
|
||||
, _selectedRect(ImageRoundRadius::Large, st::groupCallMembersBgOver)
|
||||
, _activeRect(ImageRoundRadius::Large, st::groupCallMuted1)
|
||||
, _source(source) {
|
||||
_widget.paintRequest(
|
||||
) | rpl::on_next([=] {
|
||||
paint();
|
||||
}, _widget.lifetime());
|
||||
|
||||
_label.setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
_widget.sizeValue(
|
||||
) | rpl::on_next([=](QSize size) {
|
||||
const auto padding = st::desktopCapturePadding;
|
||||
_label.resizeToNaturalWidth(
|
||||
size.width() - padding.left() - padding.right());
|
||||
_label.move(
|
||||
(size.width() - _label.width()) / 2,
|
||||
size.height() - _label.height() - st::desktopCaptureLabelBottom);
|
||||
}, _label.lifetime());
|
||||
|
||||
_widget.setClickedCallback([=] {
|
||||
setActive(true);
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<> Source::activations() const {
|
||||
return _activations.events();
|
||||
}
|
||||
|
||||
QString Source::deviceIdKey() const {
|
||||
return QString::fromStdString(_source.deviceIdKey());
|
||||
}
|
||||
|
||||
void Source::setActive(bool active) {
|
||||
if (_active != active) {
|
||||
_active = active;
|
||||
_widget.update();
|
||||
if (active) {
|
||||
_activations.fire({});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Source::setGeometry(QRect geometry) {
|
||||
_widget.setGeometry(geometry);
|
||||
}
|
||||
|
||||
void Source::clearHelper() {
|
||||
_preview = nullptr;
|
||||
}
|
||||
|
||||
void Source::paint() {
|
||||
auto p = QPainter(&_widget);
|
||||
|
||||
if (_frame.isNull() && !_preview) {
|
||||
setupPreview();
|
||||
}
|
||||
if (_active) {
|
||||
_activeRect.paint(p, _widget.rect());
|
||||
} else if (_widget.isOver() || _widget.isDown()) {
|
||||
_selectedRect.paint(p, _widget.rect());
|
||||
}
|
||||
_widget.paintRipple(
|
||||
p,
|
||||
{ 0, 0 },
|
||||
_active ? &st::shadowFg->c : nullptr);
|
||||
|
||||
const auto size = _preview ? _preview->track.frameSize() : QSize();
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto padding = st::desktopCapturePadding;
|
||||
const auto rect = _widget.rect();
|
||||
const auto inner = rect.marginsRemoved(padding);
|
||||
if (!size.isEmpty()) {
|
||||
const auto scaled = size.scaled(inner.size(), Qt::KeepAspectRatio);
|
||||
const auto request = Webrtc::FrameRequest{
|
||||
.resize = scaled * factor,
|
||||
.outer = scaled * factor,
|
||||
};
|
||||
_frame = _preview->track.frame(request);
|
||||
_preview->track.markFrameShown();
|
||||
}
|
||||
if (!_frame.isNull()) {
|
||||
clearHelper();
|
||||
const auto size = _frame.size() / factor;
|
||||
const auto x = inner.x() + (inner.width() - size.width()) / 2;
|
||||
const auto y = inner.y() + (inner.height() - size.height()) / 2;
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.drawImage(QRect(x, y, size.width(), size.height()), _frame);
|
||||
}
|
||||
}
|
||||
|
||||
void Source::setupPreview() {
|
||||
_preview = std::make_unique<Preview>(_source);
|
||||
_preview->track.renderNextFrame(
|
||||
) | rpl::on_next([=] {
|
||||
if (_preview->track.frameSize().isEmpty()) {
|
||||
_preview->track.markFrameShown();
|
||||
}
|
||||
_widget.update();
|
||||
}, _preview->lifetime);
|
||||
}
|
||||
|
||||
rpl::lifetime &Source::lifetime() {
|
||||
return _widget.lifetime();
|
||||
}
|
||||
|
||||
ChooseSourceProcess::ChooseSourceProcess(
|
||||
not_null<ChooseSourceDelegate*> delegate)
|
||||
: _delegate(delegate)
|
||||
, _window(std::make_unique<RpWindow>())
|
||||
, _scroll(std::make_unique<ScrollArea>(_window->body()))
|
||||
, _inner(_scroll->setOwnedWidget(object_ptr<RpWidget>(_scroll.get())))
|
||||
, _bottom(CreateChild<RpWidget>(_window->body().get()))
|
||||
, _submit(
|
||||
CreateChild<RoundButton>(
|
||||
_bottom.get(),
|
||||
tr::lng_group_call_screen_share_start(),
|
||||
st::desktopCaptureSubmit))
|
||||
, _finish(
|
||||
CreateChild<RoundButton>(
|
||||
_bottom.get(),
|
||||
tr::lng_group_call_screen_share_stop(),
|
||||
st::desktopCaptureFinish))
|
||||
, _withAudio(
|
||||
CreateChild<Checkbox>(
|
||||
_bottom.get(),
|
||||
tr::lng_group_call_screen_share_audio(tr::now),
|
||||
false,
|
||||
st::desktopCaptureWithAudio)) {
|
||||
setupPanel();
|
||||
setupSources();
|
||||
activate();
|
||||
}
|
||||
|
||||
void ChooseSourceProcess::Start(not_null<ChooseSourceDelegate*> delegate) {
|
||||
auto &map = Map();
|
||||
auto i = map.find(delegate);
|
||||
if (i == end(map)) {
|
||||
i = map.emplace(delegate, nullptr).first;
|
||||
delegate->chooseSourceInstanceLifetime().add([=] {
|
||||
Map().erase(delegate);
|
||||
});
|
||||
}
|
||||
if (!i->second) {
|
||||
i->second = std::make_unique<ChooseSourceProcess>(delegate);
|
||||
} else {
|
||||
i->second->activate();
|
||||
}
|
||||
}
|
||||
|
||||
void ChooseSourceProcess::activate() {
|
||||
if (_window->windowState() & Qt::WindowMinimized) {
|
||||
_window->showNormal();
|
||||
} else {
|
||||
_window->show();
|
||||
}
|
||||
_window->raise();
|
||||
_window->activateWindow();
|
||||
}
|
||||
|
||||
[[nodiscard]] base::flat_map<
|
||||
not_null<ChooseSourceDelegate*>,
|
||||
std::unique_ptr<ChooseSourceProcess>> &ChooseSourceProcess::Map() {
|
||||
static auto result = base::flat_map<
|
||||
not_null<ChooseSourceDelegate*>,
|
||||
std::unique_ptr<ChooseSourceProcess>>();
|
||||
return result;
|
||||
}
|
||||
|
||||
void ChooseSourceProcess::setupPanel() {
|
||||
#ifndef Q_OS_LINUX
|
||||
//_window->setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
#endif // Q_OS_LINUX
|
||||
//_window->setAttribute(Qt::WA_NoSystemBackground);
|
||||
|
||||
_window->setWindowIcon(QIcon(
|
||||
QPixmap::fromImage(Image::Empty()->original(), Qt::ColorOnly)));
|
||||
_window->setTitleStyle(st::desktopCaptureSourceTitle);
|
||||
|
||||
const auto skips = st::desktopCaptureSourceSkips;
|
||||
const auto margins = st::desktopCaptureMargins;
|
||||
const auto padding = st::desktopCapturePadding;
|
||||
const auto bottomSkip = margins.right() + padding.right();
|
||||
const auto bottomHeight = 2 * bottomSkip
|
||||
+ st::desktopCaptureCancel.height;
|
||||
const auto width = margins.left()
|
||||
+ kColumns * st::desktopCaptureSourceSize.width()
|
||||
+ (kColumns - 1) * skips.width()
|
||||
+ margins.right();
|
||||
const auto height = margins.top()
|
||||
+ kRows * st::desktopCaptureSourceSize.height()
|
||||
+ (kRows - 1) * skips.height()
|
||||
+ (st::desktopCaptureSourceSize.height() / 2)
|
||||
+ bottomHeight;
|
||||
_fixedSize = QSize(width, height);
|
||||
_window->setStaysOnTop(true);
|
||||
|
||||
_window->body()->paintRequest(
|
||||
) | rpl::on_next([=](QRect clip) {
|
||||
QPainter(_window->body()).fillRect(clip, st::groupCallMembersBg);
|
||||
}, _window->lifetime());
|
||||
|
||||
_bottom->setGeometry(0, height - bottomHeight, width, bottomHeight);
|
||||
|
||||
_submit->setClickedCallback([=] {
|
||||
if (_selectedId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
const auto weak = base::make_weak(_window.get());
|
||||
_delegate->chooseSourceAccepted(
|
||||
_selectedId,
|
||||
!_withAudio->isHidden() && _withAudio->checked());
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->close();
|
||||
}
|
||||
});
|
||||
_finish->setClickedCallback([=] {
|
||||
const auto weak = base::make_weak(_window.get());
|
||||
_delegate->chooseSourceStop();
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->close();
|
||||
}
|
||||
});
|
||||
const auto cancel = CreateChild<RoundButton>(
|
||||
_bottom.get(),
|
||||
tr::lng_cancel(),
|
||||
st::desktopCaptureCancel);
|
||||
cancel->setClickedCallback([=] {
|
||||
_window->close();
|
||||
});
|
||||
|
||||
rpl::combine(
|
||||
_submit->widthValue(),
|
||||
_submit->shownValue(),
|
||||
_finish->widthValue(),
|
||||
_finish->shownValue(),
|
||||
cancel->widthValue()
|
||||
) | rpl::on_next([=](
|
||||
int submitWidth,
|
||||
bool submitShown,
|
||||
int finishWidth,
|
||||
bool finishShown,
|
||||
int cancelWidth) {
|
||||
_finish->moveToRight(bottomSkip, bottomSkip);
|
||||
_submit->moveToRight(bottomSkip, bottomSkip);
|
||||
cancel->moveToRight(
|
||||
bottomSkip * 2 + (submitShown ? submitWidth : finishWidth),
|
||||
bottomSkip);
|
||||
}, _bottom->lifetime());
|
||||
|
||||
_withAudio->widthValue(
|
||||
) | rpl::on_next([=](int width) {
|
||||
const auto top = (bottomHeight - _withAudio->heightNoMargins()) / 2;
|
||||
_withAudio->moveToLeft(bottomSkip, top);
|
||||
}, _withAudio->lifetime());
|
||||
|
||||
_withAudio->setChecked(_delegate->chooseSourceActiveWithAudio());
|
||||
_withAudio->checkedChanges(
|
||||
) | rpl::on_next([=] {
|
||||
updateButtonsVisibility();
|
||||
}, _withAudio->lifetime());
|
||||
|
||||
const auto sharing = !_delegate->chooseSourceActiveDeviceId().isEmpty();
|
||||
_finish->setVisible(sharing);
|
||||
_submit->setVisible(!sharing);
|
||||
|
||||
_window->body()->sizeValue(
|
||||
) | rpl::on_next([=](QSize size) {
|
||||
_scroll->setGeometry(
|
||||
0,
|
||||
0,
|
||||
size.width(),
|
||||
size.height() - _bottom->height());
|
||||
}, _scroll->lifetime());
|
||||
|
||||
_scroll->widthValue(
|
||||
) | rpl::on_next([=](int width) {
|
||||
const auto rows = int(std::ceil(_sources.size() / float(kColumns)));
|
||||
const auto innerHeight = margins.top()
|
||||
+ rows * st::desktopCaptureSourceSize.height()
|
||||
+ (rows - 1) * skips.height()
|
||||
+ margins.bottom();
|
||||
_inner->resize(width, innerHeight);
|
||||
}, _inner->lifetime());
|
||||
|
||||
if (const auto parent = _delegate->chooseSourceParent()) {
|
||||
setupGeometryWithParent(parent);
|
||||
}
|
||||
|
||||
_window->events(
|
||||
) | rpl::filter([=](not_null<QEvent*> e) {
|
||||
return e->type() == QEvent::Close;
|
||||
}) | rpl::on_next([=] {
|
||||
destroy();
|
||||
}, _window->lifetime());
|
||||
}
|
||||
|
||||
void ChooseSourceProcess::setupSources() {
|
||||
fillSources();
|
||||
setupSourcesGeometry();
|
||||
}
|
||||
|
||||
void ChooseSourceProcess::fillSources() {
|
||||
using Type = tgcalls::DesktopCaptureType;
|
||||
auto screensManager = tgcalls::DesktopCaptureSourceManager(Type::Screen);
|
||||
auto windowsManager = tgcalls::DesktopCaptureSourceManager(Type::Window);
|
||||
|
||||
_withAudio->setVisible(_delegate->chooseSourceWithAudioSupported());
|
||||
|
||||
auto screenIndex = 0;
|
||||
auto windowIndex = 0;
|
||||
auto firstScreenSelected = false;
|
||||
const auto active = _delegate->chooseSourceActiveDeviceId();
|
||||
const auto append = [&](const tgcalls::DesktopCaptureSource &source) {
|
||||
const auto firstScreen = !source.isWindow() && !screenIndex;
|
||||
const auto title = !source.isWindow()
|
||||
? tr::lng_group_call_screen_title(
|
||||
tr::now,
|
||||
lt_index,
|
||||
QString::number(++screenIndex))
|
||||
: !source.title().empty()
|
||||
? QString::fromStdString(source.title())
|
||||
: "Window " + QString::number(++windowIndex);
|
||||
const auto id = source.deviceIdKey();
|
||||
_sources.push_back(std::make_unique<Source>(_inner, source, title));
|
||||
|
||||
const auto raw = _sources.back().get();
|
||||
if (!active.isEmpty() && active.toStdString() == id) {
|
||||
_selected = raw;
|
||||
raw->setActive(true);
|
||||
} else if (active.isEmpty() && firstScreen) {
|
||||
_selected = raw;
|
||||
raw->setActive(true);
|
||||
firstScreenSelected = true;
|
||||
}
|
||||
_sources.back()->activations(
|
||||
) | rpl::filter([=] {
|
||||
return (_selected != raw);
|
||||
}) | rpl::on_next([=]{
|
||||
if (_selected) {
|
||||
_selected->setActive(false);
|
||||
}
|
||||
_selected = raw;
|
||||
updateButtonsVisibility();
|
||||
}, raw->lifetime());
|
||||
};
|
||||
for (const auto &source : screensManager.sources()) {
|
||||
append(source);
|
||||
}
|
||||
for (const auto &source : windowsManager.sources()) {
|
||||
append(source);
|
||||
}
|
||||
if (firstScreenSelected) {
|
||||
updateButtonsVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
void ChooseSourceProcess::updateButtonsVisibility() {
|
||||
const auto selectedId = _selected
|
||||
? _selected->deviceIdKey()
|
||||
: QString();
|
||||
if (selectedId == _delegate->chooseSourceActiveDeviceId()
|
||||
&& (!_delegate->chooseSourceWithAudioSupported()
|
||||
|| (_withAudio->checked()
|
||||
== _delegate->chooseSourceActiveWithAudio()))) {
|
||||
_selectedId = QString();
|
||||
_finish->setVisible(true);
|
||||
_submit->setVisible(false);
|
||||
} else {
|
||||
_selectedId = selectedId;
|
||||
_finish->setVisible(false);
|
||||
_submit->setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
void ChooseSourceProcess::setupSourcesGeometry() {
|
||||
if (_sources.empty()) {
|
||||
destroy();
|
||||
return;
|
||||
}
|
||||
_inner->widthValue(
|
||||
) | rpl::on_next([=](int width) {
|
||||
const auto rows = int(std::ceil(_sources.size() / float(kColumns)));
|
||||
const auto margins = st::desktopCaptureMargins;
|
||||
const auto skips = st::desktopCaptureSourceSkips;
|
||||
const auto single = (width
|
||||
- margins.left()
|
||||
- margins.right()
|
||||
- (kColumns - 1) * skips.width()) / kColumns;
|
||||
const auto height = st::desktopCaptureSourceSize.height();
|
||||
auto top = margins.top();
|
||||
auto index = 0;
|
||||
for (auto row = 0; row != rows; ++row) {
|
||||
auto left = margins.left();
|
||||
for (auto column = 0; column != kColumns; ++column) {
|
||||
_sources[index]->setGeometry({ left, top, single, height });
|
||||
if (++index == _sources.size()) {
|
||||
break;
|
||||
}
|
||||
left += single + skips.width();
|
||||
}
|
||||
if (index >= _sources.size()) {
|
||||
break;
|
||||
}
|
||||
top += height + skips.height();
|
||||
}
|
||||
}, _inner->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
_scroll->scrollTopValue(),
|
||||
_scroll->heightValue()
|
||||
) | rpl::on_next([=](int scrollTop, int scrollHeight) {
|
||||
const auto rows = int(std::ceil(_sources.size() / float(kColumns)));
|
||||
const auto margins = st::desktopCaptureMargins;
|
||||
const auto skips = st::desktopCaptureSourceSkips;
|
||||
const auto height = st::desktopCaptureSourceSize.height();
|
||||
auto top = margins.top();
|
||||
auto index = 0;
|
||||
for (auto row = 0; row != rows; ++row) {
|
||||
const auto hidden = (top + height <= scrollTop)
|
||||
|| (top >= scrollTop + scrollHeight);
|
||||
if (hidden) {
|
||||
for (auto column = 0; column != kColumns; ++column) {
|
||||
_sources[index]->clearHelper();
|
||||
if (++index == _sources.size()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
index += kColumns;
|
||||
}
|
||||
if (index >= _sources.size()) {
|
||||
break;
|
||||
}
|
||||
top += height + skips.height();
|
||||
}
|
||||
}, _inner->lifetime());
|
||||
}
|
||||
|
||||
void ChooseSourceProcess::setupGeometryWithParent(
|
||||
not_null<QWidget*> parent) {
|
||||
const auto parentScreen = parent->screen();
|
||||
const auto myScreen = _window->screen();
|
||||
if (parentScreen && myScreen != parentScreen) {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
_window->setScreen(parentScreen);
|
||||
#else // Qt >= 6.0.0
|
||||
_window->createWinId();
|
||||
_window->windowHandle()->setScreen(parentScreen);
|
||||
#endif // Qt < 6.0.0
|
||||
}
|
||||
_window->setFixedSize(_fixedSize);
|
||||
_window->move(
|
||||
parent->x() + (parent->width() - _window->width()) / 2,
|
||||
parent->y() + (parent->height() - _window->height()) / 2);
|
||||
}
|
||||
|
||||
void ChooseSourceProcess::destroy() {
|
||||
auto &map = Map();
|
||||
if (const auto i = map.find(_delegate); i != end(map)) {
|
||||
if (i->second.get() == this) {
|
||||
base::take(i->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ChooseSource(not_null<ChooseSourceDelegate*> delegate) {
|
||||
ChooseSourceProcess::Start(delegate);
|
||||
}
|
||||
|
||||
} // namespace Calls::Group::Ui::DesktopCapture
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Ui {
|
||||
} // namespace Ui
|
||||
|
||||
namespace Calls::Group::Ui {
|
||||
using namespace ::Ui;
|
||||
} // namespace Calls::Group::Ui
|
||||
|
||||
namespace Calls::Group::Ui::DesktopCapture {
|
||||
|
||||
class ChooseSourceDelegate {
|
||||
public:
|
||||
virtual QWidget *chooseSourceParent() = 0;
|
||||
virtual QString chooseSourceActiveDeviceId() = 0;
|
||||
virtual bool chooseSourceActiveWithAudio() = 0;
|
||||
virtual bool chooseSourceWithAudioSupported() = 0;
|
||||
virtual rpl::lifetime &chooseSourceInstanceLifetime() = 0;
|
||||
virtual void chooseSourceAccepted(
|
||||
const QString &deviceId,
|
||||
bool withAudio) = 0;
|
||||
virtual void chooseSourceStop() = 0;
|
||||
};
|
||||
|
||||
void ChooseSource(not_null<ChooseSourceDelegate*> delegate);
|
||||
|
||||
} // namespace Calls::Group::Ui::DesktopCapture
|
||||
Reference in New Issue
Block a user