Skip to content

Commit

Permalink
Add two new button types.
Browse files Browse the repository at this point in the history
  • Loading branch information
john-preston committed Nov 28, 2020
1 parent e73bae1 commit 962c8d8
Show file tree
Hide file tree
Showing 12 changed files with 484 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,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
Expand Down
Binary file added icons/calls/voice_muted_large.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/calls/voice_muted_large@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/calls/voice_muted_large@3x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/calls/voice_unmuted_large.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/calls/voice_unmuted_large@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/calls/voice_unmuted_large@3x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
213 changes: 213 additions & 0 deletions ui/widgets/call_button.cpp
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
55 changes: 55 additions & 0 deletions ui/widgets/call_button.h
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
112 changes: 112 additions & 0 deletions ui/widgets/call_mute_button.cpp
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
Loading

0 comments on commit 962c8d8

Please sign in to comment.