Updated lib_ui sources to TDesktop version 2.5.1

This commit is contained in:
Eric Kotato 2020-12-23 21:03:36 +03:00
commit 5bb0b6a6e5
60 changed files with 3160 additions and 309 deletions

View file

@ -42,8 +42,11 @@ PRIVATE
ui/effects/animations.h
ui/effects/cross_animation.cpp
ui/effects/cross_animation.h
ui/effects/cross_line.cpp
ui/effects/cross_line.h
ui/effects/fade_animation.cpp
ui/effects/fade_animation.h
ui/effects/gradient.h
ui/effects/numbers_animation.cpp
ui/effects/numbers_animation.h
ui/effects/panel_animation.cpp
@ -66,6 +69,12 @@ PRIVATE
ui/layers/layer_manager.h
ui/layers/layer_widget.cpp
ui/layers/layer_widget.h
ui/paint/blob.cpp
ui/paint/blob.h
ui/paint/blobs.cpp
ui/paint/blobs.h
ui/paint/blobs_linear.cpp
ui/paint/blobs_linear.h
ui/platform/linux/ui_window_linux.cpp
ui/platform/linux/ui_window_linux.h
ui/platform/linux/ui_utility_linux.cpp
@ -123,6 +132,10 @@ PRIVATE
ui/widgets/box_content_divider.h
ui/widgets/buttons.cpp
ui/widgets/buttons.h
ui/widgets/call_button.cpp
ui/widgets/call_button.h
ui/widgets/call_mute_button.cpp
ui/widgets/call_mute_button.h
ui/widgets/checkbox.cpp
ui/widgets/checkbox.h
ui/widgets/dropdown_menu.cpp
@ -187,6 +200,9 @@ PRIVATE
ui/ui_log.h
ui/ui_utility.cpp
ui/ui_utility.h
ui/ui_pch.h
emoji_suggestions/emoji_suggestions.cpp
emoji_suggestions/emoji_suggestions.h
emoji_suggestions/emoji_suggestions_helper.h

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 963 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 B

After

Width:  |  Height:  |  Size: 472 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 B

After

Width:  |  Height:  |  Size: 631 B

View file

@ -73,7 +73,6 @@ roundRadiusLarge: 6px;
roundRadiusSmall: 3px;
dateRadius: roundRadiusLarge;
buttonRadius: roundRadiusSmall;
setLittleSkip: 9px;

View file

@ -105,16 +105,20 @@ protected:
class LambdaClickHandler : public ClickHandler {
public:
LambdaClickHandler(Fn<void()> handler) : _handler(std::move(handler)) {
LambdaClickHandler(Fn<void()> handler)
: _handler([handler = std::move(handler)](ClickContext) { handler(); }) {
}
LambdaClickHandler(Fn<void(ClickContext)> handler)
: _handler(std::move(handler)) {
}
void onClick(ClickContext context) const override final {
if (context.button == Qt::LeftButton && _handler) {
_handler();
_handler(context);
}
}
private:
Fn<void()> _handler;
Fn<void(ClickContext)> _handler;
};

View file

@ -554,6 +554,35 @@ callHangupBg: #d75a5a; // phone call popup hangup button background
callHangupRipple: #c04646; // phone call popup hangup button ripple effect
callMuteRipple: #ffffff12; // phone call popup mute mic and camera ripple effect
groupCallBg: #1a2026; // group call popup background
groupCallActiveFg: #4db8ff; // group call active controls text
groupCallMembersBg: #2c333d; // group call members list background
groupCallMembersBgOver: #323a45; // group call members list row with mouse over
groupCallMembersBgRipple: #39424f; // group call member row ripple effect
groupCallMembersFg: #ffffff; // group call member name text
groupCallMemberActiveIcon: #8deb90; // group call active member icon
groupCallMemberActiveStatus: #8deb90; // group call active member status text
groupCallMemberInactiveIcon: #84888f; // group call inactive member icon
groupCallMemberInactiveStatus: #61c0ff; // group call inactive member status text
groupCallMemberMutedIcon: #ed7372; // group call muted by admin member icon
groupCallMemberNotJoinedStatus: #91979e; // group call non joined member status text
groupCallIconFg: #ffffff; // group call mute / settings / leave icon
groupCallLive1: #0dcc39; // group call live button color1
groupCallLive2: #0bb6bd; // group call live button color2
groupCallMuted1: #0992ef; // group call muted button color1
groupCallMuted2: #16ccfb; // group call muted button color2
groupCallForceMutedBar1: #c65493; // group call force muted top bar color1
groupCallForceMutedBar2: #7a6af1; // group call force muted top bar color2
groupCallForceMutedBar3: #5f95e8; // group call force muted top bar color3
groupCallForceMuted1: #4f9cff; // group call force muted button color1
groupCallForceMuted2: #9b52e9; // group call force muted button color2
groupCallForceMuted3: #eb5353; // group call force muted button color3
groupCallMenuBg: #292d33; // group call popup menu background
groupCallMenuBgOver: #343940; // group call popup menu with mouse over
groupCallMenuBgRipple: #3a4047; // group call popup menu ripple effect
groupCallLeaveBg: #f75c5c7f; // group call leave button background
groupCallLeaveBgRipple: #f75c5c9e; // group call leave button ripple effect
callBarBg: dialogsBgActive; // active phone call bar background
callBarMuteRipple: dialogsRippleBgActive; // active phone call bar mute and hangup button ripple effect
callBarBgMuted: #8f8f8f | dialogsUnreadBgMuted; // phone call bar with muted mic background

View file

@ -13,7 +13,7 @@
namespace anim {
namespace {
bool AnimationsDisabled = false;
rpl::variable<bool> AnimationsDisabled = false;
} // namespace
@ -64,8 +64,12 @@ transition easeOutQuint = [](const float64 &delta, const float64 &dt) {
return delta * (t2 * t2 * t + 1);
};
rpl::producer<bool> Disables() {
return AnimationsDisabled.value();
};
bool Disabled() {
return AnimationsDisabled;
return AnimationsDisabled.current();
}
void SetDisabled(bool disabled) {

View file

@ -94,8 +94,12 @@ private:
};
TG_FORCE_INLINE float64 interpolateF(int a, int b, float64 b_ratio) {
return a + float64(b - a) * b_ratio;
}
TG_FORCE_INLINE int interpolate(int a, int b, float64 b_ratio) {
return qRound(a + float64(b - a) * b_ratio);
return std::round(interpolateF(a, b, b_ratio));
}
#ifdef ARCH_CPU_32_BITS
@ -344,6 +348,7 @@ QPainterPath path(QPointF (&from)[N]) {
return result;
}
rpl::producer<bool> Disables();
bool Disabled();
void SetDisabled(bool disabled);
@ -354,4 +359,50 @@ void DrawStaticLoading(
QPen pen,
QBrush brush = Qt::NoBrush);
class continuous_value {
public:
continuous_value() = default;
continuous_value(float64 duration) : _duration(duration) {
}
void start(float64 to, float64 duration) {
_to = to;
_delta = (_to - _cur) / duration;
}
void start(float64 to) {
start(to, _duration);
}
void reset() {
_to = _cur = _delta = 0.;
}
float64 current() const {
return _cur;
}
float64 to() const {
return _to;
}
float64 delta() const {
return _delta;
}
void update(crl::time dt, Fn<void(float64 &)> &&callback = nullptr) {
if (_to != _cur) {
_cur += _delta * dt;
if ((_to != _cur) && ((_delta > 0) == (_cur > _to))) {
_cur = _to;
}
if (callback) {
callback(_cur);
}
}
}
private:
float64 _duration = 0.;
float64 _to = 0.;
float64 _cur = 0.;
float64 _delta = 0.;
};
} // namespace anim

99
ui/effects/cross_line.cpp Normal file
View file

@ -0,0 +1,99 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "ui/effects/cross_line.h"
#include "ui/painter.h"
namespace Ui {
CrossLineAnimation::CrossLineAnimation(
const style::CrossLineAnimation &st,
bool reversed,
float angle)
: _st(st)
, _reversed(reversed)
, _transparentPen(Qt::transparent, st.stroke, Qt::SolidLine, Qt::RoundCap)
, _strokePen(st.fg, st.stroke, Qt::SolidLine, Qt::RoundCap)
, _line(st.startPosition, st.endPosition) {
_line.setAngle(angle);
}
void CrossLineAnimation::paint(
Painter &p,
QPoint position,
float64 progress,
std::optional<QColor> colorOverride) {
paint(p, position.x(), position.y(), progress, colorOverride);
}
void CrossLineAnimation::paint(
Painter &p,
int left,
int top,
float64 progress,
std::optional<QColor> colorOverride) {
if (progress == 0.) {
if (colorOverride) {
_st.icon.paint(p, left, top, _st.icon.width(), *colorOverride);
} else {
_st.icon.paint(p, left, top, _st.icon.width());
}
} else if (progress == 1.) {
if (_completeCross.isNull()) {
fillFrame(progress, colorOverride);
_completeCross = _frame;
}
p.drawImage(left, top, _completeCross);
} else {
fillFrame(progress, colorOverride);
p.drawImage(left, top, _frame);
}
}
void CrossLineAnimation::fillFrame(
float64 progress,
std::optional<QColor> colorOverride) {
const auto ratio = style::DevicePixelRatio();
if (_frame.isNull()) {
_frame = QImage(
_st.icon.size() * ratio,
QImage::Format_ARGB32_Premultiplied);
_frame.setDevicePixelRatio(ratio);
}
_frame.fill(Qt::transparent);
auto topLine = _line;
topLine.setLength(topLine.length() * progress);
auto bottomLine = topLine.translated(0, _strokePen.widthF() + 1);
Painter q(&_frame);
PainterHighQualityEnabler hq(q);
if (colorOverride) {
_st.icon.paint(q, 0, 0, _st.icon.width(), *colorOverride);
} else {
_st.icon.paint(q, 0, 0, _st.icon.width());
}
if (colorOverride) {
auto pen = _strokePen;
pen.setColor(*colorOverride);
q.setPen(pen);
} else {
q.setPen(_strokePen);
}
q.drawLine(_reversed ? topLine : bottomLine);
q.setCompositionMode(QPainter::CompositionMode_Source);
q.setPen(_transparentPen);
q.drawLine(_reversed ? bottomLine : topLine);
}
void CrossLineAnimation::invalidate() {
_completeCross = QImage();
}
} // namespace Ui

49
ui/effects/cross_line.h Normal file
View file

@ -0,0 +1,49 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "styles/style_widgets.h"
class Painter;
namespace Ui {
class CrossLineAnimation {
public:
CrossLineAnimation(
const style::CrossLineAnimation &st,
bool reversed = false,
float angle = 315);
void paint(
Painter &p,
QPoint position,
float64 progress,
std::optional<QColor> colorOverride = std::nullopt);
void paint(
Painter &p,
int left,
int top,
float64 progress,
std::optional<QColor> colorOverride = std::nullopt);
void invalidate();
private:
void fillFrame(float64 progress, std::optional<QColor> colorOverride);
const style::CrossLineAnimation &_st;
const bool _reversed;
const QPen _transparentPen;
const QPen _strokePen;
QLineF _line;
QImage _frame;
QImage _completeCross;
};
} // namespace Ui

244
ui/effects/gradient.h Normal file
View file

@ -0,0 +1,244 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "base/flat_map.h"
#include "ui/effects/animation_value.h"
#include <QtGui/QLinearGradient>
#include <QtGui/QRadialGradient>
namespace anim {
struct gradient_colors {
explicit gradient_colors(QColor color) {
stops.push_back({ 0., color });
stops.push_back({ 1., color });
}
explicit gradient_colors(std::vector<QColor> colors) {
if (colors.size() == 1) {
gradient_colors(colors.front());
return;
}
const auto last = float(colors.size() - 1);
for (auto i = 0; i < colors.size(); i++) {
stops.push_back({ i / last, std::move(colors[i]) });
}
}
explicit gradient_colors(QGradientStops colors)
: stops(std::move(colors)) {
}
QGradientStops stops;
};
namespace details {
template <typename T, typename Derived>
class gradients {
public:
gradients(base::flat_map<T, std::vector<QColor>> colors) {
Expects(colors.size() > 0);
for (const auto &[key, value] : colors) {
auto c = gradient_colors(std::move(value));
_gradients.emplace(key, gradient_with_stops(std::move(c.stops)));
}
}
gradients(base::flat_map<T, gradient_colors> colors) {
Expects(colors.size() > 0);
for (const auto &[key, c] : colors) {
_gradients.emplace(key, gradient_with_stops(std::move(c.stops)));
}
}
QGradient gradient(T state1, T state2, float64 b_ratio) const {
if (b_ratio == 0.) {
return _gradients.find(state1)->second;
} else if (b_ratio == 1.) {
return _gradients.find(state2)->second;
}
auto gradient = empty_gradient();
const auto gradient1 = _gradients.find(state1);
const auto gradient2 = _gradients.find(state2);
Assert(gradient1 != end(_gradients));
Assert(gradient2 != end(_gradients));
const auto stopsFrom = gradient1->second.stops();
const auto stopsTo = gradient2->second.stops();
if ((stopsFrom.size() == stopsTo.size())
&& ranges::equal(
stopsFrom,
stopsTo,
ranges::equal_to(),
&QGradientStop::first,
&QGradientStop::first)) {
const auto size = stopsFrom.size();
const auto &p = b_ratio;
for (auto i = 0; i < size; i++) {
auto c = color(stopsFrom[i].second, stopsTo[i].second, p);
gradient.setColorAt(stopsTo[i].first, std::move(c));
}
return gradient;
}
const auto invert = (stopsFrom.size() > stopsTo.size());
if (invert) {
b_ratio = 1. - b_ratio;
}
const auto &stops1 = invert ? stopsTo : stopsFrom;
const auto &stops2 = invert ? stopsFrom : stopsTo;
const auto size1 = stops1.size();
const auto size2 = stops2.size();
for (auto i = 0; i < size1; i++) {
const auto point1 = stops1[i].first;
const auto previousPoint1 = i ? stops1[i - 1].first : -1.;
for (auto n = 0; n < size2; n++) {
const auto point2 = stops2[n].first;
if ((point2 <= previousPoint1) || (point2 > point1)) {
continue;
}
const auto color2 = stops2[n].second;
QColor result;
if (point2 < point1) {
const auto pointRatio2 = (point2 - previousPoint1)
/ (point1 - previousPoint1);
const auto color1 = color(
stops1[i - 1].second,
stops1[i].second,
pointRatio2);
result = color(color1, color2, b_ratio);
} else {
// point2 == point1
result = color(stops1[i].second, color2, b_ratio);
}
gradient.setColorAt(point2, std::move(result));
}
}
return gradient;
}
protected:
void cache_gradients() {
auto copy = std::move(_gradients);
for (const auto &[key, value] : copy) {
_gradients.emplace(key, gradient_with_stops(value.stops()));
}
}
private:
QGradient empty_gradient() const {
return static_cast<const Derived*>(this)->empty_gradient();
}
QGradient gradient_with_stops(QGradientStops stops) const {
auto gradient = empty_gradient();
gradient.setStops(std::move(stops));
return gradient;
}
base::flat_map<T, QGradient> _gradients;
};
} // namespace details
template <typename T>
class linear_gradients final
: public details::gradients<T, linear_gradients<T>> {
using parent = details::gradients<T, linear_gradients<T>>;
public:
linear_gradients(
base::flat_map<T, std::vector<QColor>> colors,
QPointF point1,
QPointF point2)
: parent(std::move(colors)) {
set_points(point1, point2);
}
linear_gradients(
base::flat_map<T, gradient_colors> colors,
QPointF point1,
QPointF point2)
: parent(std::move(colors)) {
set_points(point1, point2);
}
void set_points(QPointF point1, QPointF point2) {
if (_point1 == point1 && _point2 == point2) {
return;
}
_point1 = point1;
_point2 = point2;
parent::cache_gradients();
}
private:
friend class details::gradients<T, linear_gradients<T>>;
QGradient empty_gradient() const {
return QLinearGradient(_point1, _point2);
}
QPointF _point1;
QPointF _point2;
};
template <typename T>
class radial_gradients final
: public details::gradients<T, radial_gradients<T>> {
using parent = details::gradients<T, radial_gradients<T>>;
public:
radial_gradients(
base::flat_map<T, std::vector<QColor>> colors,
QPointF center,
float radius)
: parent(std::move(colors)) {
set_points(center, radius);
}
radial_gradients(
base::flat_map<T, gradient_colors> colors,
QPointF center,
float radius)
: parent(std::move(colors)) {
set_points(center, radius);
}
void set_points(QPointF center, float radius) {
if (_center == center && _radius == radius) {
return;
}
_center = center;
_radius = radius;
parent::cache_gradients();
}
private:
friend class details::gradients<T, radial_gradients<T>>;
QGradient empty_gradient() const {
return QRadialGradient(_center, _radius);
}
QPointF _center;
float _radius = 0.;
};
} // namespace anim

View file

@ -19,7 +19,7 @@ NumbersAnimation::NumbersAnimation(
: _font(font)
, _animationCallback(std::move(animationCallback)) {
for (auto ch = '0'; ch != '9'; ++ch) {
accumulate_max(_digitWidth, _font->m.width(ch));
accumulate_max(_digitWidth, _font->m.horizontalAdvance(ch));
}
}
@ -67,7 +67,7 @@ void NumbersAnimation::realSetText(QString text, int value) {
digit.from = digit.to;
digit.fromWidth = digit.toWidth;
digit.to = (newSize + i < size) ? QChar(0) : text[newSize + i - size];
digit.toWidth = digit.to.unicode() ? _font->m.width(digit.to) : 0;
digit.toWidth = digit.to.unicode() ? _font->m.horizontalAdvance(digit.to) : 0;
if (digit.from != digit.to) {
animating = true;
}

View file

@ -29,7 +29,7 @@ QPointer<RoundButton> BoxContent::addButton(
return addButton(
std::move(text),
std::move(clickCallback),
st::defaultBoxButton);
getDelegate()->style().button);
}
QPointer<RoundButton> BoxContent::addLeftButton(
@ -38,7 +38,7 @@ QPointer<RoundButton> BoxContent::addLeftButton(
return getDelegate()->addLeftButton(
std::move(text),
std::move(clickCallback),
st::defaultBoxButton);
getDelegate()->style().button);
}
void BoxContent::setInner(object_ptr<TWidget> inner) {
@ -245,8 +245,9 @@ void BoxContent::paintEvent(QPaintEvent *e) {
Painter p(this);
if (testAttribute(Qt::WA_OpaquePaintEvent)) {
for (auto rect : e->region().rects()) {
p.fillRect(rect, st::boxBg);
const auto &color = getDelegate()->style().bg;
for (const auto rect : e->region()) {
p.fillRect(rect, color);
}
}
}

View file

@ -45,6 +45,7 @@ class BoxContentDelegate {
public:
virtual void setLayerType(bool layerType) = 0;
virtual void setStyle(const style::Box &st) = 0;
virtual const style::Box &style() = 0;
virtual void setTitle(rpl::producer<TextWithEntities> title) = 0;
virtual void setAdditionalTitle(rpl::producer<QString> additional) = 0;
virtual void setCloseByOutsideClick(bool close) = 0;
@ -125,6 +126,8 @@ public:
void scrollToWidget(not_null<QWidget*> widget);
virtual void showFinished() {
}
void clearButtons() {
getDelegate()->clearButtons();
}

View file

@ -42,7 +42,7 @@ BoxLayerWidget::BoxLayerWidget(
: LayerWidget(layer)
, _layer(layer)
, _content(std::move(content))
, _roundRect(ImageRoundRadius::Small, st::boxBg) {
, _roundRect(ImageRoundRadius::Small, st().bg) {
_content->setParent(this);
_content->setDelegate(this);
@ -74,12 +74,21 @@ const style::Box &BoxLayerWidget::st() const {
return _st
? *_st
: _layerType
? st::layerBox
: st::defaultBox;
? (_layer->boxStyleOverrideLayer()
? *_layer->boxStyleOverrideLayer()
: st::layerBox)
: (_layer->boxStyleOverride()
? *_layer->boxStyleOverride()
: st::defaultBox);
}
void BoxLayerWidget::setStyle(const style::Box &st) {
_st = &st;
_roundRect.setColor(st.bg);
}
const style::Box &BoxLayerWidget::style() {
return st();
}
int BoxLayerWidget::buttonsHeight() const {
@ -117,8 +126,8 @@ void BoxLayerWidget::paintEvent(QPaintEvent *e) {
}
auto other = e->region().intersected(QRect(0, st::boxRadius, width(), height() - 2 * st::boxRadius));
if (!other.isEmpty()) {
for (auto rect : other.rects()) {
p.fillRect(rect, st::boxBg);
for (const auto rect : other) {
p.fillRect(rect, st().bg);
}
}
if (!_additionalTitle.current().isEmpty()
@ -137,21 +146,29 @@ void BoxLayerWidget::paintEvent(QPaintEvent *e) {
void BoxLayerWidget::paintAdditionalTitle(Painter &p) {
p.setFont(st::boxTitleAdditionalFont);
p.setPen(st::boxTitleAdditionalFg);
p.drawTextLeft(_titleLeft + (_title ? _title->width() : 0) + st::boxTitleAdditionalSkip, _titleTop + st::boxTitleFont->ascent - st::boxTitleAdditionalFont->ascent, width(), _additionalTitle.current());
p.setPen(st().titleAdditionalFg);
p.drawTextLeft(
_titleLeft + (_title ? _title->width() : 0) + st::boxTitleAdditionalSkip,
_titleTop + st::boxTitleFont->ascent - st::boxTitleAdditionalFont->ascent,
width(),
_additionalTitle.current());
}
void BoxLayerWidget::parentResized() {
auto newHeight = countRealHeight();
auto parentSize = parentWidget()->size();
setGeometry((parentSize.width() - width()) / 2, (parentSize.height() - newHeight) / 2, width(), newHeight);
setGeometry(
(parentSize.width() - width()) / 2,
(parentSize.height() - newHeight) / 2,
width(),
newHeight);
update();
}
void BoxLayerWidget::setTitle(rpl::producer<TextWithEntities> title) {
const auto wasTitle = hasTitle();
if (title) {
_title.create(this, rpl::duplicate(title), st::boxTitle);
_title.create(this, rpl::duplicate(title), st().title);
_title->show();
std::move(
title
@ -323,9 +340,10 @@ void BoxLayerWidget::setDimensions(int newWidth, int maxHeight, bool forceCenter
resize(newWidth, countRealHeight());
auto newGeometry = geometry();
auto parentHeight = parentWidget()->height();
if (newGeometry.top() + newGeometry.height() + st::boxVerticalMargin > parentHeight
const auto bottomMargin = st().margin.bottom();
if (newGeometry.top() + newGeometry.height() + bottomMargin > parentHeight
|| forceCenterPosition) {
const auto top1 = parentHeight - int(st::boxVerticalMargin) - newGeometry.height();
const auto top1 = parentHeight - bottomMargin - newGeometry.height();
const auto top2 = (parentHeight - newGeometry.height()) / 2;
const auto newTop = forceCenterPosition
? std::min(top1, top2)
@ -343,7 +361,10 @@ void BoxLayerWidget::setDimensions(int newWidth, int maxHeight, bool forceCenter
}
int BoxLayerWidget::countRealHeight() const {
return qMin(_fullHeight, parentWidget()->height() - 2 * st::boxVerticalMargin);
const auto &margin = st().margin;
return std::min(
_fullHeight,
parentWidget()->height() - margin.top() - margin.bottom());
}
int BoxLayerWidget::countFullHeight() const {

View file

@ -45,6 +45,7 @@ public:
void setLayerType(bool layerType) override;
void setStyle(const style::Box &st) override;
const style::Box &style() override;
void setTitle(rpl::producer<TextWithEntities> title) override;
void setAdditionalTitle(rpl::producer<QString> additional) override;
void showBox(
@ -52,6 +53,10 @@ public:
LayerOptions options,
anim::type animated) override;
void showFinished() override {
_content->showFinished();
}
void clearButtons() override;
QPointer<RoundButton> addButton(
rpl::producer<QString> text,

View file

@ -38,6 +38,9 @@ public:
void setFocusCallback(Fn<void()> callback) {
_focus = callback;
}
void setShowFinishedCallback(Fn<void()> callback) {
_showFinished = callback;
}
int rowsCount() const {
return _content->count();
@ -76,6 +79,11 @@ public:
BoxContent::setInnerFocus();
}
}
void showFinished() override {
if (_showFinished) {
_showFinished();
}
}
[[nodiscard]] not_null<Ui::VerticalLayout*> verticalLayout();
@ -111,6 +119,7 @@ private:
FnMut<void(not_null<GenericBox*>)> _init;
Fn<void()> _focus;
Fn<void()> _showFinished;
object_ptr<Ui::VerticalLayout> _content;
int _width = 0;

View file

@ -11,6 +11,16 @@ namespace Ui {
LayerManager::LayerManager(not_null<RpWidget*> widget) : _widget(widget) {
}
void LayerManager::setStyleOverrides(
const style::Box *boxSt,
const style::Box *layerSt) {
_boxSt = boxSt;
_layerSt = layerSt;
if (_layer) {
_layer->setStyleOverrides(_boxSt, _layerSt);
}
}
void LayerManager::setHideByBackgroundClick(bool hide) {
_hideByBackgroundClick = hide;
if (_layer) {
@ -55,6 +65,7 @@ void LayerManager::ensureLayerCreated() {
}
_layer.emplace(_widget);
_layer->setHideByBackgroundClick(_hideByBackgroundClick);
_layer->setStyleOverrides(_boxSt, _layerSt);
_layer->hideFinishEvents(
) | rpl::filter([=] {

View file

@ -10,6 +10,10 @@
#include <QtCore/QMargins>
namespace style {
struct Box;
} // namespace style
namespace Ui {
class BoxContent;
@ -19,6 +23,10 @@ class LayerManager final {
public:
explicit LayerManager(not_null<RpWidget*> widget);
void setStyleOverrides(
const style::Box *boxSt,
const style::Box *layerSt);
void setHideByBackgroundClick(bool hide);
void showBox(
object_ptr<BoxContent> box,
@ -34,6 +42,9 @@ private:
const not_null<RpWidget*> _widget;
base::unique_qptr<LayerStackWidget> _layer;
const style::Box *_boxSt = nullptr;
const style::Box *_layerSt = nullptr;
bool _hideByBackgroundClick = false;
};

View file

@ -280,8 +280,8 @@ void LayerStackWidget::BackgroundWidget::paintEvent(QPaintEvent *e) {
// (alpha_final - alpha_current) / (1 - alpha_current) so we won't get glitches
// in the transparent special layer cache corners after filling special layer
// rect above its cache with alpha_current opacity.
auto region = QRegion(bg) - specialLayerBox;
for (auto rect : region.rects()) {
const auto region = QRegion(bg) - specialLayerBox;
for (const auto rect : region) {
p.fillRect(rect, st::layerBg);
}
p.setOpacity((bgOpacity - overSpecialOpacity) / (1. - (overSpecialOpacity * st::layerBg->c.alphaF())));
@ -458,6 +458,13 @@ bool LayerStackWidget::layerShown() const {
return _specialLayer || currentLayer() || _mainMenu;
}
void LayerStackWidget::setStyleOverrides(
const style::Box *boxSt,
const style::Box *layerSt) {
_boxSt = boxSt;
_layerSt = layerSt;
}
void LayerStackWidget::setCacheImages() {
auto bodyCache = QPixmap(), mainMenuCache = QPixmap();
auto specialLayerCache = QPixmap();

View file

@ -16,6 +16,10 @@ class SectionMemento;
struct SectionShow;
} // namespace Window
namespace style {
struct Box;
} // namespace style
namespace Ui {
class BoxContent;
@ -93,6 +97,16 @@ public:
void finishAnimating();
rpl::producer<> hideFinishEvents() const;
void setStyleOverrides(
const style::Box *boxSt,
const style::Box *layerSt);
[[nodiscard]] const style::Box *boxStyleOverrideLayer() const {
return _layerSt;
}
[[nodiscard]] const style::Box *boxStyleOverride() const {
return _boxSt;
}
void showBox(
object_ptr<BoxContent> box,
LayerOptions options,
@ -201,6 +215,9 @@ private:
class BackgroundWidget;
object_ptr<BackgroundWidget> _background;
const style::Box *_boxSt = nullptr;
const style::Box *_layerSt = nullptr;
bool _hideByBackgroundClick = true;
rpl::event_stream<> _hideFinishStream;

View file

@ -24,6 +24,11 @@ ServiceCheck {
Box {
buttonPadding: margins;
buttonHeight: pixels;
button: RoundButton;
margin: margins;
title: FlatLabel;
bg: color;
titleAdditionalFg: color;
}
boxDuration: 200;
@ -36,12 +41,6 @@ defaultBoxButton: RoundButton(defaultLightButton) {
font: boxButtonFont;
}
boxTextStyle: TextStyle(defaultTextStyle) {
font: font(boxFontSize);
linkFont: font(boxFontSize);
linkFontOver: font(boxFontSize underline);
}
boxLabelStyle: TextStyle(boxTextStyle) {
lineHeight: 22px;
}
@ -118,7 +117,6 @@ boxOptionListPadding: margins(0px, 0px, 0px, 0px);
boxOptionListSkip: 20px;
boxOptionInputSkip: 6px;
boxVerticalMargin: 10px;
boxWidth: 320px;
boxWideWidth: 364px;
boxPadding: margins(22px, 30px, 22px, 8px);
@ -129,6 +127,11 @@ boxMediumSkip: 20px;
defaultBox: Box {
buttonPadding: margins(8px, 12px, 13px, 12px);
buttonHeight: 36px;
button: defaultBoxButton;
margin: margins(0px, 10px, 0px, 10px);
bg: boxBg;
title: boxTitle;
titleAdditionalFg: boxTitleAdditionalFg;
}
layerBox: Box(defaultBox) {
buttonPadding: margins(8px, 8px, 8px, 8px);

243
ui/paint/blob.cpp Normal file
View file

@ -0,0 +1,243 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "ui/paint/blob.h"
#include "base/openssl_help.h"
#include "ui/painter.h"
#include <QtGui/QPainterPath>
#include <QtCore/QtMath>
namespace Ui::Paint {
namespace {
constexpr auto kMaxSpeed = 8.2;
constexpr auto kMinSpeed = 0.8;
constexpr auto kMinSegmentSpeed = 0.017;
constexpr auto kSegmentSpeedDiff = 0.003;
float64 RandomAdditional() {
return (openssl::RandomValue<int>() % 100 / 100.);
}
} // namespace
Blob::Blob(int n, float minSpeed, float maxSpeed)
: _segmentsCount(n)
, _minSpeed(minSpeed ? minSpeed : kMinSpeed)
, _maxSpeed(maxSpeed ? maxSpeed : kMaxSpeed)
, _pen(Qt::NoBrush, 0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin) {
}
void Blob::generateBlob() {
for (auto i = 0; i < _segmentsCount; i++) {
generateSingleValues(i);
// Fill nexts.
generateTwoValues(i);
// Fill currents.
generateTwoValues(i);
}
}
void Blob::generateSingleValues(int i) {
auto &segment = segmentAt(i);
segment.progress = 0.;
segment.speed = kMinSegmentSpeed
+ kSegmentSpeedDiff * std::abs(RandomAdditional());
}
void Blob::update(float level, float speedScale) {
for (auto i = 0; i < _segmentsCount; i++) {
auto &segment = segmentAt(i);
segment.progress += (segment.speed * _minSpeed)
+ level * segment.speed * _maxSpeed * speedScale;
if (segment.progress >= 1) {
generateSingleValues(i);
generateTwoValues(i);
}
}
}
void Blob::setRadiuses(Radiuses values) {
_radiuses = values;
}
Blob::Radiuses Blob::radiuses() const {
return _radiuses;
}
RadialBlob::RadialBlob(int n, float minScale, float minSpeed, float maxSpeed)
: Blob(n, minSpeed, maxSpeed)
, _segmentLength((4.0 / 3.0) * std::tan(M_PI / (2 * n)))
, _minScale(minScale)
, _segmentAngle(360. / n)
, _angleDiff(_segmentAngle * 0.05)
, _segments(n) {
}
void RadialBlob::paint(Painter &p, const QBrush &brush, float outerScale) {
auto path = QPainterPath();
auto m = QMatrix();
p.save();
const auto scale = (_minScale + (1. - _minScale) * _scale) * outerScale;
if (scale == 0.) {
p.restore();
return;
} else if (scale != 1.) {
p.scale(scale, scale);
}
for (auto i = 0; i < _segmentsCount; i++) {
const auto &segment = _segments[i];
const auto nextIndex = i + 1 < _segmentsCount ? (i + 1) : 0;
const auto nextSegment = _segments[nextIndex];
const auto progress = segment.progress;
const auto progressNext = nextSegment.progress;
const auto r1 = segment.radius.current * (1. - progress)
+ segment.radius.next * progress;
const auto r2 = nextSegment.radius.current * (1. - progressNext)
+ nextSegment.radius.next * progressNext;
const auto angle1 = segment.angle.current * (1. - progress)
+ segment.angle.next * progress;
const auto angle2 = nextSegment.angle.current * (1. - progressNext)
+ nextSegment.angle.next * progressNext;
const auto l = _segmentLength * (std::min(r1, r2)
+ (std::max(r1, r2) - std::min(r1, r2)) / 2.);
m.reset();
m.rotate(angle1);
const auto pointStart1 = m.map(QPointF(0, -r1));
const auto pointStart2 = m.map(QPointF(l, -r1));
m.reset();
m.rotate(angle2);
const auto pointEnd1 = m.map(QPointF(0, -r2));
const auto pointEnd2 = m.map(QPointF(-l, -r2));
if (i == 0) {
path.moveTo(pointStart1);
}
path.cubicTo(pointStart2, pointEnd2, pointEnd1);
}
p.setBrush(Qt::NoBrush);
p.setPen(_pen);
p.fillPath(path, brush);
p.drawPath(path);
p.restore();
}
void RadialBlob::generateTwoValues(int i) {
auto &radius = _segments[i].radius;
auto &angle = _segments[i].angle;
const auto radDiff = _radiuses.max - _radiuses.min;
angle.setNext(_segmentAngle * i + RandomAdditional() * _angleDiff);
radius.setNext(_radiuses.min + std::abs(RandomAdditional()) * radDiff);
}
void RadialBlob::update(float level, float speedScale) {
_scale = level;
Blob::update(level, speedScale);
}
Blob::Segment &RadialBlob::segmentAt(int i) {
return _segments[i];
};
LinearBlob::LinearBlob(
int n,
Direction direction,
float minSpeed,
float maxSpeed)
: Blob(n + 1)
, _topDown(direction == Direction::TopDown ? 1 : -1)
, _segments(_segmentsCount) {
}
void LinearBlob::paint(Painter &p, const QBrush &brush, int width) {
if (!width) {
return;
}
auto path = QPainterPath();
const auto left = 0;
const auto right = width;
path.moveTo(right, 0);
path.lineTo(left, 0);
const auto n = float(_segmentsCount - 1);
p.save();
for (auto i = 0; i < _segmentsCount; i++) {
const auto &segment = _segments[i];
if (!i) {
const auto &progress = segment.progress;
const auto r1 = segment.radius.current * (1. - progress)
+ segment.radius.next * progress;
const auto y = r1 * _topDown;
path.lineTo(left, y);
} else {
const auto &prevSegment = _segments[i - 1];
const auto &progress = prevSegment.progress;
const auto r1 = prevSegment.radius.current * (1. - progress)
+ prevSegment.radius.next * progress;
const auto &progressNext = segment.progress;
const auto r2 = segment.radius.current * (1. - progressNext)
+ segment.radius.next * progressNext;
const auto x1 = (right - left) / n * (i - 1);
const auto x2 = (right - left) / n * i;
const auto cx = x1 + (x2 - x1) / 2;
const auto y1 = r1 * _topDown;
const auto y2 = r2 * _topDown;
path.cubicTo(
QPointF(cx, y1),
QPointF(cx, y2),
QPointF(x2, y2)
);
}
}
path.lineTo(right, 0);
p.setBrush(Qt::NoBrush);
p.setPen(_pen);
p.fillPath(path, brush);
p.drawPath(path);
p.restore();
}
void LinearBlob::generateTwoValues(int i) {
auto &radius = _segments[i].radius;
const auto radDiff = _radiuses.max - _radiuses.min;
radius.setNext(_radiuses.min + std::abs(RandomAdditional()) * radDiff);
}
Blob::Segment &LinearBlob::segmentAt(int i) {
return _segments[i];
};
} // namespace Ui::Paint

113
ui/paint/blob.h Normal file
View file

@ -0,0 +1,113 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
class Painter;
namespace Ui::Paint {
class Blob {
public:
struct Radiuses {
float min = 0.;
float max = 0.;
};
Blob(int n, float minSpeed = 0, float maxSpeed = 0);
virtual ~Blob() = default;
void update(float level, float speedScale);
void generateBlob();
void setRadiuses(Radiuses values);
[[nodiscard]] Radiuses radiuses() const;
protected:
struct TwoValues {
float current = 0.;
float next = 0.;
void setNext(float v) {
current = next;
next = v;
}
};
struct Segment {
float progress = 0.;
float speed = 0.;
};
void generateSingleValues(int i);
virtual void generateTwoValues(int i) = 0;
virtual Segment &segmentAt(int i) = 0;
const int _segmentsCount;
const float _minSpeed;
const float _maxSpeed;
const QPen _pen;
Radiuses _radiuses;
};
class RadialBlob final : public Blob {
public:
RadialBlob(int n, float minScale, float minSpeed = 0, float maxSpeed = 0);
void paint(Painter &p, const QBrush &brush, float outerScale = 1.);
void update(float level, float speedScale);
private:
struct Segment : Blob::Segment {
Blob::TwoValues radius;
Blob::TwoValues angle;
};
void generateTwoValues(int i) override;
Blob::Segment &segmentAt(int i) override;
const float64 _segmentLength;
const float _minScale;
const float _segmentAngle;
const float _angleDiff;
std::vector<Segment> _segments;
float64 _scale = 0;
};
class LinearBlob final : public Blob {
public:
enum class Direction {
TopDown,
BottomUp,
};
LinearBlob(
int n,
Direction direction = Direction::TopDown,
float minSpeed = 0,
float maxSpeed = 0);
void paint(Painter &p, const QBrush &brush, int width);
private:
struct Segment : Blob::Segment {
Blob::TwoValues radius;
};
void generateTwoValues(int i) override;
Blob::Segment &segmentAt(int i) override;
const int _topDown;
std::vector<Segment> _segments;
};
} // namespace Ui::Paint

99
ui/paint/blobs.cpp Normal file
View file

@ -0,0 +1,99 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "ui/paint/blobs.h"
#include "ui/painter.h"
namespace Ui::Paint {
Blobs::Blobs(
std::vector<BlobData> blobDatas,
float levelDuration,
float maxLevel)
: _maxLevel(maxLevel)
, _blobDatas(std::move(blobDatas))
, _levelValue(levelDuration) {
init();
}
void Blobs::init() {
for (const auto &data : _blobDatas) {
auto blob = Paint::RadialBlob(
data.segmentsCount,
data.minScale,
data.minSpeed,
data.maxSpeed);
blob.setRadiuses({ data.minRadius, data.maxRadius });
blob.generateBlob();
_blobs.push_back(std::move(blob));
}
}
float Blobs::maxRadius() const {
const auto maxOfRadiuses = [](const BlobData data) {
return std::max(data.maxRadius, data.minRadius);
};
const auto max = *ranges::max_element(
_blobDatas,
std::less<>(),
maxOfRadiuses);
return maxOfRadiuses(max);
}
int Blobs::size() const {
return _blobs.size();
}
void Blobs::setRadiusesAt(
rpl::producer<Blob::Radiuses> &&radiuses,
int index) {
Expects(index >= 0 && index < size());
std::move(
radiuses
) | rpl::start_with_next([=](Blob::Radiuses r) {
_blobs[index].setRadiuses(std::move(r));
}, _lifetime);
}
Blob::Radiuses Blobs::radiusesAt(int index) {
Expects(index >= 0 && index < size());
return _blobs[index].radiuses();
}
void Blobs::setLevel(float value) {
const auto to = std::min(_maxLevel, value) / _maxLevel;
_levelValue.start(to);
}
void Blobs::resetLevel() {
_levelValue.reset();
}
void Blobs::paint(Painter &p, const QBrush &brush, float outerScale) {
const auto opacity = p.opacity();
for (auto i = 0; i < _blobs.size(); i++) {
_blobs[i].update(_levelValue.current(), _blobDatas[i].speedScale);
const auto alpha = _blobDatas[i].alpha;
if (alpha != 1.) {
p.setOpacity(opacity * alpha);
}
_blobs[i].paint(p, brush, outerScale);
if (alpha != 1.) {
p.setOpacity(opacity);
}
}
}
void Blobs::updateLevel(crl::time dt) {
_levelValue.update((dt > 20) ? 17 : dt);
}
float64 Blobs::currentLevel() const {
return _levelValue.current();
}
} // namespace Ui::Paint

64
ui/paint/blobs.h Normal file
View file

@ -0,0 +1,64 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "ui/effects/animation_value.h"
#include "ui/paint/blob.h"
class Painter;
namespace Ui::Paint {
class Blobs final {
public:
struct BlobData {
int segmentsCount = 0;
float minScale = 0;
float minRadius = 0;
float maxRadius = 0;
float speedScale = 0;
float alpha = 0;
float minSpeed = 0;
float maxSpeed = 0;
};
Blobs(
std::vector<BlobData> blobDatas,
float levelDuration,
float maxLevel);
void setRadiusesAt(
rpl::producer<Blob::Radiuses> &&radiuses,
int index);
Blob::Radiuses radiusesAt(int index);
void setLevel(float value);
void resetLevel();
void paint(Painter &p, const QBrush &brush, float outerScale = 1.);
void updateLevel(crl::time dt);
[[nodiscard]] float maxRadius() const;
[[nodiscard]] int size() const;
[[nodiscard]] float64 currentLevel() const;
static constexpr auto kHideBlobsDuration = 2000;
private:
void init();
const float _maxLevel;
std::vector<BlobData> _blobDatas;
std::vector<RadialBlob> _blobs;
anim::continuous_value _levelValue;
rpl::lifetime _lifetime;
};
} // namespace Ui::Paint

107
ui/paint/blobs_linear.cpp Normal file
View file

@ -0,0 +1,107 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "ui/paint/blobs_linear.h"
#include "ui/painter.h"
namespace Ui::Paint {
LinearBlobs::LinearBlobs(
std::vector<BlobData> blobDatas,
float levelDuration,
float maxLevel,
LinearBlob::Direction direction)
: _maxLevel(maxLevel)
, _direction(direction)
, _blobDatas(std::move(blobDatas))
, _levelValue(levelDuration) {
init();
}
void LinearBlobs::init() {
for (const auto &data : _blobDatas) {
auto blob = Paint::LinearBlob(
data.segmentsCount,
_direction,
data.minSpeed,
data.maxSpeed);
blob.setRadiuses({ data.minRadius, data.idleRadius });
blob.generateBlob();
_blobs.push_back(std::move(blob));
}
}
float LinearBlobs::maxRadius() const {
const auto maxOfRadiuses = [](const BlobData &d) {
return std::max(d.idleRadius, std::max(d.maxRadius, d.minRadius));
};
const auto max = *ranges::max_element(
_blobDatas,
std::less<>(),
maxOfRadiuses);
return maxOfRadiuses(max);
}
int LinearBlobs::size() const {
return _blobs.size();
}
void LinearBlobs::setRadiusesAt(
rpl::producer<Blob::Radiuses> &&radiuses,
int index) {
Expects(index >= 0 && index < size());
std::move(
radiuses
) | rpl::start_with_next([=](Blob::Radiuses r) {
_blobs[index].setRadiuses(std::move(r));
}, _lifetime);
}
Blob::Radiuses LinearBlobs::radiusesAt(int index) {
Expects(index >= 0 && index < size());
return _blobs[index].radiuses();
}
void LinearBlobs::setLevel(float value) {
const auto to = std::min(_maxLevel, value) / _maxLevel;
_levelValue.start(to);
}
void LinearBlobs::paint(Painter &p, const QBrush &brush, int width) {
PainterHighQualityEnabler hq(p);
const auto opacity = p.opacity();
for (auto i = 0; i < _blobs.size(); i++) {
_blobs[i].update(_levelValue.current(), _blobDatas[i].speedScale);
const auto alpha = _blobDatas[i].alpha;
if (alpha != 1.) {
p.setOpacity(opacity * alpha);
}
_blobs[i].paint(p, brush, width);
if (alpha != 1.) {
p.setOpacity(opacity);
}
}
}
void LinearBlobs::updateLevel(crl::time dt) {
const auto d = (dt > 20) ? 17 : dt;
_levelValue.update(d);
const auto level = (float)currentLevel();
for (auto i = 0; i < _blobs.size(); i++) {
const auto &data = _blobDatas[i];
_blobs[i].setRadiuses({
data.minRadius,
data.idleRadius + (data.maxRadius - data.idleRadius) * level });
}
}
float64 LinearBlobs::currentLevel() const {
return _levelValue.current();
}
} // namespace Ui::Paint

65
ui/paint/blobs_linear.h Normal file
View file

@ -0,0 +1,65 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "ui/effects/animation_value.h"
#include "ui/paint/blob.h"
class Painter;
namespace Ui::Paint {
class LinearBlobs final {
public:
struct BlobData {
int segmentsCount = 0;
float minRadius = 0;
float maxRadius = 0;
float idleRadius = 0;
float speedScale = 0;
float alpha = 0;
float minSpeed = 0;
float maxSpeed = 0;
};
LinearBlobs(
std::vector<BlobData> blobDatas,
float levelDuration,
float maxLevel,
LinearBlob::Direction direction);
void setRadiusesAt(
rpl::producer<Blob::Radiuses> &&radiuses,
int index);
Blob::Radiuses radiusesAt(int index);
void setLevel(float value);
void paint(Painter &p, const QBrush &brush, int width);
void updateLevel(crl::time dt);
[[nodiscard]] float maxRadius() const;
[[nodiscard]] int size() const;
[[nodiscard]] float64 currentLevel() const;
static constexpr auto kHideBlobsDuration = 2000;
private:
void init();
const float _maxLevel;
const LinearBlob::Direction _direction;
std::vector<BlobData> _blobDatas;
std::vector<LinearBlob> _blobs;
anim::continuous_value _levelValue;
rpl::lifetime _lifetime;
};
} // namespace Ui::Paint

View file

@ -19,13 +19,13 @@ public:
void drawTextLeft(int x, int y, int outerw, const QString &text, int textWidth = -1) {
QFontMetrics m(fontMetrics());
auto ascent = (_ascent == 0 ? m.ascent() : _ascent);
if (style::RightToLeft() && textWidth < 0) textWidth = m.width(text);
if (style::RightToLeft() && textWidth < 0) textWidth = m.horizontalAdvance(text);
drawText(style::RightToLeft() ? (outerw - x - textWidth) : x, y + ascent, text);
}
void drawTextRight(int x, int y, int outerw, const QString &text, int textWidth = -1) {
QFontMetrics m(fontMetrics());
auto ascent = (_ascent == 0 ? m.ascent() : _ascent);
if (!style::RightToLeft() && textWidth < 0) textWidth = m.width(text);
if (!style::RightToLeft() && textWidth < 0) textWidth = m.horizontalAdvance(text);
drawText(style::RightToLeft() ? x : (outerw - x - textWidth), y + ascent, text);
}
void drawPixmapLeft(int x, int y, int outerw, const QPixmap &pix, const QRect &from) {
@ -91,8 +91,7 @@ public:
static constexpr QPainter::RenderHint Hints[] = {
QPainter::Antialiasing,
QPainter::SmoothPixmapTransform,
QPainter::TextAntialiasing,
QPainter::HighQualityAntialiasing
QPainter::TextAntialiasing
};
const auto hints = _painter.renderHints();
@ -119,6 +118,6 @@ public:
private:
QPainter &_painter;
QPainter::RenderHints _hints = 0;
QPainter::RenderHints _hints;
};

View file

@ -6,13 +6,14 @@
//
#include "ui/platform/linux/ui_utility_linux.h"
#include "base/flat_set.h"
#include "ui/ui_log.h"
#include "base/platform/base_platform_info.h"
#include "base/qt_adapters.h"
#include "base/flat_set.h"
#include <QtCore/QPoint>
#include <QtGui/QScreen>
#include <QtWidgets/QApplication>
#include <QtWidgets/QDesktopWidget>
#include <qpa/qplatformnativeinterface.h>
namespace Ui {
@ -28,19 +29,18 @@ bool TranslucentWindowsSupported(QPoint globalPosition) {
}
if (const auto native = QGuiApplication::platformNativeInterface()) {
if (const auto desktop = QApplication::desktop()) {
const auto index = desktop->screenNumber(globalPosition);
const auto screens = QGuiApplication::screens();
if (const auto screen = (index >= 0 && index < screens.size()) ? screens[index] : QGuiApplication::primaryScreen()) {
if (const auto screen = base::QScreenNearestTo(globalPosition)) {
if (native->nativeResourceForScreen(QByteArray("compositingEnabled"), screen)) {
return true;
}
const auto index = QGuiApplication::screens().indexOf(screen);
static auto WarnedAbout = base::flat_set<int>();
if (!WarnedAbout.contains(index)) {
WarnedAbout.emplace(index);
UI_LOG(("WARNING: Compositing is disabled for screen index %1 (for position %2,%3)").arg(index).arg(globalPosition.x()).arg(globalPosition.y()));
}
} else {
UI_LOG(("WARNING: Could not get screen for index %1 (for position %2,%3)").arg(index).arg(globalPosition.x()).arg(globalPosition.y()));
UI_LOG(("WARNING: Could not get screen for position %1,%2").arg(globalPosition.x()).arg(globalPosition.y()));
}
}
}

View file

@ -12,7 +12,7 @@ class QPaintEvent;
namespace Ui {
namespace Platform {
inline void StartTranslucentPaint(QPainter &p, gsl::span<const QRect> rects) {
inline void StartTranslucentPaint(QPainter &p, const QRegion &region) {
}
inline void InitOnTopPanel(not_null<QWidget*> panel) {

View file

@ -68,10 +68,10 @@ void ReInitOnTopPanel(not_null<QWidget*> panel) {
[platformPanel setCollectionBehavior:newBehavior];
}
void StartTranslucentPaint(QPainter &p, gsl::span<const QRect> rects) {
void StartTranslucentPaint(QPainter &p, const QRegion &region) {
p.setCompositionMode(QPainter::CompositionMode_Source);
for (const auto &r : rects) {
p.fillRect(r, Qt::transparent);
for (const auto rect : region) {
p.fillRect(rect, Qt::transparent);
}
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
}

View file

@ -33,11 +33,12 @@ private:
void setupBodyTitleAreaEvents() override;
void init();
void toggleCustomTitle(bool visible);
void updateCustomTitleVisibility(bool force = false);
const std::unique_ptr<Private> _private;
const not_null<TitleWidget*> _title;
const not_null<RpWidget*> _body;
bool _titleVisible = true;
#ifdef OS_OSX
struct WindowDrag {

View file

@ -207,7 +207,8 @@ void WindowHelper::Private::close() {
Fn<void(bool)> WindowHelper::Private::toggleCustomTitleCallback() {
return crl::guard(_owner->window(), [=](bool visible) {
_owner->toggleCustomTitle(visible);
_owner->_titleVisible = visible;
_owner->updateCustomTitleVisibility(true);
});
}
@ -286,7 +287,7 @@ WindowHelper::WindowHelper(not_null<RpWidget*> window)
: nullptr)
, _body(Ui::CreateChild<RpWidget>(window.get())) {
if (_title->shouldBeHidden()) {
toggleCustomTitle(false);
updateCustomTitleVisibility();
}
init();
}
@ -303,27 +304,25 @@ void WindowHelper::setTitle(const QString &title) {
_title->setText(title);
}
window()->setWindowTitle(
(!_title || _title->isHidden()) ? title : QString());
(!_title || !_titleVisible) ? title : QString());
}
void WindowHelper::setTitleStyle(const style::WindowTitle &st) {
if (_title) {
_title->setStyle(st);
if (_title->shouldBeHidden()) {
toggleCustomTitle(false);
updateCustomTitleVisibility();
}
}
}
void WindowHelper::toggleCustomTitle(bool visible) {
if (_title->shouldBeHidden()) {
visible = false;
}
if (!_title || _title->isHidden() != visible) {
void WindowHelper::updateCustomTitleVisibility(bool force) {
auto visible = !_title->shouldBeHidden() && _titleVisible;
if (!_title || (!force && _title->isHidden() != visible)) {
return;
}
_title->setVisible(visible);
window()->setWindowTitle(visible ? QString() : _title->text());
window()->setWindowTitle(_titleVisible ? QString() : _title->text());
}
void WindowHelper::setMinimumSize(QSize size) {

View file

@ -16,7 +16,7 @@ namespace Platform {
[[nodiscard]] bool IsApplicationActive();
[[nodiscard]] bool TranslucentWindowsSupported(QPoint globalPosition);
void StartTranslucentPaint(QPainter &p, gsl::span<const QRect> rects);
void StartTranslucentPaint(QPainter &p, const QRegion &region);
void InitOnTopPanel(not_null<QWidget*> panel);
void DeInitOnTopPanel(not_null<QWidget*> panel);

View file

@ -27,7 +27,7 @@ inline void DeInitOnTopPanel(not_null<QWidget*> panel) {
inline void ReInitOnTopPanel(not_null<QWidget*> panel) {
}
inline void StartTranslucentPaint(QPainter &p, gsl::span<const QRect> rects) {
inline void StartTranslucentPaint(QPainter &p, const QRegion &region) {
}
inline void ShowOverAll(not_null<QWidget*> widget, bool canFocus) {

View file

@ -67,22 +67,25 @@ RoundRect::RoundRect(
ImageRoundRadius radius,
const style::color &color)
: _color(color)
, _corners(Images::PrepareCorners(radius, color)) {
, _refresh([=] { _corners = Images::PrepareCorners(radius, _color); }) {
_refresh();
style::PaletteChanged(
) | rpl::start_with_next([=] {
_corners = Images::PrepareCorners(radius, _color);
}, _lifetime);
) | rpl::start_with_next(_refresh, _lifetime);
}
RoundRect::RoundRect(
int radius,
const style::color &color)
: _color(color)
, _corners(Images::PrepareCorners(radius, color)) {
, _refresh([=] { _corners = Images::PrepareCorners(radius, _color); }) {
_refresh();
style::PaletteChanged(
) | rpl::start_with_next([=] {
_corners = Images::PrepareCorners(radius, _color);
}, _lifetime);
) | rpl::start_with_next(_refresh, _lifetime);
}
void RoundRect::setColor(const style::color &color) {
_color = color;
_refresh();
}
const style::color &RoundRect::color() const {

View file

@ -27,6 +27,7 @@ public:
RoundRect(int radius, const style::color &color);
[[nodiscard]] const style::color &color() const;
void setColor(const style::color &color);
void paint(
QPainter &p,
const QRect &rect,
@ -39,6 +40,7 @@ public:
private:
style::color _color;
std::array<QImage, 4> _corners;
Fn<void()> _refresh;
rpl::lifetime _lifetime;

View file

@ -348,7 +348,7 @@ QString MonospaceFont() {
// Prefer system monospace font.
const auto metrics = QFontMetrics(QFont(system));
const auto useSystem = manual.isEmpty()
|| (metrics.charWidth("i", 0) == metrics.charWidth("W", 0));
|| (metrics.horizontalAdvance(QChar('i')) == metrics.horizontalAdvance(QChar('W')));
#endif // Q_OS_WIN || Q_OS_MAC
return (useSystem || UseSystemFont) ? system : manual;
}();

View file

@ -79,13 +79,13 @@ enum FontFlags {
class FontData {
public:
int width(const QString &str) const {
return m.width(str);
return m.horizontalAdvance(str);
}
int width(const QString &str, int32 from, int32 to) const {
return width(str.mid(from, to));
}
int width(QChar ch) const {
return m.width(ch);
return m.horizontalAdvance(ch);
}
QString elided(
const QString &str,

View file

@ -9,6 +9,7 @@
#include "base/qthelp_url.h"
#include "base/qthelp_regex.h"
#include "base/crc32hash.h"
#include "base/qt_adapters.h"
#include "ui/text/text.h"
#include "ui/widgets/input_fields.h"
#include "ui/emoji_config.h"
@ -1403,7 +1404,7 @@ QStringList PrepareSearchWords(
auto list = clean.split(SplitterOverride
? *SplitterOverride
: RegExpWordSplit(),
QString::SkipEmptyParts);
base::QStringSkipEmptyParts);
auto size = list.size();
result.reserve(list.size());
for (const auto &word : std::as_const(list)) {
@ -1949,6 +1950,20 @@ void Trim(TextWithEntities &result) {
}
}
int SerializeTagsSize(const TextWithTags::Tags &tags) {
auto result = qint32(0);
if (tags.isEmpty()) {
return result;
}
result += sizeof(qint32);
for (const auto &tag : tags) {
result += 2 * sizeof(qint32) // offset, length
+ sizeof(quint32) // id.size
+ tag.id.size() * sizeof(ushort);
}
return result;
}
QByteArray SerializeTags(const TextWithTags::Tags &tags) {
if (tags.isEmpty()) {
return QByteArray();

View file

@ -344,10 +344,13 @@ inline QString PrepareForSending(const QString &text, PrepareTextOption option =
// Replace bad symbols with space and remove '\r'.
void ApplyServerCleaning(TextWithEntities &result);
QByteArray SerializeTags(const TextWithTags::Tags &tags);
TextWithTags::Tags DeserializeTags(QByteArray data, int textLength);
QString TagsMimeType();
QString TagsTextMimeType();
[[nodiscard]] int SerializeTagsSize(const TextWithTags::Tags &tags);
[[nodiscard]] QByteArray SerializeTags(const TextWithTags::Tags &tags);
[[nodiscard]] TextWithTags::Tags DeserializeTags(
QByteArray data,
int textLength);
[[nodiscard]] QString TagsMimeType();
[[nodiscard]] QString TagsTextMimeType();
inline const auto kMentionTagStart = qstr("mention://user.");

View file

@ -236,8 +236,8 @@ RoundButton::RoundButton(
: RippleButton(parent, st.ripple)
, _textFull(std::move(text))
, _st(st)
, _roundRect(ImageRoundRadius::Small, _st.textBg)
, _roundRectOver(ImageRoundRadius::Small, _st.textBgOver) {
, _roundRect(st::buttonRadius, _st.textBg)
, _roundRectOver(st::buttonRadius, _st.textBgOver) {
_textFull.value(
) | rpl::start_with_next([=](const QString &text) {
resizeToText(text);

232
ui/widgets/call_button.cpp Normal file
View file

@ -0,0 +1,232 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "ui/widgets/call_button.h"
#include "ui/effects/ripple_animation.h"
#include "ui/painter.h"
#include "ui/widgets/labels.h"
#include "styles/style_widgets.h"
#include "styles/palette.h"
namespace Ui {
namespace {
constexpr auto kOuterBounceDuration = crl::time(100);
} // namespace
CallButton::CallButton(
QWidget *parent,
const style::CallButton &stFrom,
const style::CallButton *stTo)
: RippleButton(parent, stFrom.button.ripple)
, _stFrom(&stFrom)
, _stTo(stTo) {
resize(_stFrom->button.width, _stFrom->button.height);
_bgMask = RippleAnimation::ellipseMask(QSize(_stFrom->bgSize, _stFrom->bgSize));
_bgFrom = Ui::PixmapFromImage(style::colorizeImage(_bgMask, _stFrom->bg));
if (_stTo) {
Assert(_stFrom->button.width == _stTo->button.width);
Assert(_stFrom->button.height == _stTo->button.height);
Assert(_stFrom->bgPosition == _stTo->bgPosition);
Assert(_stFrom->bgSize == _stTo->bgSize);
_bg = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied);
_bg.setDevicePixelRatio(style::DevicePixelRatio());
_bgTo = Ui::PixmapFromImage(style::colorizeImage(_bgMask, _stTo->bg));
_iconMixedMask = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied);
_iconMixedMask.setDevicePixelRatio(style::DevicePixelRatio());
_iconFrom = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied);
_iconFrom.setDevicePixelRatio(style::DevicePixelRatio());
_iconFrom.fill(Qt::black);
{
QPainter p(&_iconFrom);
p.drawImage(
(_stFrom->bgSize
- _stFrom->button.icon.width()) / 2,
(_stFrom->bgSize
- _stFrom->button.icon.height()) / 2,
_stFrom->button.icon.instance(Qt::white));
}
_iconTo = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied);
_iconTo.setDevicePixelRatio(style::DevicePixelRatio());
_iconTo.fill(Qt::black);
{
QPainter p(&_iconTo);
p.drawImage(
(_stTo->bgSize
- _stTo->button.icon.width()) / 2,
(_stTo->bgSize
- _stTo->button.icon.height()) / 2,
_stTo->button.icon.instance(Qt::white));
}
_iconMixed = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied);
_iconMixed.setDevicePixelRatio(style::DevicePixelRatio());
}
}
void CallButton::setOuterValue(float64 value) {
if (_outerValue != value) {
_outerAnimation.start([this] {
if (_progress == 0. || _progress == 1.) {
update();
}
}, _outerValue, value, kOuterBounceDuration);
_outerValue = value;
}
}
void CallButton::setText(rpl::producer<QString> text) {
_label.create(this, std::move(text), _stFrom->label);
_label->show();
rpl::combine(
sizeValue(),
_label->sizeValue()
) | rpl::start_with_next([=](QSize my, QSize label) {
_label->moveToLeft(
(my.width() - label.width()) / 2,
my.height() - label.height(),
my.width());
}, _label->lifetime());
}
void CallButton::setProgress(float64 progress) {
_progress = progress;
update();
}
void CallButton::paintEvent(QPaintEvent *e) {
QPainter p(this);
auto bgPosition = myrtlpoint(_stFrom->bgPosition);
auto paintFrom = (_progress == 0.) || !_stTo;
auto paintTo = !paintFrom && (_progress == 1.);
auto outerValue = _outerAnimation.value(_outerValue);
if (outerValue > 0.) {
auto outerRadius = paintFrom ? _stFrom->outerRadius : paintTo ? _stTo->outerRadius : (_stFrom->outerRadius * (1. - _progress) + _stTo->outerRadius * _progress);
auto outerPixels = outerValue * outerRadius;
auto outerRect = QRectF(myrtlrect(bgPosition.x(), bgPosition.y(), _stFrom->bgSize, _stFrom->bgSize));
outerRect = outerRect.marginsAdded(QMarginsF(outerPixels, outerPixels, outerPixels, outerPixels));
PainterHighQualityEnabler hq(p);
if (paintFrom) {
p.setBrush(_stFrom->outerBg);
} else if (paintTo) {
p.setBrush(_stTo->outerBg);
} else {
p.setBrush(anim::brush(_stFrom->outerBg, _stTo->outerBg, _progress));
}
p.setPen(Qt::NoPen);
p.drawEllipse(outerRect);
}
if (_bgOverride) {
const auto &s = _stFrom->bgSize;
p.setPen(Qt::NoPen);
p.setBrush(*_bgOverride);
PainterHighQualityEnabler hq(p);
p.drawEllipse(QRect(_stFrom->bgPosition, QSize(s, s)));
} else if (paintFrom) {
p.drawPixmap(bgPosition, _bgFrom);
} else if (paintTo) {
p.drawPixmap(bgPosition, _bgTo);
} else {
style::colorizeImage(_bgMask, anim::color(_stFrom->bg, _stTo->bg, _progress), &_bg);
p.drawImage(bgPosition, _bg);
}
auto rippleColorInterpolated = QColor();
auto rippleColorOverride = &rippleColorInterpolated;
if (_rippleOverride) {
rippleColorOverride = &(*_rippleOverride);
} else if (paintFrom) {
rippleColorOverride = nullptr;
} else if (paintTo) {
rippleColorOverride = &_stTo->button.ripple.color->c;
} else {
rippleColorInterpolated = anim::color(_stFrom->button.ripple.color, _stTo->button.ripple.color, _progress);
}
paintRipple(p, _stFrom->button.rippleAreaPosition.x(), _stFrom->button.rippleAreaPosition.y(), rippleColorOverride);
auto positionFrom = iconPosition(_stFrom);
if (paintFrom) {
const auto icon = &_stFrom->button.icon;
icon->paint(p, positionFrom, width());
} else {
auto positionTo = iconPosition(_stTo);
if (paintTo) {
_stTo->button.icon.paint(p, positionTo, width());
} else {
mixIconMasks();
style::colorizeImage(_iconMixedMask, st::callIconFg->c, &_iconMixed);
p.drawImage(myrtlpoint(_stFrom->bgPosition), _iconMixed);
}
}
}
QPoint CallButton::iconPosition(not_null<const style::CallButton*> st) const {
auto result = st->button.iconPosition;
if (result.x() < 0) {
result.setX((width() - st->button.icon.width()) / 2);
}
if (result.y() < 0) {
result.setY((height() - st->button.icon.height()) / 2);
}
return result;
}
void CallButton::mixIconMasks() {
_iconMixedMask.fill(Qt::black);
Painter p(&_iconMixedMask);
PainterHighQualityEnabler hq(p);
auto paintIconMask = [this, &p](const QImage &mask, float64 angle) {
auto skipFrom = _stFrom->bgSize / 2;
p.translate(skipFrom, skipFrom);
p.rotate(angle);
p.translate(-skipFrom, -skipFrom);
p.drawImage(0, 0, mask);
};
p.save();
paintIconMask(_iconFrom, (_stFrom->angle - _stTo->angle) * _progress);
p.restore();
p.setOpacity(_progress);
paintIconMask(_iconTo, (_stTo->angle - _stFrom->angle) * (1. - _progress));
}
void CallButton::onStateChanged(State was, StateChangeSource source) {
RippleButton::onStateChanged(was, source);
auto over = isOver();
auto wasOver = static_cast<bool>(was & StateFlag::Over);
if (over != wasOver) {
update();
}
}
void CallButton::setColorOverrides(rpl::producer<CallButtonColors> &&colors) {
std::move(
colors
) | rpl::start_with_next([=](const CallButtonColors &c) {
_bgOverride = c.bg;
_rippleOverride = c.ripple;
update();
}, lifetime());
}
QPoint CallButton::prepareRippleStartPosition() const {
return mapFromGlobal(QCursor::pos()) - _stFrom->button.rippleAreaPosition;
}
QImage CallButton::prepareRippleMask() const {
return RippleAnimation::ellipseMask(QSize(_stFrom->button.rippleAreaSize, _stFrom->button.rippleAreaSize));
}
} // namespace Ui

64
ui/widgets/call_button.h Normal file
View file

@ -0,0 +1,64 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "base/object_ptr.h"
#include "ui/widgets/buttons.h"
#include "ui/effects/animations.h"
namespace Ui {
class FlatLabel;
struct CallButtonColors {
std::optional<QColor> bg;
std::optional<QColor> ripple;
};
class CallButton final : public RippleButton {
public:
CallButton(
QWidget *parent,
const style::CallButton &stFrom,
const style::CallButton *stTo = nullptr);
void setProgress(float64 progress);
void setOuterValue(float64 value);
void setText(rpl::producer<QString> text);
void setColorOverrides(rpl::producer<CallButtonColors> &&colors);
protected:
void paintEvent(QPaintEvent *e) override;
void onStateChanged(State was, StateChangeSource source) override;
QImage prepareRippleMask() const override;
QPoint prepareRippleStartPosition() const override;
private:
QPoint iconPosition(not_null<const style::CallButton*> st) const;
void mixIconMasks();
not_null<const style::CallButton*> _stFrom;
const style::CallButton *_stTo = nullptr;
float64 _progress = 0.;
object_ptr<FlatLabel> _label = { nullptr };
std::optional<QColor> _bgOverride;
std::optional<QColor> _rippleOverride;
QImage _bgMask, _bg;
QPixmap _bgFrom, _bgTo;
QImage _iconMixedMask, _iconFrom, _iconTo, _iconMixed;
float64 _outerValue = 0.;
Animations::Simple _outerAnimation;
};
} // namespace Ui

View file

@ -0,0 +1,937 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "ui/widgets/call_mute_button.h"
#include "base/flat_map.h"
#include "ui/abstract_button.h"
#include "ui/paint/blobs.h"
#include "ui/painter.h"
#include "ui/widgets/call_button.h"
#include "ui/widgets/labels.h"
#include "styles/palette.h"
#include "styles/style_widgets.h"
#include <QtCore/QtMath>
namespace Ui {
namespace {
using Radiuses = Paint::Blob::Radiuses;
constexpr auto kMaxLevel = 1.;
constexpr auto kLevelDuration = 100. + 500. * 0.33;
constexpr auto kScaleBig = 0.807 - 0.1;
constexpr auto kScaleSmall = 0.704 - 0.1;
constexpr auto kScaleBigMin = 0.878;
constexpr auto kScaleSmallMin = 0.926;
constexpr auto kScaleBigMax = (float)(kScaleBigMin + kScaleBig);
constexpr auto kScaleSmallMax = (float)(kScaleSmallMin + kScaleSmall);
constexpr auto kMainRadiusFactor = (float)(50. / 57.);
constexpr auto kGlowPaddingFactor = 1.2;
constexpr auto kGlowMinScale = 0.6;
constexpr auto kGlowAlpha = 150;
constexpr auto kOverrideColorBgAlpha = 76;
constexpr auto kOverrideColorRippleAlpha = 50;
constexpr auto kShiftDuration = crl::time(300);
constexpr auto kSwitchStateDuration = crl::time(120);
constexpr auto kSwitchLabelDuration = crl::time(180);
// Switch state from Connecting animation.
constexpr auto kSwitchRadialDuration = crl::time(350);
constexpr auto kSwitchCirclelDuration = crl::time(275);
constexpr auto kBlobsScaleEnterDuration = crl::time(400);
constexpr auto kSwitchStateFromConnectingDuration = kSwitchRadialDuration
+ kSwitchCirclelDuration
+ kBlobsScaleEnterDuration;
constexpr auto kRadialEndPartAnimation = float(kSwitchRadialDuration)
/ kSwitchStateFromConnectingDuration;
constexpr auto kBlobsWidgetPartAnimation = 1. - kRadialEndPartAnimation;
constexpr auto kFillCirclePartAnimation = float(kSwitchCirclelDuration)
/ (kSwitchCirclelDuration + kBlobsScaleEnterDuration);
constexpr auto kBlobPartAnimation = float(kBlobsScaleEnterDuration)
/ (kSwitchCirclelDuration + kBlobsScaleEnterDuration);
constexpr auto kOverlapProgressRadialHide = 1.2;
constexpr auto kRadialFinishArcShift = 1200;
auto MuteBlobs() {
return std::vector<Paint::Blobs::BlobData>{
{
.segmentsCount = 9,
.minScale = kScaleSmallMin / kScaleSmallMax,
.minRadius = st::callMuteMinorBlobMinRadius
* kScaleSmallMax
* kMainRadiusFactor,
.maxRadius = st::callMuteMinorBlobMaxRadius
* kScaleSmallMax
* kMainRadiusFactor,
.speedScale = 1.,
.alpha = (76. / 255.),
},
{
.segmentsCount = 12,
.minScale = kScaleBigMin / kScaleBigMax,
.minRadius = st::callMuteMajorBlobMinRadius
* kScaleBigMax
* kMainRadiusFactor,
.maxRadius = st::callMuteMajorBlobMaxRadius
* kScaleBigMax
* kMainRadiusFactor,
.speedScale = 1.,
.alpha = (76. / 255.),
},
};
}
auto Colors() {
using Vector = std::vector<QColor>;
using Colors = anim::gradient_colors;
return base::flat_map<CallMuteButtonType, Colors>{
{
CallMuteButtonType::ForceMuted,
Colors(QGradientStops{
{ .0, st::groupCallForceMuted1->c },
{ .5, st::groupCallForceMuted2->c },
{ 1., st::groupCallForceMuted3->c } })
},
{
CallMuteButtonType::Active,
Colors(Vector{ st::groupCallLive1->c, st::groupCallLive2->c })
},
{
CallMuteButtonType::Connecting,
Colors(st::callIconBg->c)
},
{
CallMuteButtonType::Muted,
Colors(Vector{ st::groupCallMuted1->c, st::groupCallMuted2->c })
},
};
}
bool IsMuted(CallMuteButtonType type) {
return (type != CallMuteButtonType::Active);
}
bool IsConnecting(CallMuteButtonType type) {
return (type == CallMuteButtonType::Connecting);
}
bool IsInactive(CallMuteButtonType type) {
return IsConnecting(type) || (type == CallMuteButtonType::ForceMuted);
}
auto Clamp(float64 value) {
return std::clamp(value, 0., 1.);
}
void ComputeRadialFinish(
int &value,
float64 progress,
int to = -RadialState::kFull) {
value = anim::interpolate(value, to, Clamp(progress));
}
} // namespace
class AnimatedLabel final : public RpWidget {
public:
AnimatedLabel(
QWidget *parent,
rpl::producer<QString> &&text,
crl::time duration,
int additionalHeight,
const style::FlatLabel &st = st::defaultFlatLabel);
int height() const;
private:
int realHeight() const;
void setText(const QString &text);
const style::FlatLabel &_st;
const crl::time _duration;
const int _additionalHeight;
const TextParseOptions _options;
Text::String _text;
Text::String _previousText;
Animations::Simple _animation;
};
AnimatedLabel::AnimatedLabel(
QWidget *parent,
rpl::producer<QString> &&text,
crl::time duration,
int additionalHeight,
const style::FlatLabel &st)
: RpWidget(parent)
, _st(st)
, _duration(duration)
, _additionalHeight(additionalHeight)
, _options({ 0, 0, 0, Qt::LayoutDirectionAuto }) {
std::move(
text
) | rpl::start_with_next([=](const QString &value) {
setText(value);
}, lifetime());
paintRequest(
) | rpl::start_with_next([=] {
Painter p(this);
const auto progress = _animation.value(1.);
p.setFont(_st.style.font);
p.setPen(_st.textFg);
p.setTextPalette(_st.palette);
const auto textHeight = height();
const auto diffHeight = realHeight() - textHeight;
const auto center = (diffHeight) / 2;
p.setOpacity(1. - progress);
if (p.opacity()) {
_previousText.draw(
p,
0,
anim::interpolate(center, diffHeight, progress),
width(),
style::al_center);
}
p.setOpacity(progress);
if (p.opacity()) {
_text.draw(
p,
0,
anim::interpolate(0, center, progress),
width(),
style::al_center);
}
}, lifetime());
}
int AnimatedLabel::height() const {
return _st.style.font->height;
}
int AnimatedLabel::realHeight() const {
return RpWidget::height();
}
void AnimatedLabel::setText(const QString &text) {
if (_text.toString() == text) {
return;
}
_previousText = _text;
_text.setText(_st.style, text, _options);
const auto width = std::max(
_st.style.font->width(_text.toString()),
_st.style.font->width(_previousText.toString()));
resize(width + _additionalHeight, height() + _additionalHeight * 2);
_animation.stop();
_animation.start([=] { update(); }, 0., 1., _duration);
}
class BlobsWidget final : public RpWidget {
public:
BlobsWidget(
not_null<RpWidget*> parent,
rpl::producer<bool> &&hideBlobs);
void setLevel(float level);
void setBlobBrush(QBrush brush);
void setGlowBrush(QBrush brush);
[[nodiscard]] QRectF innerRect() const;
[[nodiscard]] float64 switchConnectingProgress() const;
void setSwitchConnectingProgress(float64 progress);
private:
void init();
Paint::Blobs _blobs;
const float _circleRadius;
QBrush _blobBrush;
QBrush _glowBrush;
int _center = 0;
QRectF _circleRect;
float64 _switchConnectingProgress = 0.;
crl::time _blobsLastTime = 0;
crl::time _blobsHideLastTime = 0;
float64 _blobsScaleEnter = 0.;
crl::time _blobsScaleLastTime = 0;
bool _hideBlobs = true;
Animations::Basic _animation;
};
BlobsWidget::BlobsWidget(
not_null<RpWidget*> parent,
rpl::producer<bool> &&hideBlobs)
: RpWidget(parent)
, _blobs(MuteBlobs(), kLevelDuration, kMaxLevel)
, _circleRadius(st::callMuteButtonActive.bgSize / 2.)
, _blobBrush(Qt::transparent)
, _glowBrush(Qt::transparent)
, _blobsLastTime(crl::now())
, _blobsScaleLastTime(crl::now()) {
init();
std::move(
hideBlobs
) | rpl::start_with_next([=](bool hide) {
if (_hideBlobs != hide) {
const auto now = crl::now();
if ((now - _blobsScaleLastTime) >= kBlobsScaleEnterDuration) {
_blobsScaleLastTime = now;
}
_hideBlobs = hide;
}
if (hide) {
setLevel(0.);
}
_blobsHideLastTime = hide ? crl::now() : 0;
if (!hide && !_animation.animating()) {
_animation.start();
}
}, lifetime());
}
void BlobsWidget::init() {
setAttribute(Qt::WA_TransparentForMouseEvents);
{
const auto s = _blobs.maxRadius() * 2 * kGlowPaddingFactor;
resize(s, s);
}
sizeValue(
) | rpl::start_with_next([=](QSize size) {
_center = size.width() / 2;
{
const auto &r = _circleRadius;
const auto left = (size.width() - r * 2.) / 2.;
const auto add = st::callConnectingRadial.thickness / 2;
_circleRect = QRectF(left, left, r * 2, r * 2).marginsAdded(
style::margins(add, add, add, add));
}
}, lifetime());
paintRequest(
) | rpl::start_with_next([=] {
Painter p(this);
PainterHighQualityEnabler hq(p);
// Glow.
const auto s = kGlowMinScale
+ (1. - kGlowMinScale) * _blobs.currentLevel();
p.translate(_center, _center);
p.scale(s, s);
p.translate(-_center, -_center);
p.fillRect(rect(), _glowBrush);
p.resetTransform();
// Blobs.
p.translate(_center, _center);
const auto scale = (_switchConnectingProgress > 0.)
? anim::easeOutBack(
1.,
_blobsScaleEnter * (1. - Clamp(
_switchConnectingProgress / kBlobPartAnimation)))
: _blobsScaleEnter;
_blobs.paint(p, _blobBrush, scale);
// Main circle.
p.translate(-_center, -_center);
p.setPen(Qt::NoPen);
p.setBrush(_blobBrush);
p.drawEllipse(_circleRect);
if (_switchConnectingProgress > 0.) {
p.resetTransform();
const auto circleProgress =
Clamp(_switchConnectingProgress - kBlobPartAnimation)
/ kFillCirclePartAnimation;
const auto mF = (_circleRect.width() / 2) * (1. - circleProgress);
const auto cutOutRect = _circleRect.marginsRemoved(
QMarginsF(mF, mF, mF, mF));
p.setPen(Qt::NoPen);
p.setBrush(st::callConnectingRadial.color);
p.setOpacity(circleProgress);
p.drawEllipse(_circleRect);
p.setOpacity(1.);
p.setBrush(st::callIconBg);
p.save();
p.setCompositionMode(QPainter::CompositionMode_Source);
p.drawEllipse(cutOutRect);
p.restore();
p.drawEllipse(cutOutRect);
}
}, lifetime());
_animation.init([=](crl::time now) {
if (const auto &last = _blobsHideLastTime; (last > 0)
&& (now - last >= kBlobsScaleEnterDuration)) {
_animation.stop();
return false;
}
_blobs.updateLevel(now - _blobsLastTime);
_blobsLastTime = now;
const auto dt = Clamp(
(now - _blobsScaleLastTime) / float64(kBlobsScaleEnterDuration));
_blobsScaleEnter = _hideBlobs
? (1. - anim::easeInCirc(1., dt))
: anim::easeOutBack(1., dt);
update();
return true;
});
shownValue(
) | rpl::start_with_next([=](bool shown) {
if (shown) {
_animation.start();
} else {
_animation.stop();
}
}, lifetime());
}
QRectF BlobsWidget::innerRect() const {
return _circleRect;
}
void BlobsWidget::setBlobBrush(QBrush brush) {
if (_blobBrush == brush) {
return;
}
_blobBrush = brush;
}
void BlobsWidget::setGlowBrush(QBrush brush) {
if (_glowBrush == brush) {
return;
}
_glowBrush = brush;
}
void BlobsWidget::setLevel(float level) {
if (_blobsHideLastTime) {
return;
}
_blobs.setLevel(level);
}
float64 BlobsWidget::switchConnectingProgress() const {
return _switchConnectingProgress;
}
void BlobsWidget::setSwitchConnectingProgress(float64 progress) {
_switchConnectingProgress = progress;
}
CallMuteButton::CallMuteButton(
not_null<RpWidget*> parent,
rpl::producer<bool> &&hideBlobs,
CallMuteButtonState initial)
: _state(initial)
, _st(st::callMuteButtonActive)
, _blobs(base::make_unique_q<BlobsWidget>(
parent,
rpl::combine(
rpl::single(anim::Disabled()) | rpl::then(anim::Disables()),
std::move(hideBlobs),
_state.value(
) | rpl::map([](const CallMuteButtonState &state) {
return IsInactive(state.type);
})
) | rpl::map([](bool animDisabled, bool hide, bool isBadState) {
return isBadState || !(!animDisabled && !hide);
})))
, _content(base::make_unique_q<AbstractButton>(parent))
, _centerLabel(base::make_unique_q<AnimatedLabel>(
parent,
_state.value(
) | rpl::map([](const CallMuteButtonState &state) {
return state.subtext.isEmpty() ? state.text : QString();
}),
kSwitchLabelDuration,
st::callMuteButtonLabelAdditional,
_st.label))
, _label(base::make_unique_q<AnimatedLabel>(
parent,
_state.value(
) | rpl::map([](const CallMuteButtonState &state) {
return state.subtext.isEmpty() ? QString() : state.text;
}),
kSwitchLabelDuration,
st::callMuteButtonLabelAdditional,
_st.label))
, _sublabel(base::make_unique_q<AnimatedLabel>(
parent,
_state.value(
) | rpl::map([](const CallMuteButtonState &state) {
return state.subtext;
}),
kSwitchLabelDuration,
st::callMuteButtonLabelAdditional,
st::callMuteButtonSublabel))
, _radial(nullptr)
, _colors(Colors())
, _crossLineMuteAnimation(st::callMuteCrossLine) {
init();
}
void CallMuteButton::init() {
_content->resize(_st.button.width, _st.button.height);
style::PaletteChanged(
) | rpl::start_with_next([=] {
_crossLineMuteAnimation.invalidate();
}, lifetime());
// Label text.
_label->show();
rpl::combine(
_content->geometryValue(),
_label->sizeValue()
) | rpl::start_with_next([=](QRect my, QSize size) {
updateLabelGeometry(my, size);
}, _label->lifetime());
_label->setAttribute(Qt::WA_TransparentForMouseEvents);
_sublabel->show();
rpl::combine(
_content->geometryValue(),
_sublabel->sizeValue()
) | rpl::start_with_next([=](QRect my, QSize size) {
updateSublabelGeometry(my, size);
}, _sublabel->lifetime());
_sublabel->setAttribute(Qt::WA_TransparentForMouseEvents);
_centerLabel->show();
rpl::combine(
_content->geometryValue(),
_centerLabel->sizeValue()
) | rpl::start_with_next([=](QRect my, QSize size) {
updateCenterLabelGeometry(my, size);
}, _centerLabel->lifetime());
_centerLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
_radialInfo.rawShowProgress.value(
) | rpl::start_with_next([=](float64 value) {
auto &info = _radialInfo;
info.realShowProgress = (1. - value) / kRadialEndPartAnimation;
if (((value == 0.) || anim::Disabled()) && _radial) {
_radial->stop();
_radial = nullptr;
return;
}
if ((value > 0.) && !anim::Disabled() && !_radial) {
_radial = std::make_unique<InfiniteRadialAnimation>(
[=] { _content->update(); },
_radialInfo.st);
_radial->start();
}
if ((info.realShowProgress < 1.) && !info.isDirectionToShow) {
_radial->stop(anim::type::instant);
_radial->start();
info.state = std::nullopt;
return;
}
if (value == 1.) {
info.state = std::nullopt;
} else {
if (_radial && !info.state.has_value()) {
info.state = _radial->computeState();
}
}
}, lifetime());
// State type.
const auto previousType =
lifetime().make_state<CallMuteButtonType>(_state.current().type);
setHandleMouseState(HandleMouseState::Disabled);
const auto blobsInner = [&] {
// The point of the circle at 45 degrees.
const auto w = _blobs->innerRect().width();
const auto mF = (1 - std::cos(M_PI / 4.)) * (w / 2.);
return _blobs->innerRect().marginsRemoved(QMarginsF(mF, mF, mF, mF));
}();
auto linearGradients = anim::linear_gradients<CallMuteButtonType>(
_colors,
QPointF(blobsInner.x() + blobsInner.width(), blobsInner.y()),
QPointF(blobsInner.x(), blobsInner.y() + blobsInner.height()));
auto glowColors = [&] {
auto copy = _colors;
for (auto &[type, stops] : copy) {
auto firstColor = IsInactive(type)
? st::groupCallBg->c
: stops.stops[0].second;
firstColor.setAlpha(kGlowAlpha);
stops.stops = QGradientStops{
{ 0., std::move(firstColor) },
{ 1., QColor(Qt::transparent) }
};
}
return copy;
}();
auto glows = anim::radial_gradients<CallMuteButtonType>(
std::move(glowColors),
blobsInner.center(),
_blobs->width() / 2);
_state.value(
) | rpl::map([](const CallMuteButtonState &state) {
return state.type;
}) | rpl::start_with_next([=](CallMuteButtonType type) {
const auto previous = *previousType;
*previousType = type;
const auto mouseState = HandleMouseStateFromType(type);
setHandleMouseState(HandleMouseState::Disabled);
if (mouseState != HandleMouseState::Enabled) {
setHandleMouseState(mouseState);
}
const auto fromConnecting = IsConnecting(previous);
const auto toConnecting = IsConnecting(type);
const auto crossFrom = IsMuted(previous) ? 0. : 1.;
const auto crossTo = IsMuted(type) ? 0. : 1.;
const auto radialShowFrom = fromConnecting ? 1. : 0.;
const auto radialShowTo = toConnecting ? 1. : 0.;
const auto from = (_switchAnimation.animating() && !fromConnecting)
? (1. - _switchAnimation.value(0.))
: 0.;
const auto to = 1.;
_radialInfo.isDirectionToShow = fromConnecting;
auto callback = [=](float64 value) {
const auto brushProgress = fromConnecting ? 1. : value;
_blobs->setBlobBrush(QBrush(
linearGradients.gradient(previous, type, brushProgress)));
_blobs->setGlowBrush(QBrush(
glows.gradient(previous, type, value)));
_blobs->update();
const auto crossProgress = (crossFrom == crossTo)
? crossTo
: anim::interpolateF(crossFrom, crossTo, value);
if (crossProgress != _crossLineProgress) {
_crossLineProgress = crossProgress;
_content->update(_muteIconRect);
}
const auto radialShowProgress = (radialShowFrom == radialShowTo)
? radialShowTo
: anim::interpolateF(radialShowFrom, radialShowTo, value);
if (radialShowProgress != _radialInfo.rawShowProgress.current()) {
_radialInfo.rawShowProgress = radialShowProgress;
_blobs->setSwitchConnectingProgress(Clamp(
radialShowProgress / kBlobsWidgetPartAnimation));
}
overridesColors(previous, type, value);
if (value == to) {
setHandleMouseState(mouseState);
}
};
_switchAnimation.stop();
const auto duration = (1. - from) * ((fromConnecting || toConnecting)
? kSwitchStateFromConnectingDuration
: kSwitchStateDuration);
_switchAnimation.start(std::move(callback), from, to, duration);
}, lifetime());
// Icon rect.
_content->sizeValue(
) | rpl::start_with_next([=](QSize size) {
const auto &icon = _st.button.icon;
const auto &pos = _st.button.iconPosition;
_muteIconRect = QRect(
(pos.x() < 0) ? ((size.width() - icon.width()) / 2) : pos.x(),
(pos.y() < 0) ? ((size.height() - icon.height()) / 2) : pos.y(),
icon.width(),
icon.height());
}, lifetime());
// Paint.
_content->paintRequest(
) | rpl::start_with_next([=](QRect clip) {
Painter p(_content);
_crossLineMuteAnimation.paint(
p,
_muteIconRect.topLeft(),
1. - _crossLineProgress);
if (_radialInfo.state.has_value() && _switchAnimation.animating()) {
const auto radialProgress = _radialInfo.realShowProgress;
auto r = *_radialInfo.state;
r.shown = 1.;
if (_radialInfo.isDirectionToShow) {
const auto to = r.arcFrom - kRadialFinishArcShift;
ComputeRadialFinish(r.arcFrom, radialProgress, to);
ComputeRadialFinish(r.arcLength, radialProgress);
}
const auto opacity = (radialProgress > kOverlapProgressRadialHide)
? 0.
: _blobs->switchConnectingProgress();
p.setOpacity(opacity);
InfiniteRadialAnimation::Draw(
p,
r,
_st.bgPosition,
_radialInfo.st.size,
_content->width(),
QPen(_radialInfo.st.color),
_radialInfo.st.thickness);
} else if (_radial) {
auto state = _radial->computeState();
state.shown = 1.;
InfiniteRadialAnimation::Draw(
p,
std::move(state),
_st.bgPosition,
_radialInfo.st.size,
_content->width(),
QPen(_radialInfo.st.color),
_radialInfo.st.thickness);
}
}, _content->lifetime());
}
void CallMuteButton::updateLabelsGeometry() {
updateLabelGeometry(_content->geometry(), _label->size());
updateCenterLabelGeometry(_content->geometry(), _centerLabel->size());
updateSublabelGeometry(_content->geometry(), _sublabel->size());
}
void CallMuteButton::updateLabelGeometry(QRect my, QSize size) {
const auto skip = st::callMuteButtonSublabelSkip
+ st::callMuteButtonLabelsSkip;
_label->moveToLeft(
my.x() + (my.width() - size.width()) / 2 + _labelShakeShift,
my.y() + my.height() - _label->height() - skip,
my.width());
}
void CallMuteButton::updateCenterLabelGeometry(QRect my, QSize size) {
const auto skip = (st::callMuteButtonSublabelSkip / 2)
+ st::callMuteButtonLabelsSkip;
_centerLabel->moveToLeft(
my.x() + (my.width() - size.width()) / 2 + _labelShakeShift,
my.y() + my.height() - _centerLabel->height() - skip,
my.width());
}
void CallMuteButton::updateSublabelGeometry(QRect my, QSize size) {
const auto skip = st::callMuteButtonLabelsSkip;
_sublabel->moveToLeft(
my.x() + (my.width() - size.width()) / 2 + _labelShakeShift,
my.y() + my.height() - _sublabel->height() - skip,
my.width());
}
void CallMuteButton::shake() {
if (_shakeAnimation.animating()) {
return;
}
const auto update = [=] {
const auto fullProgress = _shakeAnimation.value(1.) * 6;
const auto segment = std::clamp(int(std::floor(fullProgress)), 0, 5);
const auto part = fullProgress - segment;
const auto from = (segment == 0)
? 0.
: (segment == 1 || segment == 3 || segment == 5)
? 1.
: -1.;
const auto to = (segment == 0 || segment == 2 || segment == 4)
? 1.
: (segment == 1 || segment == 3)
? -1.
: 0.;
const auto shift = from * (1. - part) + to * part;
_labelShakeShift = int(std::round(shift * st::shakeShift));
updateLabelsGeometry();
};
_shakeAnimation.start(
update,
0.,
1.,
kShiftDuration);
}
CallMuteButton::HandleMouseState CallMuteButton::HandleMouseStateFromType(
CallMuteButtonType type) {
switch (type) {
case CallMuteButtonType::Active:
case CallMuteButtonType::Muted:
return HandleMouseState::Enabled;
case CallMuteButtonType::Connecting:
return HandleMouseState::Disabled;
case CallMuteButtonType::ForceMuted:
return HandleMouseState::Blocked;
}
Unexpected("Type in HandleMouseStateFromType.");
}
void CallMuteButton::setState(const CallMuteButtonState &state) {
_state = state;
}
void CallMuteButton::setLevel(float level) {
_level = level;
_blobs->setLevel(level);
}
rpl::producer<Qt::MouseButton> CallMuteButton::clicks() const {
return _content->clicks();
}
QSize CallMuteButton::innerSize() const {
return innerGeometry().size();
}
QRect CallMuteButton::innerGeometry() const {
const auto &skip = _st.outerRadius;
return QRect(
_content->x(),
_content->y(),
_content->width() - 2 * skip,
_content->width() - 2 * skip);
}
void CallMuteButton::moveInner(QPoint position) {
const auto &skip = _st.outerRadius;
_content->move(position - QPoint(skip, skip));
{
const auto offset = QPoint(
(_blobs->width() - _content->width()) / 2,
(_blobs->height() - _content->width()) / 2);
_blobs->move(_content->pos() - offset);
}
}
void CallMuteButton::setVisible(bool visible) {
_content->setVisible(visible);
_blobs->setVisible(visible);
}
void CallMuteButton::raise() {
_blobs->raise();
_content->raise();
}
void CallMuteButton::lower() {
_content->lower();
_blobs->lower();
}
void CallMuteButton::setHandleMouseState(HandleMouseState state) {
if (_handleMouseState == state) {
return;
}
_handleMouseState = state;
const auto handle = (_handleMouseState != HandleMouseState::Disabled);
const auto pointer = (_handleMouseState == HandleMouseState::Enabled);
_content->setAttribute(Qt::WA_TransparentForMouseEvents, !handle);
_content->setPointerCursor(pointer);
}
void CallMuteButton::overridesColors(
CallMuteButtonType fromType,
CallMuteButtonType toType,
float64 progress) {
const auto forceMutedToConnecting = [](CallMuteButtonType &type) {
if (type == CallMuteButtonType::ForceMuted) {
type = CallMuteButtonType::Connecting;
}
};
forceMutedToConnecting(toType);
forceMutedToConnecting(fromType);
const auto toInactive = IsInactive(toType);
const auto fromInactive = IsInactive(fromType);
if (toInactive && (progress == 1)) {
_colorOverrides.fire({ std::nullopt, std::nullopt });
return;
}
auto from = _colors.find(fromType)->second.stops[0].second;
auto to = _colors.find(toType)->second.stops[0].second;
auto fromRipple = from;
auto toRipple = to;
if (!toInactive) {
toRipple.setAlpha(kOverrideColorRippleAlpha);
to.setAlpha(kOverrideColorBgAlpha);
}
if (!fromInactive) {
fromRipple.setAlpha(kOverrideColorRippleAlpha);
from.setAlpha(kOverrideColorBgAlpha);
}
const auto resultBg = anim::color(from, to, progress);
const auto resultRipple = anim::color(fromRipple, toRipple, progress);
_colorOverrides.fire({ resultBg, resultRipple });
}
rpl::producer<CallButtonColors> CallMuteButton::colorOverrides() const {
return _colorOverrides.events();
}
rpl::lifetime &CallMuteButton::lifetime() {
return _blobs->lifetime();
}
CallMuteButton::~CallMuteButton() = default;
} // namespace Ui

View file

@ -0,0 +1,126 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "base/unique_qptr.h"
#include "ui/effects/animations.h"
#include "ui/effects/cross_line.h"
#include "ui/effects/gradient.h"
#include "ui/effects/radial_animation.h"
namespace Ui {
class BlobsWidget;
class AbstractButton;
class FlatLabel;
class RpWidget;
class AnimatedLabel;
struct CallButtonColors;
enum class CallMuteButtonType {
Connecting,
Active,
Muted,
ForceMuted,
};
struct CallMuteButtonState {
QString text;
QString subtext;
CallMuteButtonType type = CallMuteButtonType::Connecting;
};
class CallMuteButton final {
public:
explicit CallMuteButton(
not_null<RpWidget*> parent,
rpl::producer<bool> &&hideBlobs,
CallMuteButtonState initial = CallMuteButtonState());
~CallMuteButton();
void setState(const CallMuteButtonState &state);
void setLevel(float level);
[[nodiscard]] rpl::producer<Qt::MouseButton> clicks() const;
[[nodiscard]] QSize innerSize() const;
[[nodiscard]] QRect innerGeometry() const;
void moveInner(QPoint position);
void shake();
void setVisible(bool visible);
void show() {
setVisible(true);
}
void hide() {
setVisible(false);
}
void raise();
void lower();
[[nodiscard]] rpl::producer<CallButtonColors> colorOverrides() const;
[[nodiscard]] rpl::lifetime &lifetime();
private:
enum class HandleMouseState {
Enabled,
Blocked,
Disabled,
};
struct RadialInfo {
std::optional<RadialState> state = std::nullopt;
bool isDirectionToShow = false;
rpl::variable<float64> rawShowProgress = 0.;
float64 realShowProgress = 0.;
const style::InfiniteRadialAnimation &st = st::callConnectingRadial;
};
void init();
void overridesColors(
CallMuteButtonType fromType,
CallMuteButtonType toType,
float64 progress);
void setHandleMouseState(HandleMouseState state);
void updateCenterLabelGeometry(QRect my, QSize size);
void updateLabelGeometry(QRect my, QSize size);
void updateSublabelGeometry(QRect my, QSize size);
void updateLabelsGeometry();
[[nodiscard]] static HandleMouseState HandleMouseStateFromType(
CallMuteButtonType type);
rpl::variable<CallMuteButtonState> _state;
float _level = 0.;
float64 _crossLineProgress = 0.;
QRect _muteIconRect;
HandleMouseState _handleMouseState = HandleMouseState::Enabled;
const style::CallButton &_st;
const base::unique_qptr<BlobsWidget> _blobs;
const base::unique_qptr<AbstractButton> _content;
const base::unique_qptr<AnimatedLabel> _centerLabel;
const base::unique_qptr<AnimatedLabel> _label;
const base::unique_qptr<AnimatedLabel> _sublabel;
int _labelShakeShift = 0;
RadialInfo _radialInfo;
std::unique_ptr<InfiniteRadialAnimation> _radial;
const base::flat_map<CallMuteButtonType, anim::gradient_colors> _colors;
CrossLineAnimation _crossLineMuteAnimation;
Animations::Simple _switchAnimation;
Animations::Simple _shakeAnimation;
rpl::event_stream<CallButtonColors> _colorOverrides;
};
} // namespace Ui

View file

@ -262,7 +262,7 @@ void CheckView::paint(Painter &p, int left, int top, int outerWidth) {
{
PainterHighQualityEnabler hq(p);
p.drawRoundedRect(style::rtlrect(QRectF(left, top, _st->diameter, _st->diameter).marginsRemoved(QMarginsF(_st->thickness / 2., _st->thickness / 2., _st->thickness / 2., _st->thickness / 2.)), outerWidth), st::buttonRadius - (_st->thickness / 2.), st::buttonRadius - (_st->thickness / 2.));
p.drawRoundedRect(style::rtlrect(QRectF(left, top, _st->diameter, _st->diameter).marginsRemoved(QMarginsF(_st->thickness / 2., _st->thickness / 2., _st->thickness / 2., _st->thickness / 2.)), outerWidth), st::roundRadiusSmall - (_st->thickness / 2.), st::roundRadiusSmall - (_st->thickness / 2.));
}
if (toggled > 0) {

View file

@ -1058,7 +1058,7 @@ void FlatInput::paintEvent(QPaintEvent *e) {
p.setBrush(anim::brush(_st.bgColor, _st.bgActive, placeholderFocused));
{
PainterHighQualityEnabler hq(p);
p.drawRoundedRect(QRectF(0, 0, width(), height()).marginsRemoved(QMarginsF(_st.borderWidth / 2., _st.borderWidth / 2., _st.borderWidth / 2., _st.borderWidth / 2.)), st::buttonRadius - (_st.borderWidth / 2.), st::buttonRadius - (_st.borderWidth / 2.));
p.drawRoundedRect(QRectF(0, 0, width(), height()).marginsRemoved(QMarginsF(_st.borderWidth / 2., _st.borderWidth / 2., _st.borderWidth / 2., _st.borderWidth / 2.)), st::roundRadiusSmall - (_st.borderWidth / 2.), st::roundRadiusSmall - (_st.borderWidth / 2.));
}
if (!_st.icon.empty()) {
@ -3370,7 +3370,7 @@ bool InputField::revertFormatReplace() {
void InputField::contextMenuEventInner(QContextMenuEvent *e, QMenu *m) {
if (const auto menu = m ? m : _inner->createStandardContextMenu()) {
addMarkdownActions(menu, e);
_contextMenu = base::make_unique_q<PopupMenu>(this, menu);
_contextMenu = base::make_unique_q<PopupMenu>(this, menu, _st.menu);
_contextMenu->popup(e->globalPos());
}
}

View file

@ -454,13 +454,12 @@ Text::StateResult FlatLabel::dragActionFinish(const QPoint &p, Qt::MouseButton b
}
}
#if defined Q_OS_UNIX && !defined Q_OS_MAC
if (!_selection.empty()) {
if (QGuiApplication::clipboard()->supportsSelection()
&& !_selection.empty()) {
TextUtilities::SetClipboardText(
_text.toTextForMimeData(_selection),
QClipboard::Selection);
}
#endif // Q_OS_UNIX && !Q_OS_MAC
return state;
}

View file

@ -12,11 +12,12 @@
#include "ui/ui_utility.h"
#include "ui/delayed_activation.h"
#include "base/platform/base_platform_info.h"
#include "base/qt_adapters.h"
#include <QtGui/QtEvents>
#include <QtGui/QPainter>
#include <QtGui/QScreen>
#include <QtWidgets/QApplication>
#include <QtWidgets/QDesktopWidget>
namespace Ui {
@ -448,7 +449,9 @@ void PopupMenu::popup(const QPoint &p) {
}
void PopupMenu::showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource source) {
if (!parent && ::Platform::IsMac() && !Platform::IsApplicationActive()) {
const auto screen = base::QScreenNearestTo(p);
if (!screen
|| (!parent && ::Platform::IsMac() && !Platform::IsApplicationActive())) {
_hiding = false;
_a_opacity.stop();
_a_show.stop();
@ -476,7 +479,7 @@ void PopupMenu::showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource sou
&& (*_forcedOrigin == Origin::BottomLeft
|| *_forcedOrigin == Origin::BottomRight);
auto w = p - QPoint(0, _padding.top());
auto r = QApplication::desktop()->screenGeometry(p);
auto r = screen->availableGeometry();
_useTransparency = Platform::TranslucentWindowsSupported(p);
setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency);
handleCompositingUpdate();
@ -535,7 +538,9 @@ PopupMenu::~PopupMenu() {
delete submenu;
}
if (const auto parent = parentWidget()) {
if (QApplication::focusWidget() != nullptr
const auto focused = QApplication::focusWidget();
if (_reactivateParent
&& focused != nullptr
&& Ui::InFocusChain(parent->window())) {
ActivateWindowDelayed(parent);
}

View file

@ -37,6 +37,9 @@ public:
void setDestroyedCallback(Fn<void()> callback) {
_destroyedCallback = std::move(callback);
}
void discardParentReActivate() {
_reactivateParent = false;
}
~PopupMenu();
@ -122,6 +125,7 @@ private:
bool _deleteOnHide = true;
bool _triggering = false;
bool _deleteLater = false;
bool _reactivateParent = true;
Fn<void()> _destroyedCallback;

View file

@ -9,10 +9,11 @@
#include "ui/ui_utility.h"
#include "ui/platform/ui_platform_utility.h"
#include "base/invoke_queued.h"
#include "base/qt_adapters.h"
#include "styles/style_widgets.h"
#include <QtGui/QScreen>
#include <QtWidgets/QApplication>
#include <QtWidgets/QDesktopWidget>
namespace Ui {
@ -72,6 +73,12 @@ Tooltip::~Tooltip() {
}
void Tooltip::popup(const QPoint &m, const QString &text, const style::Tooltip *st) {
const auto screen = base::QScreenNearestTo(m);
if (!screen) {
Hide();
return;
}
if (!_isEventFilter) {
_isEventFilter = true;
QCoreApplication::instance()->installEventFilter(this);
@ -108,7 +115,7 @@ void Tooltip::popup(const QPoint &m, const QString &text, const style::Tooltip *
}
// adjust tooltip position
QRect r(QApplication::desktop()->screenGeometry(m));
const auto r = screen->availableGeometry();
if (r.x() + r.width() - _st->skip < p.x() + s.width() && p.x() + s.width() > m.x()) {
p.setX(qMax(r.x() + r.width() - int32(_st->skip) - s.width(), m.x() - s.width()));
}
@ -135,7 +142,7 @@ void Tooltip::paintEvent(QPaintEvent *e) {
p.setPen(_st->textBorder);
p.setBrush(_st->textBg);
PainterHighQualityEnabler hq(p);
p.drawRoundedRect(QRectF(0.5, 0.5, width() - 1., height() - 1.), st::buttonRadius, st::buttonRadius);
p.drawRoundedRect(QRectF(0.5, 0.5, width() - 1., height() - 1.), st::roundRadiusSmall, st::roundRadiusSmall);
} else {
p.fillRect(rect(), _st->textBg);

View file

@ -182,6 +182,74 @@ ScrollArea {
hiding: int;
}
Shadow {
left: icon;
topLeft: icon;
top: icon;
topRight: icon;
right: icon;
bottomRight: icon;
bottom: icon;
bottomLeft: icon;
extend: margins;
fallback: color;
}
PanelAnimation {
startWidth: double;
widthDuration: double;
startHeight: double;
heightDuration: double;
startOpacity: double;
opacityDuration: double;
startFadeTop: double;
fadeHeight: double;
fadeOpacity: double;
fadeBg: color;
shadow: Shadow;
}
Menu {
skip: pixels;
itemBg: color;
itemBgOver: color;
itemFg: color;
itemFgOver: color;
itemFgDisabled: color;
itemFgShortcut: color;
itemFgShortcutOver: color;
itemFgShortcutDisabled: color;
itemPadding: margins;
itemIconPosition: point;
itemStyle: TextStyle;
itemToggle: Toggle;
itemToggleOver: Toggle;
itemToggleShift: pixels;
separatorPadding: margins;
separatorWidth: pixels;
separatorFg: color;
arrow: icon;
widthMin: pixels;
widthMax: pixels;
ripple: RippleAnimation;
}
PopupMenu {
shadow: Shadow;
scrollPadding: margins;
animation: PanelAnimation;
menu: Menu;
duration: int;
showDuration: int;
}
FlatInput {
textColor: color;
bgColor: color;
@ -231,6 +299,7 @@ InputField {
borderActive: pixels;
font: font;
menu: PopupMenu;
width: pixels;
heightMin: pixels;
@ -265,19 +334,6 @@ IconButton {
ripple: RippleAnimation;
}
Shadow {
left: icon;
topLeft: icon;
top: icon;
topRight: icon;
right: icon;
bottomRight: icon;
bottom: icon;
bottomLeft: icon;
extend: margins;
fallback: color;
}
MediaSlider {
width: pixels;
activeFg: color;
@ -344,6 +400,14 @@ CrossButton {
ripple: RippleAnimation;
}
CrossLineAnimation {
fg: color;
icon: icon;
startPosition: point;
endPosition: point;
stroke: pixels;
}
MultiSelectItem {
padding: margins;
maxWidth: pixels;
@ -379,67 +443,14 @@ MultiSelect {
CallButton {
button: IconButton;
bg: color;
bgSize: pixels;
bgPosition: point;
angle: double;
outerRadius: pixels;
outerBg: color;
label: FlatLabel;
}
Menu {
skip: pixels;
itemBg: color;
itemBgOver: color;
itemFg: color;
itemFgOver: color;
itemFgDisabled: color;
itemFgShortcut: color;
itemFgShortcutOver: color;
itemFgShortcutDisabled: color;
itemPadding: margins;
itemIconPosition: point;
itemStyle: TextStyle;
itemToggle: Toggle;
itemToggleOver: Toggle;
itemToggleShift: pixels;
separatorPadding: margins;
separatorWidth: pixels;
separatorFg: color;
arrow: icon;
widthMin: pixels;
widthMax: pixels;
ripple: RippleAnimation;
}
PanelAnimation {
startWidth: double;
widthDuration: double;
startHeight: double;
heightDuration: double;
startOpacity: double;
opacityDuration: double;
startFadeTop: double;
fadeHeight: double;
fadeOpacity: double;
fadeBg: color;
shadow: Shadow;
}
PopupMenu {
shadow: Shadow;
scrollPadding: margins;
animation: PanelAnimation;
menu: Menu;
duration: int;
showDuration: int;
}
InnerDropdown {
padding: margins;
shadow: Shadow;
@ -493,6 +504,7 @@ SettingsButton {
textBgOver: color;
font: font;
rightLabel: FlatLabel;
height: pixels;
padding: margins;
@ -614,6 +626,8 @@ defaultRippleAnimation: RippleAnimation {
emptyRippleAnimation: RippleAnimation {
}
buttonRadius: 4px;
defaultActiveButton: RoundButton {
textFg: activeButtonFg;
textFgOver: activeButtonFgOver;
@ -709,35 +723,6 @@ defaultFlatInput: FlatInput {
phDuration: 100;
}
defaultInputField: InputField {
textBg: windowBg;
textFg: windowFg;
textMargins: margins(0px, 26px, 0px, 4px);
textAlign: align(topleft);
placeholderFg: windowSubTextFg;
placeholderFgActive: windowActiveTextFg;
placeholderFgError: attentionButtonFg;
placeholderMargins: margins(0px, 0px, 0px, 0px);
placeholderAlign: align(topleft);
placeholderScale: 0.9;
placeholderShift: -20px;
placeholderFont: font(semibold 14px);
duration: 150;
borderFg: inputBorderFg;
borderFgActive: activeLineFg;
borderFgError: activeLineFgError;
border: 1px;
borderActive: 2px;
font: boxTextFont;
heightMin: 52px;
heightMax: 148px;
}
defaultCheckboxIcon: icon {{ "default_checkbox_check", overviewCheckFgActive, point(4px, 7px) }};
defaultCheck: Check {
@ -778,6 +763,7 @@ defaultToggle: Toggle {
}
defaultCheckbox: Checkbox {
textFg: windowFg;
textFgActive: windowFg;
width: -44px;
margin: margins(8px, 8px, 8px, 8px);
@ -795,83 +781,6 @@ defaultCheckbox: Checkbox {
disabledOpacity: 0.5;
}
defaultIconButton: IconButton {
iconPosition: point(-1px, -1px);
}
defaultMultiSelectItem: MultiSelectItem {
padding: margins(6px, 7px, 12px, 0px);
maxWidth: 128px;
height: 32px;
style: defaultTextStyle;
textBg: contactsBgOver;
textFg: windowFg;
textActiveBg: activeButtonBg;
textActiveFg: activeButtonFg;
deleteFg: activeButtonFg;
deleteCross: CrossAnimation {
size: 32px;
skip: 10px;
stroke: 2px;
minScale: 0.3;
}
duration: 150;
minScale: 0.3;
}
widgetSlideDuration: 200;
widgetFadeDuration: 200;
fieldSearchIcon: icon {{ "box_search", menuIconFg, point(9px, 8px) }};
boxFieldSearchIcon: icon {{ "box_search", menuIconFg, point(10px, 9px) }};
SettingsSlider {
height: pixels;
barTop: pixels;
barSkip: pixels;
barStroke: pixels;
barFg: color;
barFgActive: color;
labelTop: pixels;
labelStyle: TextStyle;
labelFg: color;
labelFgActive: color;
duration: int;
rippleBottomSkip: pixels;
rippleBg: color;
rippleBgActive: color;
ripple: RippleAnimation;
}
defaultSettingsSlider: SettingsSlider {
height: 39px;
barTop: 5px;
barSkip: 3px;
barStroke: 3px;
barFg: sliderBgInactive;
barFgActive: sliderBgActive;
labelTop: 17px;
labelStyle: defaultTextStyle;
labelFg: windowActiveTextFg;
labelFgActive: windowActiveTextFg;
duration: 150;
}
defaultTabsSlider: SettingsSlider(defaultSettingsSlider) {
height: 53px;
barTop: 50px;
barSkip: 0px;
barFg: transparent;
labelTop: 19px;
labelStyle: semiboldTextStyle;
labelFg: windowSubTextFg;
labelFgActive: lightButtonFg;
rippleBottomSkip: 1px;
rippleBg: windowBgOver;
rippleBgActive: lightButtonBgOver;
ripple: defaultRippleAnimation;
}
defaultRoundShadow: Shadow {
left: icon {{ "round_shadow_left", windowShadowFg }};
topLeft: icon {{ "round_shadow_top_left", windowShadowFg }};
@ -902,48 +811,6 @@ defaultPanelAnimation: PanelAnimation {
shadow: defaultRoundShadow;
}
defaultContinuousSlider: MediaSlider {
width: 3px;
activeFg: mediaPlayerActiveFg;
inactiveFg: mediaPlayerInactiveFg;
activeFgOver: mediaPlayerActiveFg;
inactiveFgOver: mediaPlayerInactiveFg;
activeFgDisabled: mediaPlayerInactiveFg;
inactiveFgDisabled: windowBg;
receivedTillFg: mediaPlayerInactiveFg;
seekSize: size(9px, 9px);
duration: 150;
}
defaultRoundCheckbox: RoundCheckbox {
border: windowBg;
bgActive: windowBgActive;
width: 2px;
duration: 160;
bgDuration: 0.75;
fgDuration: 1.;
}
defaultPeerListCheckIcon: icon {{
"default_checkbox_check",
overviewCheckFgActive,
point(3px, 6px)
}};
defaultPeerListCheck: RoundCheckbox(defaultRoundCheckbox) {
size: 20px;
sizeSmall: 0.3;
bgInactive: overviewCheckBg;
bgActive: overviewCheckBgActive;
check: defaultPeerListCheckIcon;
}
defaultPeerListCheckbox: RoundImageCheckbox {
imageRadius: 21px;
imageSmallRadius: 18px;
selectWidth: 2px;
selectFg: windowBgActive;
selectDuration: 150;
check: defaultPeerListCheck;
}
defaultMenuArrow: icon {{ "dropdown_submenu_arrow", menuSubmenuArrowFg }};
defaultMenuToggle: Toggle(defaultToggle) {
untoggledFg: menuIconFg;
@ -991,6 +858,220 @@ defaultPopupMenu: PopupMenu {
duration: 150;
showDuration: 200;
}
defaultInputField: InputField {
textBg: windowBg;
textFg: windowFg;
textMargins: margins(0px, 26px, 0px, 4px);
textAlign: align(topleft);
placeholderFg: windowSubTextFg;
placeholderFgActive: windowActiveTextFg;
placeholderFgError: attentionButtonFg;
placeholderMargins: margins(0px, 0px, 0px, 0px);
placeholderAlign: align(topleft);
placeholderScale: 0.9;
placeholderShift: -20px;
placeholderFont: font(semibold 14px);
duration: 150;
borderFg: inputBorderFg;
borderFgActive: activeLineFg;
borderFgError: activeLineFgError;
border: 1px;
borderActive: 2px;
font: boxTextFont;
menu: defaultPopupMenu;
heightMin: 52px;
heightMax: 148px;
}
defaultIconButton: IconButton {
iconPosition: point(-1px, -1px);
}
defaultMultiSelectItem: MultiSelectItem {
padding: margins(6px, 7px, 12px, 0px);
maxWidth: 128px;
height: 32px;
style: defaultTextStyle;
textBg: contactsBgOver;
textFg: windowFg;
textActiveBg: activeButtonBg;
textActiveFg: activeButtonFg;
deleteFg: activeButtonFg;
deleteCross: CrossAnimation {
size: 32px;
skip: 10px;
stroke: 2px;
minScale: 0.3;
}
duration: 150;
minScale: 0.3;
}
defaultMultiSelectSearchField: InputField(defaultInputField) {
textBg: transparent;
textMargins: margins(2px, 7px, 2px, 0px);
placeholderFg: placeholderFg;
placeholderFgActive: placeholderFgActive;
placeholderFgError: placeholderFgActive;
placeholderMargins: margins(2px, 0px, 2px, 0px);
placeholderScale: 0.;
placeholderFont: normalFont;
border: 0px;
borderActive: 0px;
heightMin: 32px;
font: normalFont;
}
fieldSearchIcon: icon {{ "box_search", menuIconFg, point(9px, 8px) }};
boxFieldSearchIcon: icon {{ "box_search", menuIconFg, point(10px, 9px) }};
defaultMultiSelectSearchCancel: CrossButton {
width: 44px;
height: 44px;
cross: CrossAnimation {
size: 36px;
skip: 12px;
stroke: 2px;
minScale: 0.3;
}
crossFg: boxTitleCloseFg;
crossFgOver: boxTitleCloseFgOver;
crossPosition: point(4px, 4px);
duration: 150;
loadingPeriod: 1000;
ripple: RippleAnimation(defaultRippleAnimation) {
color: windowBgOver;
}
}
defaultMultiSelect: MultiSelect {
bg: boxSearchBg;
padding: margins(8px, 6px, 8px, 6px);
maxHeight: 104px;
scroll: ScrollArea(defaultSolidScroll) {
deltat: 3px;
deltab: 3px;
round: 1px;
width: 8px;
deltax: 3px;
hiding: 1000;
}
item: defaultMultiSelectItem;
itemSkip: 8px;
field: defaultMultiSelectSearchField;
fieldMinWidth: 42px;
fieldIcon: boxFieldSearchIcon;
fieldIconSkip: 36px;
fieldCancel: defaultMultiSelectSearchCancel;
fieldCancelSkip: 40px;
}
widgetSlideDuration: 200;
widgetFadeDuration: 200;
SettingsSlider {
height: pixels;
barTop: pixels;
barSkip: pixels;
barStroke: pixels;
barFg: color;
barFgActive: color;
labelTop: pixels;
labelStyle: TextStyle;
labelFg: color;
labelFgActive: color;
duration: int;
rippleBottomSkip: pixels;
rippleBg: color;
rippleBgActive: color;
ripple: RippleAnimation;
}
defaultSettingsSlider: SettingsSlider {
height: 39px;
barTop: 5px;
barSkip: 3px;
barStroke: 3px;
barFg: sliderBgInactive;
barFgActive: sliderBgActive;
labelTop: 17px;
labelStyle: defaultTextStyle;
labelFg: windowActiveTextFg;
labelFgActive: windowActiveTextFg;
duration: 150;
}
defaultTabsSlider: SettingsSlider(defaultSettingsSlider) {
height: 53px;
barTop: 50px;
barSkip: 0px;
barFg: transparent;
labelTop: 19px;
labelStyle: semiboldTextStyle;
labelFg: windowSubTextFg;
labelFgActive: lightButtonFg;
rippleBottomSkip: 1px;
rippleBg: windowBgOver;
rippleBgActive: lightButtonBgOver;
ripple: defaultRippleAnimation;
}
defaultContinuousSlider: MediaSlider {
width: 3px;
activeFg: mediaPlayerActiveFg;
inactiveFg: mediaPlayerInactiveFg;
activeFgOver: mediaPlayerActiveFg;
inactiveFgOver: mediaPlayerInactiveFg;
activeFgDisabled: mediaPlayerInactiveFg;
inactiveFgDisabled: windowBg;
receivedTillFg: mediaPlayerInactiveFg;
seekSize: size(9px, 9px);
duration: 150;
}
defaultRoundCheckbox: RoundCheckbox {
border: windowBg;
bgActive: windowBgActive;
width: 2px;
duration: 160;
bgDuration: 0.75;
fgDuration: 1.;
}
defaultPeerListCheckIcon: icon {{
"default_checkbox_check",
overviewCheckFgActive,
point(3px, 6px)
}};
defaultPeerListCheck: RoundCheckbox(defaultRoundCheckbox) {
size: 20px;
sizeSmall: 0.3;
bgInactive: overviewCheckBg;
bgActive: overviewCheckBgActive;
check: defaultPeerListCheckIcon;
}
defaultPeerListCheckbox: RoundImageCheckbox {
imageRadius: 21px;
imageSmallRadius: 18px;
selectWidth: 2px;
selectFg: windowBgActive;
selectDuration: 150;
check: defaultPeerListCheck;
}
defaultInnerDropdown: InnerDropdown {
padding: margins(10px, 10px, 10px, 10px);
shadow: defaultRoundShadow;
@ -1117,12 +1198,15 @@ PeerListItem {
chatNamePosition: point;
chatDescPosition: point;
nameStyle: TextStyle;
nameFg: color;
nameFgChecked: color;
statusPosition: point;
photoSize: pixels;
maximalWidth: pixels;
button: OutlineButton;
checkbox: RoundImageCheckbox;
disabledCheckFg: color;
statusFg: color;
statusFgOver: color;
statusFgActive: color;
@ -1130,6 +1214,8 @@ PeerListItem {
PeerList {
padding: margins;
bg: color;
about: FlatLabel;
item: PeerListItem;
}
@ -1155,17 +1241,37 @@ defaultPeerListItem: PeerListItem {
linkFont: semiboldFont;
linkFontOver: semiboldFont;
}
nameFg: contactsNameFg;
nameFgChecked: windowActiveTextFg;
statusPosition: point(68px, 31px);
photoSize: 46px;
button: defaultPeerListButton;
checkbox: defaultPeerListCheckbox;
disabledCheckFg: menuIconFg;
statusFg: windowSubTextFg;
statusFgOver: windowSubTextFgOver;
statusFgActive: windowActiveTextFg;
}
boxTextStyle: TextStyle(defaultTextStyle) {
font: font(boxFontSize);
linkFont: font(boxFontSize);
linkFontOver: font(boxFontSize underline);
}
defaultPeerListAbout: FlatLabel(defaultFlatLabel) {
minWidth: 240px;
textFg: membersAboutLimitFg;
align: align(top);
style: TextStyle(boxTextStyle) {
lineHeight: 22px;
}
}
defaultPeerList: PeerList {
padding: margins(0px, 0px, 0px, 0px);
bg: contactsBg;
about: defaultPeerListAbout;
item: defaultPeerListItem;
}
@ -1252,6 +1358,11 @@ defaultSettingsToggle: Toggle(defaultToggle) {
defaultSettingsToggleOver: Toggle(defaultSettingsToggle) {
untoggledFg: menuIconFgOver;
}
defaultSettingsRightLabel: FlatLabel(defaultFlatLabel) {
textFg: windowActiveTextFg;
style: boxTextStyle;
maxHeight: 20px;
}
defaultSettingsButton: SettingsButton {
textFg: windowBoldFg;
textFgOver: windowBoldFgOver;
@ -1259,6 +1370,7 @@ defaultSettingsButton: SettingsButton {
textBgOver: windowBgOver;
font: boxTextFont;
rightLabel: defaultSettingsRightLabel;
height: 20px;
padding: margins(22px, 10px, 22px, 8px);
@ -1297,6 +1409,75 @@ defaultToast: Toast {
durationSlide: 160;
}
callMuteMainBlobMinRadius: 57px;
callMuteMainBlobMaxRadius: 63px;
callMuteMinorBlobMinRadius: 64px;
callMuteMinorBlobMaxRadius: 74px;
callMuteMajorBlobMinRadius: 67px;
callMuteMajorBlobMaxRadius: 77px;
callMuteButtonActiveIcon: icon {{ "calls/voice_unmuted_large", groupCallIconFg }};
callMuteButtonActiveInner: IconButton {
width: 136px;
height: 165px;
iconPosition: point(-1px, 50px);
icon: callMuteButtonActiveIcon;
}
callMuteButtonLabel: FlatLabel(defaultFlatLabel) {
textFg: groupCallMembersFg;
style: TextStyle(defaultTextStyle) {
font: font(14px);
linkFont: font(14px);
linkFontOver: font(14px underline);
}
}
callMuteButtonSublabel: FlatLabel(defaultFlatLabel) {
textFg: groupCallMemberNotJoinedStatus;
}
callMuteButtonLabelsSkip: 5px;
callMuteButtonSublabelSkip: 19px;
callMuteButtonActive: CallButton {
button: callMuteButtonActiveInner;
bg: groupCallLive1;
bgSize: 100px;
bgPosition: point(18px, 18px);
outerRadius: 18px;
outerBg: callAnswerBgOuter;
label: callMuteButtonLabel;
}
callMuteButtonMuted: CallButton(callMuteButtonActive) {
button: IconButton(callMuteButtonActiveInner) {
icon: icon {{ "calls/voice_muted_large", groupCallIconFg }};
}
bg: groupCallMuted1;
label: callMuteButtonLabel;
}
callMuteButtonConnecting: CallButton(callMuteButtonMuted) {
button: IconButton(callMuteButtonActiveInner) {
icon: icon {{ "calls/voice_muted_large", groupCallIconFg }};
}
bg: callIconBg;
label: callMuteButtonLabel;
}
callMuteButtonLabelAdditional: 5px;
callMuteCrossLine: CrossLineAnimation {
fg: groupCallIconFg;
icon: callMuteButtonActiveIcon;
startPosition: point(7px, 2px);
endPosition: point(34px, 30px);
stroke: 4px;
}
callConnectingRadial: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) {
color: lightButtonFg;
thickness: 4px;
size: size(100px, 100px);
}
shakeShift: 4px;
// Windows specific title
windowTitleButton: IconButton {