Updated lib_ui sources to TDesktop version 2.5.1
|
|
@ -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
|
||||
|
|
|
|||
BIN
icons/calls/voice_muted_large.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
icons/calls/voice_muted_large@2x.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
icons/calls/voice_muted_large@3x.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
icons/calls/voice_unmuted_large.png
Normal file
|
After Width: | Height: | Size: 963 B |
BIN
icons/calls/voice_unmuted_large@2x.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
icons/calls/voice_unmuted_large@3x.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 224 B After Width: | Height: | Size: 472 B |
|
Before Width: | Height: | Size: 376 B After Width: | Height: | Size: 631 B |
|
|
@ -73,7 +73,6 @@ roundRadiusLarge: 6px;
|
|||
roundRadiusSmall: 3px;
|
||||
|
||||
dateRadius: roundRadiusLarge;
|
||||
buttonRadius: roundRadiusSmall;
|
||||
|
||||
setLittleSkip: 9px;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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([=] {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 ®ion) {
|
||||
}
|
||||
|
||||
inline void InitOnTopPanel(not_null<QWidget*> panel) {
|
||||
|
|
|
|||
|
|
@ -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 ®ion) {
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 ®ion);
|
||||
|
||||
void InitOnTopPanel(not_null<QWidget*> panel);
|
||||
void DeInitOnTopPanel(not_null<QWidget*> panel);
|
||||
|
|
|
|||
|
|
@ -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 ®ion) {
|
||||
}
|
||||
|
||||
inline void ShowOverAll(not_null<QWidget*> widget, bool canFocus) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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.");
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
937
ui/widgets/call_mute_button.cpp
Normal 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
|
||||
126
ui/widgets/call_mute_button.h
Normal 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
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||