-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e73bae1
commit 962c8d8
Showing
12 changed files
with
484 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
// 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 (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 (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(); | ||
} | ||
} | ||
|
||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// 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; | ||
|
||
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); | ||
|
||
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 }; | ||
|
||
QImage _bgMask, _bg; | ||
QPixmap _bgFrom, _bgTo; | ||
QImage _iconMixedMask, _iconFrom, _iconTo, _iconMixed; | ||
|
||
float64 _outerValue = 0.; | ||
Animations::Simple _outerAnimation; | ||
|
||
}; | ||
|
||
} // namespace Ui |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
// 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 "styles/style_widgets.h" | ||
|
||
namespace Ui { | ||
|
||
CallMuteButton::CallMuteButton( | ||
not_null<QWidget*> parent, | ||
CallMuteButtonState initial) | ||
: _state(initial) | ||
, _content(parent, st::callMuteButtonActive, &st::callMuteButtonMuted) | ||
, _connecting(parent, st::callMuteButtonConnecting) { | ||
if (_state.type == CallMuteButtonType::Connecting | ||
|| _state.type == CallMuteButtonType::ForceMuted) { | ||
_connecting.setText(rpl::single(_state.text)); | ||
_connecting.show(); | ||
_content.hide(); | ||
} else { | ||
_content.setText(rpl::single(_state.text)); | ||
_content.setProgress((_state.type == CallMuteButtonType::Muted) ? 1. : 0.); | ||
_connecting.hide(); | ||
_content.show(); | ||
} | ||
_connecting.setAttribute(Qt::WA_TransparentForMouseEvents); | ||
} | ||
|
||
void CallMuteButton::setState(const CallMuteButtonState &state) { | ||
if (state.type == CallMuteButtonType::Connecting | ||
|| state.type == CallMuteButtonType::ForceMuted) { | ||
if (_state.text != state.text) { | ||
_connecting.setText(rpl::single(state.text)); | ||
} | ||
if (!_connecting.isHidden() || !_content.isHidden()) { | ||
_connecting.show(); | ||
} | ||
_content.setOuterValue(0.); | ||
_content.hide(); | ||
} else { | ||
if (_state.text != state.text) { | ||
_content.setText(rpl::single(state.text)); | ||
} | ||
_content.setProgress((state.type == CallMuteButtonType::Muted) ? 1. : 0.); | ||
if (!_connecting.isHidden() || !_content.isHidden()) { | ||
_content.show(); | ||
} | ||
_connecting.hide(); | ||
if (state.type == CallMuteButtonType::Active) { | ||
_content.setOuterValue(_level); | ||
} else { | ||
_content.setOuterValue(0.); | ||
} | ||
} | ||
_state = state; | ||
} | ||
|
||
void CallMuteButton::setLevel(float level) { | ||
_level = level; | ||
if (_state.type == CallMuteButtonType::Active) { | ||
_content.setOuterValue(level); | ||
} | ||
} | ||
|
||
rpl::producer<Qt::MouseButton> CallMuteButton::clicks() const { | ||
return _content.clicks(); | ||
} | ||
|
||
QSize CallMuteButton::innerSize() const { | ||
const auto skip = st::callMuteButtonActive.outerRadius; | ||
return QSize( | ||
_content.width() - 2 * skip, | ||
_content.width() - 2 * skip); | ||
} | ||
|
||
void CallMuteButton::moveInner(QPoint position) { | ||
const auto skip = st::callMuteButtonActive.outerRadius; | ||
_content.move(position - QPoint(skip, skip)); | ||
_connecting.move(_content.pos()); | ||
} | ||
|
||
void CallMuteButton::setVisible(bool visible) { | ||
if (!visible) { | ||
_content.hide(); | ||
_connecting.hide(); | ||
} else if (_state.type == CallMuteButtonType::Connecting | ||
|| _state.type == CallMuteButtonType::ForceMuted) { | ||
_connecting.show(); | ||
} else { | ||
_content.show(); | ||
} | ||
} | ||
|
||
void CallMuteButton::raise() { | ||
_content.raise(); | ||
_connecting.raise(); | ||
} | ||
|
||
void CallMuteButton::lower() { | ||
_content.lower(); | ||
_connecting.lower(); | ||
} | ||
|
||
rpl::lifetime &CallMuteButton::lifetime() { | ||
return _content.lifetime(); | ||
} | ||
|
||
} // namespace Ui |
Oops, something went wrong.