Updated lib_ui sources to TDesktop version 2.6
|
|
@ -69,12 +69,16 @@ PRIVATE
|
|||
ui/layers/layer_manager.h
|
||||
ui/layers/layer_widget.cpp
|
||||
ui/layers/layer_widget.h
|
||||
ui/paint/arcs.cpp
|
||||
ui/paint/arcs.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_linux_wayland_integration.cpp
|
||||
ui/platform/linux/ui_linux_wayland_integration.h
|
||||
ui/platform/linux/ui_window_linux.cpp
|
||||
ui/platform/linux/ui_window_linux.h
|
||||
ui/platform/linux/ui_utility_linux.cpp
|
||||
|
|
@ -93,8 +97,11 @@ PRIVATE
|
|||
ui/platform/win/ui_window_win.h
|
||||
ui/platform/win/ui_utility_win.cpp
|
||||
ui/platform/win/ui_utility_win.h
|
||||
ui/platform/ui_platform_window_title.cpp
|
||||
ui/platform/ui_platform_window_title.h
|
||||
ui/platform/ui_platform_window.cpp
|
||||
ui/platform/ui_platform_window.h
|
||||
ui/platform/ui_platform_utility.cpp
|
||||
ui/platform/ui_platform_utility.h
|
||||
ui/style/style_core.cpp
|
||||
ui/style/style_core.h
|
||||
|
|
@ -146,8 +153,18 @@ PRIVATE
|
|||
ui/widgets/input_fields.h
|
||||
ui/widgets/labels.cpp
|
||||
ui/widgets/labels.h
|
||||
ui/widgets/menu.cpp
|
||||
ui/widgets/menu.h
|
||||
ui/widgets/menu/menu.cpp
|
||||
ui/widgets/menu/menu.h
|
||||
ui/widgets/menu/menu_action.cpp
|
||||
ui/widgets/menu/menu_action.h
|
||||
ui/widgets/menu/menu_common.cpp
|
||||
ui/widgets/menu/menu_common.h
|
||||
ui/widgets/menu/menu_item_base.cpp
|
||||
ui/widgets/menu/menu_item_base.h
|
||||
ui/widgets/menu/menu_separator.cpp
|
||||
ui/widgets/menu/menu_separator.h
|
||||
ui/widgets/menu/menu_toggle.cpp
|
||||
ui/widgets/menu/menu_toggle.h
|
||||
ui/widgets/popup_menu.cpp
|
||||
ui/widgets/popup_menu.h
|
||||
ui/widgets/scroll_area.cpp
|
||||
|
|
@ -216,6 +233,11 @@ if (NOT DESKTOP_APP_USE_PACKAGED_FONTS)
|
|||
nice_target_sources(lib_ui ${src_loc} PRIVATE fonts/fonts.qrc)
|
||||
endif()
|
||||
|
||||
if (DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION)
|
||||
remove_target_sources(Telegram ${src_loc} ui/platform/linux/ui_linux_wayland_integration.cpp)
|
||||
nice_target_sources(Telegram ${src_loc} PRIVATE ui/platform/linux/ui_linux_wayland_integration_dummy.cpp)
|
||||
endif()
|
||||
|
||||
target_include_directories(lib_ui
|
||||
PUBLIC
|
||||
${src_loc}
|
||||
|
|
|
|||
BIN
icons/calls/call_shadow_left.png
Normal file
|
After Width: | Height: | Size: 100 B |
BIN
icons/calls/call_shadow_left@2x.png
Normal file
|
After Width: | Height: | Size: 125 B |
BIN
icons/calls/call_shadow_left@3x.png
Normal file
|
After Width: | Height: | Size: 139 B |
BIN
icons/calls/call_shadow_top.png
Normal file
|
After Width: | Height: | Size: 103 B |
BIN
icons/calls/call_shadow_top@2x.png
Normal file
|
After Width: | Height: | Size: 127 B |
BIN
icons/calls/call_shadow_top@3x.png
Normal file
|
After Width: | Height: | Size: 141 B |
BIN
icons/calls/call_shadow_top_left.png
Normal file
|
After Width: | Height: | Size: 295 B |
BIN
icons/calls/call_shadow_top_left@2x.png
Normal file
|
After Width: | Height: | Size: 559 B |
BIN
icons/calls/call_shadow_top_left@3x.png
Normal file
|
After Width: | Height: | Size: 927 B |
|
|
@ -636,6 +636,8 @@ sideBarBadgeBg: #5eb5f7; // filters side bar badge background
|
|||
sideBarBadgeBgMuted: #8393a3; // filters side bar unimportant badge background
|
||||
sideBarBadgeFg: #ffffff; // filters side bar badge text
|
||||
|
||||
songCoverOverlayFg: #00000066; // song cover overlay
|
||||
|
||||
// kotatogram
|
||||
ktgTopBarBg: topBarBg; // Kotatogram: top bar background
|
||||
ktgTopBarNameFg: dialogsNameFg; // Kotatogram: top bar name text
|
||||
|
|
|
|||
|
|
@ -43,11 +43,14 @@ void CrossLineAnimation::paint(
|
|||
_st.icon.paint(p, left, top, _st.icon.width());
|
||||
}
|
||||
} else if (progress == 1.) {
|
||||
if (_completeCross.isNull()) {
|
||||
auto &complete = colorOverride
|
||||
? _completeCrossOverride
|
||||
: _completeCross;
|
||||
if (complete.isNull()) {
|
||||
fillFrame(progress, colorOverride);
|
||||
_completeCross = _frame;
|
||||
complete = _frame;
|
||||
}
|
||||
p.drawImage(left, top, _completeCross);
|
||||
p.drawImage(left, top, complete);
|
||||
} else {
|
||||
fillFrame(progress, colorOverride);
|
||||
p.drawImage(left, top, _frame);
|
||||
|
|
@ -94,6 +97,8 @@ void CrossLineAnimation::fillFrame(
|
|||
|
||||
void CrossLineAnimation::invalidate() {
|
||||
_completeCross = QImage();
|
||||
_completeCrossOverride = QImage();
|
||||
_strokePen = QPen(_st.fg, _st.stroke, Qt::SolidLine, Qt::RoundCap);
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
|
|
|
|||
|
|
@ -39,10 +39,11 @@ private:
|
|||
const style::CrossLineAnimation &_st;
|
||||
const bool _reversed;
|
||||
const QPen _transparentPen;
|
||||
const QPen _strokePen;
|
||||
QPen _strokePen;
|
||||
QLineF _line;
|
||||
QImage _frame;
|
||||
QImage _completeCross;
|
||||
QImage _completeCrossOverride;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
|||
233
ui/paint/arcs.cpp
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
// 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/arcs.h"
|
||||
|
||||
#include "ui/effects/animation_value.h"
|
||||
#include "ui/painter.h"
|
||||
|
||||
namespace Ui::Paint {
|
||||
namespace {
|
||||
|
||||
inline float64 InterpolateF(float a, float b, float64 b_ratio) {
|
||||
return a + float64(b - a) * b_ratio;
|
||||
};
|
||||
|
||||
QRectF InterpolatedRect(const QRectF &r1, const QRectF &r2, float64 ratio) {
|
||||
return QRectF(
|
||||
InterpolateF(r1.x(), r2.x(), ratio),
|
||||
InterpolateF(r1.y(), r2.y(), ratio),
|
||||
InterpolateF(r1.width(), r2.width(), ratio),
|
||||
InterpolateF(r1.height(), r2.height(), ratio));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ArcsAnimation::ArcsAnimation(
|
||||
const style::ArcsAnimation &st,
|
||||
std::vector<float> thresholds,
|
||||
float64 startValue,
|
||||
Direction direction)
|
||||
: _st(st)
|
||||
, _direction(direction)
|
||||
, _startAngle(16
|
||||
* (st.deltaAngle
|
||||
+ ((direction == Direction::Up)
|
||||
? 90
|
||||
: (direction == Direction::Down)
|
||||
? 270
|
||||
: (direction == Direction::Left)
|
||||
? 180
|
||||
: 0)))
|
||||
, _spanAngle(-st.deltaAngle * 2 * 16)
|
||||
, _emptyRect(computeArcRect(0))
|
||||
, _currentValue(startValue) {
|
||||
initArcs(std::move(thresholds));
|
||||
}
|
||||
|
||||
void ArcsAnimation::initArcs(std::vector<float> thresholds) {
|
||||
const auto count = thresholds.size();
|
||||
_arcs.reserve(count);
|
||||
|
||||
for (auto i = 0; i < count; i++) {
|
||||
const auto threshold = thresholds[i];
|
||||
const auto progress = (threshold > _currentValue) ? 1. : 0.;
|
||||
auto arc = Arc{
|
||||
.rect = computeArcRect(i + 1),
|
||||
.threshold = threshold,
|
||||
.progress = progress,
|
||||
};
|
||||
_arcs.push_back(std::move(arc));
|
||||
}
|
||||
}
|
||||
|
||||
bool ArcsAnimation::isHorizontal() const {
|
||||
return _direction == Direction::Left || _direction == Direction::Right;
|
||||
}
|
||||
|
||||
QRectF ArcsAnimation::computeArcRect(int index) const {
|
||||
const auto w = _st.startWidth + _st.deltaWidth * index;
|
||||
const auto h = _st.startHeight + _st.deltaHeight * index;
|
||||
if (isHorizontal()) {
|
||||
auto rect = QRectF(0, -h / 2.0, w, h);
|
||||
if (_direction == Direction::Right) {
|
||||
rect.moveRight(index * _st.space);
|
||||
} else {
|
||||
rect.moveLeft(-index * _st.space);
|
||||
}
|
||||
return rect;
|
||||
} else {
|
||||
auto rect = QRectF(-w / 2.0, 0, w, h);
|
||||
if (_direction == Direction::Up) {
|
||||
rect.moveTop(-index * _st.space);
|
||||
} else {
|
||||
rect.moveBottom(index * _st.space);
|
||||
}
|
||||
return rect;
|
||||
}
|
||||
return QRectF();
|
||||
}
|
||||
|
||||
void ArcsAnimation::update(crl::time now) {
|
||||
for (auto &arc : _arcs) {
|
||||
if (!isArcFinished(arc)) {
|
||||
const auto progress = std::clamp(
|
||||
(now - arc.startTime) / float64(_st.duration),
|
||||
0.,
|
||||
1.);
|
||||
arc.progress = (arc.threshold > _currentValue)
|
||||
? progress
|
||||
: (1. - progress);
|
||||
}
|
||||
}
|
||||
if (isFinished()) {
|
||||
_stopUpdateRequests.fire({});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ArcsAnimation::setValue(float64 value) {
|
||||
if (_currentValue == value) {
|
||||
return;
|
||||
}
|
||||
const auto previousValue = _currentValue;
|
||||
_currentValue = value;
|
||||
if (!isFinished()) {
|
||||
const auto now = crl::now();
|
||||
_startUpdateRequests.fire({});
|
||||
for (auto &arc : _arcs) {
|
||||
updateArcStartTime(arc, previousValue, now);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ArcsAnimation::updateArcStartTime(
|
||||
Arc &arc,
|
||||
float64 previousValue,
|
||||
crl::time now) {
|
||||
if ((arc.progress == 0.) || (arc.progress == 1.)) {
|
||||
arc.startTime = isArcFinished(arc) ? 0 : now;
|
||||
return;
|
||||
}
|
||||
const auto isPreviousToHide = (arc.threshold <= previousValue); // 0 -> 1
|
||||
const auto isCurrentToHide = (arc.threshold <= _currentValue);
|
||||
if (isPreviousToHide != isCurrentToHide) {
|
||||
const auto passedTime = _st.duration * arc.progress;
|
||||
const auto newDelta = isCurrentToHide
|
||||
? (_st.duration - passedTime)
|
||||
: passedTime;
|
||||
arc.startTime = now - newDelta;
|
||||
}
|
||||
}
|
||||
|
||||
float ArcsAnimation::width() const {
|
||||
if (_arcs.empty()) {
|
||||
return 0;
|
||||
}
|
||||
for (const auto &arc : ranges::view::reverse(_arcs)) {
|
||||
if ((arc.progress != 1.)) {
|
||||
return arc.rect.x() + arc.rect.width();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
float ArcsAnimation::finishedWidth() const {
|
||||
if (_arcs.empty()) {
|
||||
return 0;
|
||||
}
|
||||
for (const auto &arc : ranges::view::reverse(_arcs)) {
|
||||
if (arc.threshold <= _currentValue) {
|
||||
return arc.rect.x() + arc.rect.width();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
float ArcsAnimation::maxWidth() const {
|
||||
if (_arcs.empty()) {
|
||||
return 0;
|
||||
}
|
||||
const auto &r = _arcs.back().rect;
|
||||
return r.x() + r.width();
|
||||
}
|
||||
|
||||
float ArcsAnimation::height() const {
|
||||
return _arcs.empty()
|
||||
? 0
|
||||
: _arcs.back().rect.height();
|
||||
}
|
||||
|
||||
rpl::producer<> ArcsAnimation::startUpdateRequests() {
|
||||
return _startUpdateRequests.events();
|
||||
}
|
||||
|
||||
rpl::producer<> ArcsAnimation::stopUpdateRequests() {
|
||||
return _stopUpdateRequests.events();
|
||||
}
|
||||
|
||||
bool ArcsAnimation::isFinished() const {
|
||||
return ranges::all_of(
|
||||
_arcs,
|
||||
[=](const Arc &arc) { return isArcFinished(arc); });
|
||||
}
|
||||
|
||||
bool ArcsAnimation::isArcFinished(const Arc &arc) const {
|
||||
return ((arc.threshold > _currentValue) && (arc.progress == 1.))
|
||||
|| ((arc.threshold <= _currentValue) && (arc.progress == 0.));
|
||||
}
|
||||
|
||||
void ArcsAnimation::paint(Painter &p, std::optional<QColor> colorOverride) {
|
||||
PainterHighQualityEnabler hq(p);
|
||||
QPen pen;
|
||||
if (_strokeRatio) {
|
||||
pen.setWidthF(_st.stroke * _strokeRatio);
|
||||
} else {
|
||||
pen.setWidth(_st.stroke);
|
||||
}
|
||||
pen.setCapStyle(Qt::RoundCap);
|
||||
pen.setColor(colorOverride ? (*colorOverride) : _st.fg->c);
|
||||
p.setPen(pen);
|
||||
for (auto i = 0; i < _arcs.size(); i++) {
|
||||
const auto &arc = _arcs[i];
|
||||
const auto previousRect = (!i) ? _emptyRect : _arcs[i - 1].rect;
|
||||
const auto progress = arc.progress;
|
||||
const auto opactity = (1. - progress);
|
||||
p.setOpacity(opactity * opactity);
|
||||
const auto rect = (progress == 0.)
|
||||
? arc.rect
|
||||
: (progress == 1.)
|
||||
? previousRect
|
||||
: InterpolatedRect(arc.rect, previousRect, progress);
|
||||
p.drawArc(rect, _startAngle, _spanAngle);
|
||||
}
|
||||
}
|
||||
|
||||
void ArcsAnimation::setStrokeRatio(float ratio) {
|
||||
_strokeRatio = ratio;
|
||||
}
|
||||
|
||||
} // namespace Ui::Paint
|
||||
85
ui/paint/arcs.h
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
// 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::Paint {
|
||||
|
||||
class ArcsAnimation {
|
||||
public:
|
||||
|
||||
enum class Direction {
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
};
|
||||
|
||||
ArcsAnimation(
|
||||
const style::ArcsAnimation &st,
|
||||
std::vector<float> thresholds,
|
||||
float64 startValue,
|
||||
Direction direction);
|
||||
|
||||
void paint(
|
||||
Painter &p,
|
||||
std::optional<QColor> colorOverride = std::nullopt);
|
||||
|
||||
void setValue(float64 value);
|
||||
|
||||
rpl::producer<> startUpdateRequests();
|
||||
rpl::producer<> stopUpdateRequests();
|
||||
|
||||
void update(crl::time now);
|
||||
|
||||
bool isFinished() const;
|
||||
|
||||
float width() const;
|
||||
float maxWidth() const;
|
||||
float finishedWidth() const;
|
||||
float height() const;
|
||||
|
||||
void setStrokeRatio(float ratio);
|
||||
|
||||
private:
|
||||
struct Arc {
|
||||
QRectF rect;
|
||||
float threshold;
|
||||
crl::time startTime = 0;
|
||||
float64 progress = 0.;
|
||||
};
|
||||
|
||||
void initArcs(std::vector<float> thresholds);
|
||||
QRectF computeArcRect(int index) const;
|
||||
bool isHorizontal() const;
|
||||
|
||||
bool isArcFinished(const Arc &arc) const;
|
||||
void updateArcStartTime(
|
||||
Arc &arc,
|
||||
float64 previousValue,
|
||||
crl::time now);
|
||||
|
||||
const style::ArcsAnimation &_st;
|
||||
const Direction _direction;
|
||||
const int _startAngle;
|
||||
const int _spanAngle;
|
||||
const QRectF _emptyRect;
|
||||
|
||||
float64 _currentValue = 0.;
|
||||
float _strokeRatio = 0.;
|
||||
|
||||
rpl::event_stream<> _startUpdateRequests;
|
||||
rpl::event_stream<> _stopUpdateRequests;
|
||||
|
||||
std::vector<Arc> _arcs;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui::Paint
|
||||
|
|
@ -52,11 +52,12 @@ void Blob::generateSingleValues(int i) {
|
|||
+ kSegmentSpeedDiff * std::abs(RandomAdditional());
|
||||
}
|
||||
|
||||
void Blob::update(float level, float speedScale) {
|
||||
void Blob::update(float level, float speedScale, float64 rate) {
|
||||
for (auto i = 0; i < _segmentsCount; i++) {
|
||||
auto &segment = segmentAt(i);
|
||||
segment.progress += (segment.speed * _minSpeed)
|
||||
+ level * segment.speed * _maxSpeed * speedScale;
|
||||
segment.progress += (_minSpeed + level * _maxSpeed * speedScale)
|
||||
* segment.speed
|
||||
* rate;
|
||||
if (segment.progress >= 1) {
|
||||
generateSingleValues(i);
|
||||
generateTwoValues(i);
|
||||
|
|
@ -152,9 +153,9 @@ void RadialBlob::generateTwoValues(int i) {
|
|||
radius.setNext(_radiuses.min + std::abs(RandomAdditional()) * radDiff);
|
||||
}
|
||||
|
||||
void RadialBlob::update(float level, float speedScale) {
|
||||
void RadialBlob::update(float level, float speedScale, float64 rate) {
|
||||
_scale = level;
|
||||
Blob::update(level, speedScale);
|
||||
Blob::update(level, speedScale, rate);
|
||||
}
|
||||
|
||||
Blob::Segment &RadialBlob::segmentAt(int i) {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ public:
|
|||
Blob(int n, float minSpeed = 0, float maxSpeed = 0);
|
||||
virtual ~Blob() = default;
|
||||
|
||||
void update(float level, float speedScale);
|
||||
void update(float level, float speedScale, float64 rate);
|
||||
void generateBlob();
|
||||
|
||||
void setRadiuses(Radiuses values);
|
||||
|
|
@ -59,7 +59,7 @@ 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);
|
||||
void update(float level, float speedScale, float64 rate);
|
||||
|
||||
private:
|
||||
struct Segment : Blob::Segment {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,13 @@
|
|||
|
||||
namespace Ui::Paint {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kRateLimitF = 1000. / 60.;
|
||||
constexpr auto kRateLimit = int(kRateLimitF + 0.5); // Round.
|
||||
|
||||
} // namespace
|
||||
|
||||
Blobs::Blobs(
|
||||
std::vector<BlobData> blobDatas,
|
||||
float levelDuration,
|
||||
|
|
@ -76,7 +83,6 @@ void Blobs::resetLevel() {
|
|||
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);
|
||||
|
|
@ -89,7 +95,15 @@ void Blobs::paint(Painter &p, const QBrush &brush, float outerScale) {
|
|||
}
|
||||
|
||||
void Blobs::updateLevel(crl::time dt) {
|
||||
_levelValue.update((dt > 20) ? 17 : dt);
|
||||
const auto limitedDt = (dt > 20) ? kRateLimit : dt;
|
||||
_levelValue.update(limitedDt);
|
||||
|
||||
for (auto i = 0; i < _blobs.size(); i++) {
|
||||
_blobs[i].update(
|
||||
_levelValue.current(),
|
||||
_blobDatas[i].speedScale,
|
||||
limitedDt / kRateLimitF);
|
||||
}
|
||||
}
|
||||
|
||||
float64 Blobs::currentLevel() const {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,13 @@
|
|||
|
||||
namespace Ui::Paint {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kRateLimitF = 1000. / 60.;
|
||||
constexpr auto kRateLimit = int(kRateLimitF + 0.5); // Round.
|
||||
|
||||
} // namespace
|
||||
|
||||
LinearBlobs::LinearBlobs(
|
||||
std::vector<BlobData> blobDatas,
|
||||
float levelDuration,
|
||||
|
|
@ -75,7 +82,6 @@ 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);
|
||||
|
|
@ -88,8 +94,8 @@ void LinearBlobs::paint(Painter &p, const QBrush &brush, int width) {
|
|||
}
|
||||
|
||||
void LinearBlobs::updateLevel(crl::time dt) {
|
||||
const auto d = (dt > 20) ? 17 : dt;
|
||||
_levelValue.update(d);
|
||||
const auto limitedDt = (dt > 20) ? kRateLimit : dt;
|
||||
_levelValue.update(limitedDt);
|
||||
|
||||
const auto level = (float)currentLevel();
|
||||
for (auto i = 0; i < _blobs.size(); i++) {
|
||||
|
|
@ -97,6 +103,10 @@ void LinearBlobs::updateLevel(crl::time dt) {
|
|||
_blobs[i].setRadiuses({
|
||||
data.minRadius,
|
||||
data.idleRadius + (data.maxRadius - data.idleRadius) * level });
|
||||
_blobs[i].update(
|
||||
_levelValue.current(),
|
||||
data.speedScale,
|
||||
limitedDt / kRateLimitF);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
46
ui/platform/linux/ui_linux_wayland_integration.cpp
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "ui/platform/linux/ui_linux_wayland_integration.h"
|
||||
|
||||
#include "base/platform/base_platform_info.h"
|
||||
|
||||
#include <QtGui/QWindow>
|
||||
|
||||
#include <private/qwaylanddisplay_p.h>
|
||||
#include <private/qwaylandwindow_p.h>
|
||||
#include <private/qwaylandshellsurface_p.h>
|
||||
|
||||
using QtWaylandClient::QWaylandWindow;
|
||||
|
||||
namespace Ui {
|
||||
namespace Platform {
|
||||
|
||||
WaylandIntegration::WaylandIntegration() {
|
||||
}
|
||||
|
||||
WaylandIntegration *WaylandIntegration::Instance() {
|
||||
if (!::Platform::IsWayland()) return nullptr;
|
||||
static WaylandIntegration instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
bool WaylandIntegration::showWindowMenu(QWindow *window) {
|
||||
if (const auto waylandWindow = static_cast<QWaylandWindow*>(
|
||||
window->handle())) {
|
||||
if (const auto seat = waylandWindow->display()->lastInputDevice()) {
|
||||
if (const auto shellSurface = waylandWindow->shellSurface()) {
|
||||
return shellSurface->showWindowMenu(seat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
25
ui/platform/linux/ui_linux_wayland_integration.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class QWindow;
|
||||
|
||||
namespace Ui {
|
||||
namespace Platform {
|
||||
|
||||
class WaylandIntegration {
|
||||
public:
|
||||
static WaylandIntegration *Instance();
|
||||
bool showWindowMenu(QWindow *window);
|
||||
|
||||
private:
|
||||
WaylandIntegration();
|
||||
};
|
||||
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
29
ui/platform/linux/ui_linux_wayland_integration_dummy.cpp
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "ui/platform/linux/ui_linux_wayland_integration.h"
|
||||
|
||||
#include "base/platform/base_platform_info.h"
|
||||
|
||||
namespace Ui {
|
||||
namespace Platform {
|
||||
|
||||
WaylandIntegration::WaylandIntegration() {
|
||||
}
|
||||
|
||||
WaylandIntegration *WaylandIntegration::Instance() {
|
||||
if (!::Platform::IsWayland()) return nullptr;
|
||||
static WaylandIntegration instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
bool WaylandIntegration::showWindowMenu(QWindow *window) {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
|
|
@ -8,16 +8,150 @@
|
|||
|
||||
#include "ui/ui_log.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/platform/linux/base_linux_gtk_integration.h"
|
||||
#include "ui/platform/linux/ui_linux_wayland_integration.h"
|
||||
#include "base/const_string.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "base/flat_set.h"
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||
#include "base/platform/linux/base_linux_xcb_utilities.h"
|
||||
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||
|
||||
#include <QtCore/QPoint>
|
||||
#include <QtGui/QScreen>
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <qpa/qplatformnativeinterface.h>
|
||||
|
||||
Q_DECLARE_METATYPE(QMargins);
|
||||
|
||||
namespace Ui {
|
||||
namespace Platform {
|
||||
namespace {
|
||||
|
||||
constexpr auto kXCBFrameExtentsAtomName = "_GTK_FRAME_EXTENTS"_cs;
|
||||
|
||||
constexpr auto kXDGDesktopPortalService = "org.freedesktop.portal.Desktop"_cs;
|
||||
constexpr auto kXDGDesktopPortalObjectPath = "/org/freedesktop/portal/desktop"_cs;
|
||||
constexpr auto kSettingsPortalInterface = "org.freedesktop.portal.Settings"_cs;
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||
bool SetXCBFrameExtents(QWindow *window, const QMargins &extents) {
|
||||
const auto connection = base::Platform::XCB::GetConnectionFromQt();
|
||||
if (!connection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto frameExtentsAtom = base::Platform::XCB::GetAtom(
|
||||
connection,
|
||||
kXCBFrameExtentsAtomName.utf16());
|
||||
|
||||
if (!frameExtentsAtom.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto extentsVector = std::vector<uint>{
|
||||
uint(extents.left()),
|
||||
uint(extents.right()),
|
||||
uint(extents.top()),
|
||||
uint(extents.bottom()),
|
||||
};
|
||||
|
||||
xcb_change_property(
|
||||
connection,
|
||||
XCB_PROP_MODE_REPLACE,
|
||||
window->winId(),
|
||||
*frameExtentsAtom,
|
||||
XCB_ATOM_CARDINAL,
|
||||
32,
|
||||
extentsVector.size(),
|
||||
extentsVector.data());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnsetXCBFrameExtents(QWindow *window) {
|
||||
const auto connection = base::Platform::XCB::GetConnectionFromQt();
|
||||
if (!connection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto frameExtentsAtom = base::Platform::XCB::GetAtom(
|
||||
connection,
|
||||
kXCBFrameExtentsAtomName.utf16());
|
||||
|
||||
if (!frameExtentsAtom.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
xcb_delete_property(
|
||||
connection,
|
||||
window->winId(),
|
||||
*frameExtentsAtom);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShowXCBWindowMenu(QWindow *window) {
|
||||
const auto connection = base::Platform::XCB::GetConnectionFromQt();
|
||||
if (!connection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto root = base::Platform::XCB::GetRootWindowFromQt();
|
||||
if (!root.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto showWindowMenuAtom = base::Platform::XCB::GetAtom(
|
||||
connection,
|
||||
"_GTK_SHOW_WINDOW_MENU");
|
||||
|
||||
if (!showWindowMenuAtom.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto globalPos = QCursor::pos();
|
||||
|
||||
xcb_client_message_event_t xev;
|
||||
xev.response_type = XCB_CLIENT_MESSAGE;
|
||||
xev.type = *showWindowMenuAtom;
|
||||
xev.sequence = 0;
|
||||
xev.window = window->winId();
|
||||
xev.format = 32;
|
||||
xev.data.data32[0] = 0;
|
||||
xev.data.data32[1] = globalPos.x();
|
||||
xev.data.data32[2] = globalPos.y();
|
||||
xev.data.data32[3] = 0;
|
||||
xev.data.data32[4] = 0;
|
||||
|
||||
xcb_ungrab_pointer(connection, XCB_CURRENT_TIME);
|
||||
xcb_send_event(
|
||||
connection,
|
||||
false,
|
||||
*root,
|
||||
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT
|
||||
| XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY,
|
||||
reinterpret_cast<const char*>(&xev));
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||
|
||||
TitleControls::Control GtkKeywordToTitleControl(const QString &keyword) {
|
||||
if (keyword == qstr("minimize")) {
|
||||
return TitleControls::Control::Minimize;
|
||||
} else if (keyword == qstr("maximize")) {
|
||||
return TitleControls::Control::Maximize;
|
||||
} else if (keyword == qstr("close")) {
|
||||
return TitleControls::Control::Close;
|
||||
}
|
||||
|
||||
return TitleControls::Control::Unknown;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool IsApplicationActive() {
|
||||
return QApplication::activeWindow() != nullptr;
|
||||
|
|
@ -50,5 +184,130 @@ bool TranslucentWindowsSupported(QPoint globalPosition) {
|
|||
void IgnoreAllActivation(not_null<QWidget*> widget) {
|
||||
}
|
||||
|
||||
bool WindowExtentsSupported() {
|
||||
#ifdef DESKTOP_APP_QT_PATCHED
|
||||
if (::Platform::IsWayland()) {
|
||||
return true;
|
||||
}
|
||||
#endif // DESKTOP_APP_QT_PATCHED
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||
namespace XCB = base::Platform::XCB;
|
||||
if (!::Platform::IsWayland()
|
||||
&& XCB::IsSupportedByWM(kXCBFrameExtentsAtomName.utf16())) {
|
||||
return true;
|
||||
}
|
||||
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SetWindowExtents(QWindow *window, const QMargins &extents) {
|
||||
if (::Platform::IsWayland()) {
|
||||
#ifdef DESKTOP_APP_QT_PATCHED
|
||||
window->setProperty("WaylandCustomMargins", QVariant::fromValue<QMargins>(extents));
|
||||
return true;
|
||||
#else // DESKTOP_APP_QT_PATCHED
|
||||
return false;
|
||||
#endif // !DESKTOP_APP_QT_PATCHED
|
||||
} else {
|
||||
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||
return SetXCBFrameExtents(window, extents);
|
||||
#else // !DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||
return false;
|
||||
#endif // DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||
}
|
||||
}
|
||||
|
||||
bool UnsetWindowExtents(QWindow *window) {
|
||||
if (::Platform::IsWayland()) {
|
||||
#ifdef DESKTOP_APP_QT_PATCHED
|
||||
window->setProperty("WaylandCustomMargins", QVariant());
|
||||
return true;
|
||||
#else // DESKTOP_APP_QT_PATCHED
|
||||
return false;
|
||||
#endif // !DESKTOP_APP_QT_PATCHED
|
||||
} else {
|
||||
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||
return UnsetXCBFrameExtents(window);
|
||||
#else // !DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||
return false;
|
||||
#endif // DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||
}
|
||||
}
|
||||
|
||||
bool ShowWindowMenu(QWindow *window) {
|
||||
if (const auto integration = WaylandIntegration::Instance()) {
|
||||
return integration->showWindowMenu(window);
|
||||
} else {
|
||||
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||
return ShowXCBWindowMenu(window);
|
||||
#else // !DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||
return false;
|
||||
#endif // DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||
}
|
||||
}
|
||||
|
||||
TitleControls::Layout TitleControlsLayout() {
|
||||
const auto gtkResult = []() -> std::optional<TitleControls::Layout> {
|
||||
const auto integration = base::Platform::GtkIntegration::Instance();
|
||||
if (!integration || !integration->checkVersion(3, 12, 0)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto decorationLayoutSetting = integration->getStringSetting(
|
||||
"gtk-decoration-layout");
|
||||
|
||||
if (!decorationLayoutSetting.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto decorationLayout = decorationLayoutSetting->split(':');
|
||||
|
||||
std::vector<TitleControls::Control> controlsLeft;
|
||||
ranges::transform(
|
||||
decorationLayout[0].split(','),
|
||||
ranges::back_inserter(controlsLeft),
|
||||
GtkKeywordToTitleControl);
|
||||
|
||||
std::vector<TitleControls::Control> controlsRight;
|
||||
if (decorationLayout.size() > 1) {
|
||||
ranges::transform(
|
||||
decorationLayout[1].split(','),
|
||||
ranges::back_inserter(controlsRight),
|
||||
GtkKeywordToTitleControl);
|
||||
}
|
||||
|
||||
return TitleControls::Layout{
|
||||
.left = controlsLeft,
|
||||
.right = controlsRight
|
||||
};
|
||||
}();
|
||||
|
||||
if (gtkResult.has_value()) {
|
||||
return *gtkResult;
|
||||
}
|
||||
|
||||
#ifdef __HAIKU__
|
||||
return TitleControls::Layout{
|
||||
.left = {
|
||||
TitleControls::Control::Close,
|
||||
},
|
||||
.right = {
|
||||
TitleControls::Control::Minimize,
|
||||
TitleControls::Control::Maximize,
|
||||
}
|
||||
};
|
||||
#else // __HAIKU__
|
||||
return TitleControls::Layout{
|
||||
.right = {
|
||||
TitleControls::Control::Minimize,
|
||||
TitleControls::Control::Maximize,
|
||||
TitleControls::Control::Close,
|
||||
}
|
||||
};
|
||||
#endif // !__HAIKU__
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
//
|
||||
#pragma once
|
||||
|
||||
#include "ui/platform/ui_platform_utility.h"
|
||||
|
||||
class QPainter;
|
||||
class QPaintEvent;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
#pragma once
|
||||
|
||||
#include "ui/platform/ui_platform_utility.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include <QtCore/QPoint>
|
||||
|
||||
|
|
@ -23,5 +24,21 @@ inline constexpr bool UseMainQueueGeneric() {
|
|||
return ::Platform::IsMacStoreBuild();
|
||||
}
|
||||
|
||||
inline bool WindowExtentsSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool SetWindowExtents(QWindow *window, const QMargins &extents) {
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool UnsetWindowExtents(QWindow *window) {
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool ShowWindowMenu(QWindow *window) {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
|
|
|
|||
|
|
@ -99,5 +99,15 @@ void DrainMainQueue() {
|
|||
void IgnoreAllActivation(not_null<QWidget*> widget) {
|
||||
}
|
||||
|
||||
TitleControls::Layout TitleControlsLayout() {
|
||||
return TitleControls::Layout{
|
||||
.left = {
|
||||
TitleControls::Control::Close,
|
||||
TitleControls::Control::Minimize,
|
||||
TitleControls::Control::Maximize,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
|
|
|
|||
26
ui/platform/ui_platform_utility.cpp
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// 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/platform/ui_platform_utility.h"
|
||||
|
||||
namespace Ui {
|
||||
namespace Platform {
|
||||
namespace {
|
||||
|
||||
rpl::event_stream<> TitleControlsLayoutChanges;
|
||||
|
||||
} // namespace
|
||||
|
||||
rpl::producer<> TitleControlsLayoutChanged() {
|
||||
return TitleControlsLayoutChanges.events();
|
||||
}
|
||||
|
||||
void NotifyTitleControlsLayoutChanged() {
|
||||
TitleControlsLayoutChanges.fire({});
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
|
|
@ -6,6 +6,8 @@
|
|||
//
|
||||
#pragma once
|
||||
|
||||
#include "ui/platform/ui_platform_window_title.h"
|
||||
|
||||
class QPoint;
|
||||
class QPainter;
|
||||
class QPaintEvent;
|
||||
|
|
@ -30,6 +32,15 @@ void IgnoreAllActivation(not_null<QWidget*> widget);
|
|||
[[nodiscard]] constexpr bool UseMainQueueGeneric();
|
||||
void DrainMainQueue(); // Needed only if UseMainQueueGeneric() is false.
|
||||
|
||||
[[nodiscard]] bool WindowExtentsSupported();
|
||||
bool SetWindowExtents(QWindow *window, const QMargins &extents);
|
||||
bool UnsetWindowExtents(QWindow *window);
|
||||
bool ShowWindowMenu(QWindow *window);
|
||||
|
||||
[[nodiscard]] TitleControls::Layout TitleControlsLayout();
|
||||
[[nodiscard]] rpl::producer<> TitleControlsLayoutChanged();
|
||||
void NotifyTitleControlsLayoutChanged();
|
||||
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
|
||||
|
|
|
|||
|
|
@ -6,21 +6,31 @@
|
|||
//
|
||||
#include "ui/platform/ui_platform_window.h"
|
||||
|
||||
#include "ui/platform/ui_platform_window_title.h"
|
||||
#include "ui/platform/ui_platform_utility.h"
|
||||
#include "ui/widgets/window.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/painter.h"
|
||||
#include "styles/style_widgets.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtGui/QtEvents>
|
||||
|
||||
namespace Ui {
|
||||
namespace Platform {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] const style::Shadow &Shadow() {
|
||||
return st::callShadow;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BasicWindowHelper::BasicWindowHelper(not_null<RpWidget*> window)
|
||||
: _window(window) {
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
|
||||
_window->setWindowFlag(Qt::Window);
|
||||
#else // Qt >= 5.9
|
||||
_window->setWindowFlags(_window->windowFlags() | Qt::Window);
|
||||
#endif // Qt >= 5.9
|
||||
}
|
||||
|
||||
not_null<RpWidget*> BasicWindowHelper::body() {
|
||||
|
|
@ -100,8 +110,6 @@ void BasicWindowHelper::setupBodyTitleAreaEvents() {
|
|||
&& (static_cast<QMouseEvent*>(e.get())->button()
|
||||
== Qt::LeftButton)) {
|
||||
_mousePressed = true;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) || defined DESKTOP_APP_QT_PATCHED
|
||||
} else if (e->type() == QEvent::MouseMove) {
|
||||
const auto mouseEvent = static_cast<QMouseEvent*>(e.get());
|
||||
if (_mousePressed
|
||||
|
|
@ -109,7 +117,6 @@ void BasicWindowHelper::setupBodyTitleAreaEvents() {
|
|||
&& !_window->isFullScreen()
|
||||
#endif // !Q_OS_WIN
|
||||
&& (hitTest() & WindowTitleHitTestFlag::Move)) {
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (_window->isFullScreen()) {
|
||||
// On Windows we just jump out of fullscreen
|
||||
|
|
@ -121,10 +128,270 @@ void BasicWindowHelper::setupBodyTitleAreaEvents() {
|
|||
_mousePressed = false;
|
||||
_window->windowHandle()->startSystemMove();
|
||||
}
|
||||
#endif // Qt >= 5.15 || DESKTOP_APP_QT_PATCHED
|
||||
}
|
||||
}, body()->lifetime());
|
||||
}
|
||||
|
||||
DefaultWindowHelper::DefaultWindowHelper(not_null<RpWidget*> window)
|
||||
: BasicWindowHelper(window)
|
||||
, _title(Ui::CreateChild<DefaultTitleWidget>(window.get()))
|
||||
, _body(Ui::CreateChild<RpWidget>(window.get())) {
|
||||
init();
|
||||
}
|
||||
|
||||
void DefaultWindowHelper::init() {
|
||||
window()->setWindowFlag(Qt::FramelessWindowHint);
|
||||
|
||||
if (WindowExtentsSupported()) {
|
||||
window()->setAttribute(Qt::WA_TranslucentBackground);
|
||||
}
|
||||
|
||||
window()->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
const auto area = resizeArea();
|
||||
_title->setGeometry(
|
||||
area.left(),
|
||||
area.top(),
|
||||
width - area.left() - area.right(),
|
||||
_title->st()->height);
|
||||
}, _title->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
window()->sizeValue(),
|
||||
_title->heightValue()
|
||||
) | rpl::start_with_next([=](QSize size, int titleHeight) {
|
||||
const auto area = resizeArea();
|
||||
|
||||
const auto sizeWithoutMargins = size
|
||||
.shrunkBy({ 0, titleHeight, 0, 0 })
|
||||
.shrunkBy(area);
|
||||
|
||||
const auto topLeft = QPoint(
|
||||
area.left(),
|
||||
area.top() + titleHeight);
|
||||
|
||||
_body->setGeometry(QRect(topLeft, sizeWithoutMargins));
|
||||
}, _body->lifetime());
|
||||
|
||||
window()->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto area = resizeArea();
|
||||
|
||||
if (area.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Painter p(window());
|
||||
|
||||
if (hasShadow()) {
|
||||
Ui::Shadow::paint(
|
||||
p,
|
||||
QRect(QPoint(), window()->size()).marginsRemoved(area),
|
||||
window()->width(),
|
||||
Shadow());
|
||||
} else {
|
||||
paintBorders(p);
|
||||
}
|
||||
}, window()->lifetime());
|
||||
|
||||
window()->shownValue(
|
||||
) | rpl::start_with_next([=](bool shown) {
|
||||
if (shown) {
|
||||
updateWindowExtents();
|
||||
}
|
||||
}, window()->lifetime());
|
||||
|
||||
window()->events() | rpl::start_with_next([=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::MouseButtonPress) {
|
||||
const auto mouseEvent = static_cast<QMouseEvent*>(e.get());
|
||||
const auto currentPoint = mouseEvent->windowPos().toPoint();
|
||||
const auto edges = edgesFromPos(currentPoint);
|
||||
|
||||
if (mouseEvent->button() == Qt::LeftButton && edges) {
|
||||
window()->windowHandle()->startSystemResize(edges);
|
||||
}
|
||||
} else if (e->type() == QEvent::Move
|
||||
|| e->type() == QEvent::Resize
|
||||
|| e->type() == QEvent::WindowStateChange) {
|
||||
updateWindowExtents();
|
||||
}
|
||||
}, window()->lifetime());
|
||||
|
||||
QCoreApplication::instance()->installEventFilter(this);
|
||||
}
|
||||
|
||||
not_null<RpWidget*> DefaultWindowHelper::body() {
|
||||
return _body;
|
||||
}
|
||||
|
||||
bool DefaultWindowHelper::hasShadow() const {
|
||||
const auto center = window()->geometry().center();
|
||||
return WindowExtentsSupported() && TranslucentWindowsSupported(center);
|
||||
}
|
||||
|
||||
QMargins DefaultWindowHelper::resizeArea() const {
|
||||
if (window()->isMaximized() || window()->isFullScreen()) {
|
||||
return QMargins();
|
||||
}
|
||||
|
||||
return Shadow().extend;
|
||||
}
|
||||
|
||||
Qt::Edges DefaultWindowHelper::edgesFromPos(const QPoint &pos) const {
|
||||
const auto area = resizeArea();
|
||||
|
||||
if (area.isNull()) {
|
||||
return Qt::Edges();
|
||||
} else if (pos.x() <= area.left()) {
|
||||
if (pos.y() <= area.top()) {
|
||||
return Qt::LeftEdge | Qt::TopEdge;
|
||||
} else if (pos.y() >= (window()->height() - area.bottom())) {
|
||||
return Qt::LeftEdge | Qt::BottomEdge;
|
||||
}
|
||||
|
||||
return Qt::LeftEdge;
|
||||
} else if (pos.x() >= (window()->width() - area.right())) {
|
||||
if (pos.y() <= area.top()) {
|
||||
return Qt::RightEdge | Qt::TopEdge;
|
||||
} else if (pos.y() >= (window()->height() - area.bottom())) {
|
||||
return Qt::RightEdge | Qt::BottomEdge;
|
||||
}
|
||||
|
||||
return Qt::RightEdge;
|
||||
} else if (pos.y() <= area.top()) {
|
||||
return Qt::TopEdge;
|
||||
} else if (pos.y() >= (window()->height() - area.bottom())) {
|
||||
return Qt::BottomEdge;
|
||||
}
|
||||
|
||||
return Qt::Edges();
|
||||
}
|
||||
|
||||
bool DefaultWindowHelper::eventFilter(QObject *obj, QEvent *e) {
|
||||
// doesn't work with RpWidget::events() for some reason
|
||||
if (e->type() == QEvent::MouseMove
|
||||
&& obj->isWidgetType()
|
||||
&& static_cast<QWidget*>(window()) == static_cast<QWidget*>(obj)) {
|
||||
const auto mouseEvent = static_cast<QMouseEvent*>(e);
|
||||
const auto currentPoint = mouseEvent->windowPos().toPoint();
|
||||
const auto edges = edgesFromPos(currentPoint);
|
||||
|
||||
if (mouseEvent->buttons() == Qt::NoButton) {
|
||||
updateCursor(edges);
|
||||
}
|
||||
}
|
||||
|
||||
return QObject::eventFilter(obj, e);
|
||||
}
|
||||
|
||||
void DefaultWindowHelper::setTitle(const QString &title) {
|
||||
_title->setText(title);
|
||||
window()->setWindowTitle(title);
|
||||
}
|
||||
|
||||
void DefaultWindowHelper::setTitleStyle(const style::WindowTitle &st) {
|
||||
const auto area = resizeArea();
|
||||
_title->setStyle(st);
|
||||
_title->setGeometry(
|
||||
area.left(),
|
||||
area.top(),
|
||||
window()->width() - area.left() - area.right(),
|
||||
_title->st()->height);
|
||||
}
|
||||
|
||||
void DefaultWindowHelper::setMinimumSize(QSize size) {
|
||||
const auto sizeWithMargins = size
|
||||
.grownBy({ 0, _title->height(), 0, 0 })
|
||||
.grownBy(resizeArea());
|
||||
window()->setMinimumSize(sizeWithMargins);
|
||||
}
|
||||
|
||||
void DefaultWindowHelper::setFixedSize(QSize size) {
|
||||
const auto sizeWithMargins = size
|
||||
.grownBy({ 0, _title->height(), 0, 0 })
|
||||
.grownBy(resizeArea());
|
||||
window()->setFixedSize(sizeWithMargins);
|
||||
_title->setResizeEnabled(false);
|
||||
}
|
||||
|
||||
void DefaultWindowHelper::setGeometry(QRect rect) {
|
||||
window()->setGeometry(rect
|
||||
.marginsAdded({ 0, _title->height(), 0, 0 })
|
||||
.marginsAdded(resizeArea()));
|
||||
}
|
||||
|
||||
void DefaultWindowHelper::paintBorders(QPainter &p) {
|
||||
const auto titleBackground = window()->isActiveWindow()
|
||||
? _title->st()->bgActive
|
||||
: _title->st()->bg;
|
||||
|
||||
const auto defaultTitleBackground = window()->isActiveWindow()
|
||||
? st::defaultWindowTitle.bgActive
|
||||
: st::defaultWindowTitle.bg;
|
||||
|
||||
const auto borderColor = QBrush(titleBackground).isOpaque()
|
||||
? titleBackground
|
||||
: defaultTitleBackground;
|
||||
|
||||
const auto area = resizeArea();
|
||||
|
||||
p.fillRect(
|
||||
0,
|
||||
area.top(),
|
||||
area.left(),
|
||||
window()->height() - area.top() - area.bottom(),
|
||||
borderColor);
|
||||
|
||||
p.fillRect(
|
||||
window()->width() - area.right(),
|
||||
area.top(),
|
||||
area.right(),
|
||||
window()->height() - area.top() - area.bottom(),
|
||||
borderColor);
|
||||
|
||||
p.fillRect(
|
||||
0,
|
||||
0,
|
||||
window()->width(),
|
||||
area.top(),
|
||||
borderColor);
|
||||
|
||||
p.fillRect(
|
||||
0,
|
||||
window()->height() - area.bottom(),
|
||||
window()->width(),
|
||||
area.bottom(),
|
||||
borderColor);
|
||||
}
|
||||
|
||||
void DefaultWindowHelper::updateWindowExtents() {
|
||||
if (hasShadow()) {
|
||||
Platform::SetWindowExtents(
|
||||
window()->windowHandle(),
|
||||
resizeArea());
|
||||
|
||||
_extentsSet = true;
|
||||
} else if (_extentsSet) {
|
||||
Platform::UnsetWindowExtents(window()->windowHandle());
|
||||
_extentsSet = false;
|
||||
}
|
||||
}
|
||||
|
||||
void DefaultWindowHelper::updateCursor(Qt::Edges edges) {
|
||||
if (((edges & Qt::LeftEdge) && (edges & Qt::TopEdge))
|
||||
|| ((edges & Qt::RightEdge) && (edges & Qt::BottomEdge))) {
|
||||
window()->setCursor(QCursor(Qt::SizeFDiagCursor));
|
||||
} else if (((edges & Qt::LeftEdge) && (edges & Qt::BottomEdge))
|
||||
|| ((edges & Qt::RightEdge) && (edges & Qt::TopEdge))) {
|
||||
window()->setCursor(QCursor(Qt::SizeBDiagCursor));
|
||||
} else if ((edges & Qt::LeftEdge) || (edges & Qt::RightEdge)) {
|
||||
window()->setCursor(QCursor(Qt::SizeHorCursor));
|
||||
} else if ((edges & Qt::TopEdge) || (edges & Qt::BottomEdge)) {
|
||||
window()->setCursor(QCursor(Qt::SizeVerCursor));
|
||||
} else {
|
||||
window()->unsetCursor();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ using WindowTitleHitTestFlags = base::flags<WindowTitleHitTestFlag>;
|
|||
|
||||
namespace Platform {
|
||||
|
||||
class DefaultTitleWidget;
|
||||
|
||||
class BasicWindowHelper {
|
||||
public:
|
||||
explicit BasicWindowHelper(not_null<RpWidget*> window);
|
||||
|
|
@ -57,6 +59,35 @@ private:
|
|||
|
||||
};
|
||||
|
||||
class DefaultWindowHelper final : public QObject, public BasicWindowHelper {
|
||||
public:
|
||||
explicit DefaultWindowHelper(not_null<RpWidget*> window);
|
||||
|
||||
not_null<RpWidget*> body() override;
|
||||
void setTitle(const QString &title) override;
|
||||
void setTitleStyle(const style::WindowTitle &st) override;
|
||||
void setMinimumSize(QSize size) override;
|
||||
void setFixedSize(QSize size) override;
|
||||
void setGeometry(QRect rect) override;
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *e) override;
|
||||
|
||||
private:
|
||||
void init();
|
||||
[[nodiscard]] bool hasShadow() const;
|
||||
[[nodiscard]] QMargins resizeArea() const;
|
||||
[[nodiscard]] Qt::Edges edgesFromPos(const QPoint &pos) const;
|
||||
void paintBorders(QPainter &p);
|
||||
void updateWindowExtents();
|
||||
void updateCursor(Qt::Edges edges);
|
||||
|
||||
const not_null<DefaultTitleWidget*> _title;
|
||||
const not_null<RpWidget*> _body;
|
||||
bool _extentsSet = false;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] std::unique_ptr<BasicWindowHelper> CreateSpecialWindowHelper(
|
||||
not_null<RpWidget*> window);
|
||||
|
||||
|
|
@ -65,7 +96,7 @@ private:
|
|||
if (auto special = CreateSpecialWindowHelper(window)) {
|
||||
return special;
|
||||
}
|
||||
return std::make_unique<BasicWindowHelper>(window);
|
||||
return std::make_unique<DefaultWindowHelper>(window);
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
|
|
|
|||
353
ui/platform/ui_platform_window_title.cpp
Normal file
|
|
@ -0,0 +1,353 @@
|
|||
// 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/platform/ui_platform_window_title.h"
|
||||
|
||||
#include "ui/platform/ui_platform_utility.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "styles/style_widgets.h"
|
||||
#include "styles/palette.h"
|
||||
#include "base/algorithm.h"
|
||||
#include "base/event_filter.h"
|
||||
|
||||
#include <QtGui/QPainter>
|
||||
#include <QtGui/QtEvents>
|
||||
#include <QtGui/QWindow>
|
||||
|
||||
namespace Ui {
|
||||
namespace Platform {
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
void RemoveDuplicates(std::vector<T> &v) {
|
||||
auto end = v.end();
|
||||
for (auto it = v.begin(); it != end; ++it) {
|
||||
end = std::remove(it + 1, end, *it);
|
||||
}
|
||||
|
||||
v.erase(end, v.end());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TitleControls::TitleControls(
|
||||
not_null<RpWidget*> parent,
|
||||
const style::WindowTitle &st,
|
||||
Fn<void(bool maximized)> maximize)
|
||||
: _st(&st)
|
||||
, _minimize(parent, _st->minimize)
|
||||
, _maximizeRestore(parent, _st->maximize)
|
||||
, _close(parent, _st->close)
|
||||
, _maximizedState(parent->windowState()
|
||||
& (Qt::WindowMaximized | Qt::WindowFullScreen))
|
||||
, _activeState(parent->isActiveWindow()) {
|
||||
init(std::move(maximize));
|
||||
|
||||
_close->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto active = window()->isActiveWindow();
|
||||
if (_activeState != active) {
|
||||
_activeState = active;
|
||||
updateButtonsState();
|
||||
}
|
||||
}, _close->lifetime());
|
||||
}
|
||||
|
||||
void TitleControls::setStyle(const style::WindowTitle &st) {
|
||||
_st = &st;
|
||||
updateButtonsState();
|
||||
}
|
||||
|
||||
not_null<const style::WindowTitle*> TitleControls::st() const {
|
||||
return _st;
|
||||
}
|
||||
|
||||
QRect TitleControls::geometry() const {
|
||||
auto result = QRect();
|
||||
const auto add = [&](auto &&control) {
|
||||
if (!control->isHidden()) {
|
||||
result = result.united(control->geometry());
|
||||
}
|
||||
};
|
||||
add(_minimize);
|
||||
add(_maximizeRestore);
|
||||
add(_close);
|
||||
return result;
|
||||
}
|
||||
|
||||
not_null<RpWidget*> TitleControls::parent() const {
|
||||
return static_cast<RpWidget*>(_close->parentWidget());
|
||||
}
|
||||
|
||||
not_null<QWidget*> TitleControls::window() const {
|
||||
return _close->window();
|
||||
}
|
||||
|
||||
void TitleControls::init(Fn<void(bool maximized)> maximize) {
|
||||
_minimize->setClickedCallback([=] {
|
||||
window()->setWindowState(
|
||||
window()->windowState() | Qt::WindowMinimized);
|
||||
_minimize->clearState();
|
||||
});
|
||||
_minimize->setPointerCursor(false);
|
||||
_maximizeRestore->setClickedCallback([=] {
|
||||
if (maximize) {
|
||||
maximize(!_maximizedState);
|
||||
} else {
|
||||
window()->setWindowState(_maximizedState
|
||||
? Qt::WindowNoState
|
||||
: Qt::WindowMaximized);
|
||||
}
|
||||
_maximizeRestore->clearState();
|
||||
});
|
||||
_maximizeRestore->setPointerCursor(false);
|
||||
_close->setClickedCallback([=] {
|
||||
window()->close();
|
||||
_close->clearState();
|
||||
});
|
||||
_close->setPointerCursor(false);
|
||||
|
||||
parent()->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
updateControlsPosition();
|
||||
}, _close->lifetime());
|
||||
|
||||
TitleControlsLayoutChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateControlsPosition();
|
||||
}, _close->lifetime());
|
||||
|
||||
const auto winIdEventFilter = std::make_shared<QObject*>(nullptr);
|
||||
*winIdEventFilter = base::install_event_filter(
|
||||
window(),
|
||||
[=](not_null<QEvent*> e) {
|
||||
if (!*winIdEventFilter || e->type() != QEvent::WinIdChange) {
|
||||
return base::EventFilterResult::Continue;
|
||||
}
|
||||
|
||||
QObject::connect(
|
||||
window()->windowHandle(),
|
||||
&QWindow::windowStateChanged,
|
||||
[=](Qt::WindowState state) {
|
||||
handleWindowStateChanged(state);
|
||||
});
|
||||
|
||||
base::take(*winIdEventFilter)->deleteLater();
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
|
||||
_activeState = parent()->isActiveWindow();
|
||||
updateButtonsState();
|
||||
}
|
||||
|
||||
void TitleControls::setResizeEnabled(bool enabled) {
|
||||
_resizeEnabled = enabled;
|
||||
updateControlsPosition();
|
||||
}
|
||||
|
||||
void TitleControls::raise() {
|
||||
_minimize->raise();
|
||||
_maximizeRestore->raise();
|
||||
_close->raise();
|
||||
}
|
||||
|
||||
Ui::IconButton *TitleControls::controlWidget(Control control) const {
|
||||
switch (control) {
|
||||
case Control::Minimize: return _minimize;
|
||||
case Control::Maximize: return _maximizeRestore;
|
||||
case Control::Close: return _close;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void TitleControls::updateControlsPosition() {
|
||||
const auto controlsLayout = TitleControlsLayout();
|
||||
auto controlsLeft = controlsLayout.left;
|
||||
auto controlsRight = controlsLayout.right;
|
||||
|
||||
const auto controlPresent = [&](Control control) {
|
||||
return ranges::contains(controlsLeft, control)
|
||||
|| ranges::contains(controlsRight, control);
|
||||
};
|
||||
|
||||
const auto eraseControl = [&](Control control) {
|
||||
controlsLeft.erase(
|
||||
ranges::remove(controlsLeft, control),
|
||||
end(controlsLeft));
|
||||
|
||||
controlsRight.erase(
|
||||
ranges::remove(controlsRight, control),
|
||||
end(controlsRight));
|
||||
};
|
||||
|
||||
if (!_resizeEnabled) {
|
||||
eraseControl(Control::Maximize);
|
||||
}
|
||||
|
||||
if (controlPresent(Control::Minimize)) {
|
||||
_minimize->show();
|
||||
} else {
|
||||
_minimize->hide();
|
||||
}
|
||||
|
||||
if (controlPresent(Control::Maximize)) {
|
||||
_maximizeRestore->show();
|
||||
} else {
|
||||
_maximizeRestore->hide();
|
||||
}
|
||||
|
||||
if (controlPresent(Control::Close)) {
|
||||
_close->show();
|
||||
} else {
|
||||
_close->hide();
|
||||
}
|
||||
|
||||
updateControlsPositionBySide(controlsLeft, false);
|
||||
updateControlsPositionBySide(controlsRight, true);
|
||||
}
|
||||
|
||||
void TitleControls::updateControlsPositionBySide(
|
||||
const std::vector<Control> &controls,
|
||||
bool right) {
|
||||
auto preparedControls = right
|
||||
? (ranges::view::reverse(controls) | ranges::to_vector)
|
||||
: controls;
|
||||
|
||||
RemoveDuplicates(preparedControls);
|
||||
|
||||
auto position = 0;
|
||||
for (const auto &control : preparedControls) {
|
||||
const auto widget = controlWidget(control);
|
||||
if (!widget) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (right) {
|
||||
widget->moveToRight(position, 0);
|
||||
} else {
|
||||
widget->moveToLeft(position, 0);
|
||||
}
|
||||
|
||||
position += widget->width();
|
||||
}
|
||||
}
|
||||
|
||||
void TitleControls::handleWindowStateChanged(Qt::WindowState state) {
|
||||
if (state == Qt::WindowMinimized) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto maximized = (state == Qt::WindowMaximized)
|
||||
|| (state == Qt::WindowFullScreen);
|
||||
if (_maximizedState != maximized) {
|
||||
_maximizedState = maximized;
|
||||
updateButtonsState();
|
||||
}
|
||||
}
|
||||
|
||||
void TitleControls::updateButtonsState() {
|
||||
const auto minimize = _activeState
|
||||
? &_st->minimizeIconActive
|
||||
: &_st->minimize.icon;
|
||||
const auto minimizeOver = _activeState
|
||||
? &_st->minimizeIconActiveOver
|
||||
: &_st->minimize.iconOver;
|
||||
_minimize->setIconOverride(minimize, minimizeOver);
|
||||
if (_maximizedState) {
|
||||
const auto restore = _activeState
|
||||
? &_st->restoreIconActive
|
||||
: &_st->restoreIcon;
|
||||
const auto restoreOver = _activeState
|
||||
? &_st->restoreIconActiveOver
|
||||
: &_st->restoreIconOver;
|
||||
_maximizeRestore->setIconOverride(restore, restoreOver);
|
||||
} else {
|
||||
const auto maximize = _activeState
|
||||
? &_st->maximizeIconActive
|
||||
: &_st->maximize.icon;
|
||||
const auto maximizeOver = _activeState
|
||||
? &_st->maximizeIconActiveOver
|
||||
: &_st->maximize.iconOver;
|
||||
_maximizeRestore->setIconOverride(maximize, maximizeOver);
|
||||
}
|
||||
const auto close = _activeState
|
||||
? &_st->closeIconActive
|
||||
: &_st->close.icon;
|
||||
const auto closeOver = _activeState
|
||||
? &_st->closeIconActiveOver
|
||||
: &_st->close.iconOver;
|
||||
_close->setIconOverride(close, closeOver);
|
||||
}
|
||||
|
||||
DefaultTitleWidget::DefaultTitleWidget(not_null<RpWidget*> parent)
|
||||
: RpWidget(parent)
|
||||
, _controls(this, st::defaultWindowTitle)
|
||||
, _shadow(this, st::titleShadow) {
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
}
|
||||
|
||||
not_null<const style::WindowTitle*> DefaultTitleWidget::st() const {
|
||||
return _controls.st();
|
||||
}
|
||||
|
||||
void DefaultTitleWidget::setText(const QString &text) {
|
||||
window()->setWindowTitle(text);
|
||||
}
|
||||
|
||||
void DefaultTitleWidget::setStyle(const style::WindowTitle &st) {
|
||||
_controls.setStyle(st);
|
||||
update();
|
||||
}
|
||||
|
||||
void DefaultTitleWidget::setResizeEnabled(bool enabled) {
|
||||
_controls.setResizeEnabled(enabled);
|
||||
}
|
||||
|
||||
void DefaultTitleWidget::paintEvent(QPaintEvent *e) {
|
||||
const auto active = window()->isActiveWindow();
|
||||
QPainter(this).fillRect(
|
||||
e->rect(),
|
||||
active ? _controls.st()->bgActive : _controls.st()->bg);
|
||||
}
|
||||
|
||||
void DefaultTitleWidget::resizeEvent(QResizeEvent *e) {
|
||||
_shadow->setGeometry(0, height() - st::lineWidth, width(), st::lineWidth);
|
||||
}
|
||||
|
||||
void DefaultTitleWidget::mousePressEvent(QMouseEvent *e) {
|
||||
if (e->button() == Qt::LeftButton) {
|
||||
_mousePressed = true;
|
||||
} else if (e->button() == Qt::RightButton) {
|
||||
ShowWindowMenu(window()->windowHandle());
|
||||
}
|
||||
}
|
||||
|
||||
void DefaultTitleWidget::mouseReleaseEvent(QMouseEvent *e) {
|
||||
if (e->button() == Qt::LeftButton) {
|
||||
_mousePressed = false;
|
||||
}
|
||||
}
|
||||
|
||||
void DefaultTitleWidget::mouseMoveEvent(QMouseEvent *e) {
|
||||
if (_mousePressed) {
|
||||
window()->windowHandle()->startSystemMove();
|
||||
}
|
||||
}
|
||||
|
||||
void DefaultTitleWidget::mouseDoubleClickEvent(QMouseEvent *e) {
|
||||
const auto state = window()->windowState();
|
||||
if (state & Qt::WindowMaximized) {
|
||||
window()->setWindowState(state & ~Qt::WindowMaximized);
|
||||
} else {
|
||||
window()->setWindowState(state | Qt::WindowMaximized);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
101
ui/platform/ui_platform_window_title.h
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
// 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/rp_widget.h"
|
||||
#include "base/object_ptr.h"
|
||||
|
||||
#include <QtCore/QRect>
|
||||
#include <QtCore/QPoint>
|
||||
|
||||
namespace style {
|
||||
struct WindowTitle;
|
||||
} // namespace style
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class IconButton;
|
||||
class PlainShadow;
|
||||
|
||||
namespace Platform {
|
||||
|
||||
class TitleControls final {
|
||||
public:
|
||||
TitleControls(
|
||||
not_null<RpWidget*> parent,
|
||||
const style::WindowTitle &st,
|
||||
Fn<void(bool maximized)> maximize = nullptr);
|
||||
|
||||
void setStyle(const style::WindowTitle &st);
|
||||
[[nodiscard]] not_null<const style::WindowTitle*> st() const;
|
||||
[[nodiscard]] QRect geometry() const;
|
||||
void setResizeEnabled(bool enabled);
|
||||
void raise();
|
||||
|
||||
enum class Control {
|
||||
Unknown,
|
||||
Minimize,
|
||||
Maximize,
|
||||
Close,
|
||||
};
|
||||
|
||||
struct Layout {
|
||||
std::vector<Control> left;
|
||||
std::vector<Control> right;
|
||||
};
|
||||
|
||||
private:
|
||||
[[nodiscard]] not_null<RpWidget*> parent() const;
|
||||
[[nodiscard]] not_null<QWidget*> window() const;
|
||||
[[nodiscard]] Ui::IconButton *controlWidget(Control control) const;
|
||||
|
||||
void init(Fn<void(bool maximized)> maximize);
|
||||
void updateButtonsState();
|
||||
void updateControlsPosition();
|
||||
void updateControlsPositionBySide(
|
||||
const std::vector<Control> &controls,
|
||||
bool right);
|
||||
void handleWindowStateChanged(Qt::WindowState state = Qt::WindowNoState);
|
||||
|
||||
not_null<const style::WindowTitle*> _st;
|
||||
|
||||
object_ptr<Ui::IconButton> _minimize;
|
||||
object_ptr<Ui::IconButton> _maximizeRestore;
|
||||
object_ptr<Ui::IconButton> _close;
|
||||
|
||||
bool _maximizedState = false;
|
||||
bool _activeState = false;
|
||||
bool _resizeEnabled = true;
|
||||
|
||||
};
|
||||
|
||||
class DefaultTitleWidget : public RpWidget {
|
||||
public:
|
||||
explicit DefaultTitleWidget(not_null<RpWidget*> parent);
|
||||
|
||||
[[nodiscard]] not_null<const style::WindowTitle*> st() const;
|
||||
void setText(const QString &text);
|
||||
void setStyle(const style::WindowTitle &st);
|
||||
void setResizeEnabled(bool enabled);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
void mouseDoubleClickEvent(QMouseEvent *e) override;
|
||||
|
||||
private:
|
||||
TitleControls _controls;
|
||||
object_ptr<Ui::PlainShadow> _shadow;
|
||||
bool _mousePressed = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
|
|
@ -6,10 +6,11 @@
|
|||
//
|
||||
#include "ui/platform/win/ui_utility_win.h"
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
#include "base/platform/win/base_windows_h.h"
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtGui/QWindow>
|
||||
|
||||
namespace Ui {
|
||||
namespace Platform {
|
||||
|
||||
|
|
@ -44,5 +45,27 @@ void IgnoreAllActivation(not_null<QWidget*> widget) {
|
|||
ShowWindow(handle, SW_SHOW);
|
||||
}
|
||||
|
||||
bool ShowWindowMenu(QWindow *window) {
|
||||
const auto pos = QCursor::pos();
|
||||
|
||||
SendMessage(
|
||||
HWND(window->winId()),
|
||||
WM_SYSCOMMAND,
|
||||
SC_MOUSEMENU,
|
||||
MAKELPARAM(pos.x(), pos.y()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TitleControls::Layout TitleControlsLayout() {
|
||||
return TitleControls::Layout{
|
||||
.right = {
|
||||
TitleControls::Control::Minimize,
|
||||
TitleControls::Control::Maximize,
|
||||
TitleControls::Control::Close,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
//
|
||||
#pragma once
|
||||
|
||||
#include "ui/platform/ui_platform_utility.h"
|
||||
|
||||
#include <QtCore/QPoint>
|
||||
|
||||
class QPainter;
|
||||
|
|
@ -40,5 +42,17 @@ inline constexpr bool UseMainQueueGeneric() {
|
|||
return true;
|
||||
}
|
||||
|
||||
inline bool WindowExtentsSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool SetWindowExtents(QWindow *window, const QMargins &extents) {
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool UnsetWindowExtents(QWindow *window) {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
|
|
|
|||
|
|
@ -19,170 +19,6 @@
|
|||
namespace Ui {
|
||||
namespace Platform {
|
||||
|
||||
TitleControls::TitleControls(
|
||||
not_null<RpWidget*> parent,
|
||||
const style::WindowTitle &st,
|
||||
Fn<void(bool maximized)> maximize)
|
||||
: _st(&st)
|
||||
, _minimize(parent, _st->minimize)
|
||||
, _maximizeRestore(parent, _st->maximize)
|
||||
, _close(parent, _st->close)
|
||||
, _maximizedState(parent->windowState()
|
||||
& (Qt::WindowMaximized | Qt::WindowFullScreen))
|
||||
, _activeState(parent->isActiveWindow()) {
|
||||
init(std::move(maximize));
|
||||
|
||||
_close->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto active = window()->isActiveWindow();
|
||||
if (_activeState != active) {
|
||||
_activeState = active;
|
||||
updateButtonsState();
|
||||
}
|
||||
}, _close->lifetime());
|
||||
}
|
||||
|
||||
void TitleControls::setStyle(const style::WindowTitle &st) {
|
||||
_st = &st;
|
||||
updateButtonsState();
|
||||
}
|
||||
|
||||
not_null<const style::WindowTitle*> TitleControls::st() const {
|
||||
return _st;
|
||||
}
|
||||
|
||||
QRect TitleControls::geometry() const {
|
||||
auto result = QRect();
|
||||
const auto add = [&](auto &&control) {
|
||||
if (!control->isHidden()) {
|
||||
result = result.united(control->geometry());
|
||||
}
|
||||
};
|
||||
add(_minimize);
|
||||
add(_maximizeRestore);
|
||||
add(_close);
|
||||
return result;
|
||||
}
|
||||
|
||||
not_null<RpWidget*> TitleControls::parent() const {
|
||||
return static_cast<RpWidget*>(_close->parentWidget());
|
||||
}
|
||||
|
||||
not_null<QWidget*> TitleControls::window() const {
|
||||
return _close->window();
|
||||
}
|
||||
|
||||
void TitleControls::init(Fn<void(bool maximized)> maximize) {
|
||||
_minimize->setClickedCallback([=] {
|
||||
window()->setWindowState(
|
||||
window()->windowState() | Qt::WindowMinimized);
|
||||
_minimize->clearState();
|
||||
});
|
||||
_minimize->setPointerCursor(false);
|
||||
_maximizeRestore->setClickedCallback([=] {
|
||||
if (maximize) {
|
||||
maximize(!_maximizedState);
|
||||
} else {
|
||||
window()->setWindowState(_maximizedState
|
||||
? Qt::WindowNoState
|
||||
: Qt::WindowMaximized);
|
||||
}
|
||||
_maximizeRestore->clearState();
|
||||
});
|
||||
_maximizeRestore->setPointerCursor(false);
|
||||
_close->setClickedCallback([=] {
|
||||
window()->close();
|
||||
_close->clearState();
|
||||
});
|
||||
_close->setPointerCursor(false);
|
||||
|
||||
parent()->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
updateControlsPosition();
|
||||
}, _close->lifetime());
|
||||
|
||||
window()->createWinId();
|
||||
QObject::connect(
|
||||
window()->windowHandle(),
|
||||
&QWindow::windowStateChanged,
|
||||
[=](Qt::WindowState state) { handleWindowStateChanged(state); });
|
||||
_activeState = parent()->isActiveWindow();
|
||||
updateButtonsState();
|
||||
}
|
||||
|
||||
void TitleControls::setResizeEnabled(bool enabled) {
|
||||
_resizeEnabled = enabled;
|
||||
updateControlsVisibility();
|
||||
}
|
||||
|
||||
void TitleControls::raise() {
|
||||
_minimize->raise();
|
||||
_maximizeRestore->raise();
|
||||
_close->raise();
|
||||
}
|
||||
|
||||
void TitleControls::updateControlsPosition() {
|
||||
auto right = 0;
|
||||
_close->moveToRight(right, 0); right += _close->width();
|
||||
_maximizeRestore->moveToRight(right, 0);
|
||||
if (_resizeEnabled) {
|
||||
right += _maximizeRestore->width();
|
||||
}
|
||||
_minimize->moveToRight(right, 0);
|
||||
}
|
||||
|
||||
void TitleControls::updateControlsVisibility() {
|
||||
_maximizeRestore->setVisible(_resizeEnabled);
|
||||
updateControlsPosition();
|
||||
}
|
||||
|
||||
void TitleControls::handleWindowStateChanged(Qt::WindowState state) {
|
||||
if (state == Qt::WindowMinimized) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto maximized = (state == Qt::WindowMaximized)
|
||||
|| (state == Qt::WindowFullScreen);
|
||||
if (_maximizedState != maximized) {
|
||||
_maximizedState = maximized;
|
||||
updateButtonsState();
|
||||
}
|
||||
}
|
||||
|
||||
void TitleControls::updateButtonsState() {
|
||||
const auto minimize = _activeState
|
||||
? &_st->minimizeIconActive
|
||||
: &_st->minimize.icon;
|
||||
const auto minimizeOver = _activeState
|
||||
? &_st->minimizeIconActiveOver
|
||||
: &_st->minimize.iconOver;
|
||||
_minimize->setIconOverride(minimize, minimizeOver);
|
||||
if (_maximizedState) {
|
||||
const auto restore = _activeState
|
||||
? &_st->restoreIconActive
|
||||
: &_st->restoreIcon;
|
||||
const auto restoreOver = _activeState
|
||||
? &_st->restoreIconActiveOver
|
||||
: &_st->restoreIconOver;
|
||||
_maximizeRestore->setIconOverride(restore, restoreOver);
|
||||
} else {
|
||||
const auto maximize = _activeState
|
||||
? &_st->maximizeIconActive
|
||||
: &_st->maximize.icon;
|
||||
const auto maximizeOver = _activeState
|
||||
? &_st->maximizeIconActiveOver
|
||||
: &_st->maximize.iconOver;
|
||||
_maximizeRestore->setIconOverride(maximize, maximizeOver);
|
||||
}
|
||||
const auto close = _activeState
|
||||
? &_st->closeIconActive
|
||||
: &_st->close.icon;
|
||||
const auto closeOver = _activeState
|
||||
? &_st->closeIconActiveOver
|
||||
: &_st->close.iconOver;
|
||||
_close->setIconOverride(close, closeOver);
|
||||
}
|
||||
|
||||
TitleWidget::TitleWidget(not_null<RpWidget*> parent)
|
||||
: RpWidget(parent)
|
||||
, _controls(this, st::defaultWindowTitle)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
#pragma once
|
||||
|
||||
#include "ui/platform/ui_platform_window_title.h"
|
||||
#include "ui/rp_widget.h"
|
||||
#include "base/object_ptr.h"
|
||||
|
||||
|
|
@ -38,41 +39,6 @@ enum class HitTestResult {
|
|||
TopLeft,
|
||||
};
|
||||
|
||||
class TitleControls final {
|
||||
public:
|
||||
TitleControls(
|
||||
not_null<RpWidget*> parent,
|
||||
const style::WindowTitle &st,
|
||||
Fn<void(bool maximized)> maximize = nullptr);
|
||||
|
||||
void setStyle(const style::WindowTitle &st);
|
||||
[[nodiscard]] not_null<const style::WindowTitle*> st() const;
|
||||
[[nodiscard]] QRect geometry() const;
|
||||
void setResizeEnabled(bool enabled);
|
||||
void raise();
|
||||
|
||||
private:
|
||||
[[nodiscard]] not_null<RpWidget*> parent() const;
|
||||
[[nodiscard]] not_null<QWidget*> window() const;
|
||||
|
||||
void init(Fn<void(bool maximized)> maximize);
|
||||
void updateControlsVisibility();
|
||||
void updateButtonsState();
|
||||
void updateControlsPosition();
|
||||
void handleWindowStateChanged(Qt::WindowState state = Qt::WindowNoState);
|
||||
|
||||
not_null<const style::WindowTitle*> _st;
|
||||
|
||||
object_ptr<Ui::IconButton> _minimize;
|
||||
object_ptr<Ui::IconButton> _maximizeRestore;
|
||||
object_ptr<Ui::IconButton> _close;
|
||||
|
||||
bool _maximizedState = false;
|
||||
bool _activeState = false;
|
||||
bool _resizeEnabled = true;
|
||||
|
||||
};
|
||||
|
||||
class TitleWidget : public RpWidget {
|
||||
public:
|
||||
explicit TitleWidget(not_null<RpWidget*> parent);
|
||||
|
|
|
|||
|
|
@ -160,8 +160,10 @@ bool RpWidgetMethods::handleEvent(QEvent *event) {
|
|||
return eventHook(event);
|
||||
}
|
||||
|
||||
RpWidgetMethods::Initer::Initer(QWidget *parent) {
|
||||
parent->setGeometry(0, 0, 0, 0);
|
||||
RpWidgetMethods::Initer::Initer(QWidget *parent, bool setZeroGeometry) {
|
||||
if (setZeroGeometry) {
|
||||
parent->setGeometry(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void RpWidgetMethods::visibilityChangedHook(bool wasVisible, bool nowVisible) {
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ using RpWidgetParent = std::conditional_t<
|
|||
TWidget,
|
||||
TWidgetHelper<Widget>>;
|
||||
|
||||
template <typename Widget>
|
||||
template <typename Widget, typename Traits>
|
||||
class RpWidgetWrap;
|
||||
|
||||
class RpWidgetMethods {
|
||||
|
|
@ -281,7 +281,7 @@ protected:
|
|||
virtual bool eventHook(QEvent *event) = 0;
|
||||
|
||||
private:
|
||||
template <typename Widget>
|
||||
template <typename Widget, typename Traits>
|
||||
friend class RpWidgetWrap;
|
||||
|
||||
struct EventStreams {
|
||||
|
|
@ -292,7 +292,7 @@ private:
|
|||
rpl::event_stream<> alive;
|
||||
};
|
||||
struct Initer {
|
||||
Initer(QWidget *parent);
|
||||
Initer(QWidget *parent, bool setZeroGeometry);
|
||||
};
|
||||
|
||||
virtual void callSetVisible(bool visible) = 0;
|
||||
|
|
@ -310,11 +310,15 @@ private:
|
|||
|
||||
};
|
||||
|
||||
template <typename Widget>
|
||||
struct RpWidgetDefaultTraits {
|
||||
static constexpr bool kSetZeroGeometry = true;
|
||||
};
|
||||
|
||||
template <typename Widget, typename Traits = RpWidgetDefaultTraits>
|
||||
class RpWidgetWrap
|
||||
: public RpWidgetParent<Widget>
|
||||
, public RpWidgetMethods {
|
||||
using Self = RpWidgetWrap<Widget>;
|
||||
using Self = RpWidgetWrap<Widget, Traits>;
|
||||
using Parent = RpWidgetParent<Widget>;
|
||||
|
||||
public:
|
||||
|
|
@ -362,7 +366,7 @@ private:
|
|||
return this->isHidden();
|
||||
}
|
||||
|
||||
Initer _initer = { this };
|
||||
Initer _initer = { this, Traits::kSetZeroGeometry };
|
||||
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -79,6 +79,10 @@ RippleButton::RippleButton(QWidget *parent, const style::RippleAnimation &st)
|
|||
|
||||
void RippleButton::clearState() {
|
||||
AbstractButton::clearState();
|
||||
finishAnimating();
|
||||
}
|
||||
|
||||
void RippleButton::finishAnimating() {
|
||||
if (_ripple) {
|
||||
_ripple.reset();
|
||||
update();
|
||||
|
|
@ -116,6 +120,13 @@ void RippleButton::setForceRippled(
|
|||
update();
|
||||
}
|
||||
|
||||
void RippleButton::paintRipple(
|
||||
QPainter &p,
|
||||
const QPoint &point,
|
||||
const QColor *colorOverride) {
|
||||
paintRipple(p, point.x(), point.y(), colorOverride);
|
||||
}
|
||||
|
||||
void RippleButton::paintRipple(QPainter &p, int x, int y, const QColor *colorOverride) {
|
||||
if (_ripple) {
|
||||
_ripple->paint(p, x, y, width(), colorOverride);
|
||||
|
|
@ -195,6 +206,11 @@ void FlatButton::setWidth(int w) {
|
|||
resize(_width, height());
|
||||
}
|
||||
|
||||
void FlatButton::setColorOverride(std::optional<QColor> color) {
|
||||
_colorOverride = color;
|
||||
update();
|
||||
}
|
||||
|
||||
int32 FlatButton::textWidth() const {
|
||||
return _st.font->width(_text);
|
||||
}
|
||||
|
|
@ -214,7 +230,11 @@ void FlatButton::paintEvent(QPaintEvent *e) {
|
|||
|
||||
p.setFont(isOver() ? _st.overFont : _st.font);
|
||||
p.setRenderHint(QPainter::TextAntialiasing);
|
||||
p.setPen(isOver() ? _st.overColor : _st.color);
|
||||
if (_colorOverride) {
|
||||
p.setPen(*_colorOverride);
|
||||
} else {
|
||||
p.setPen(isOver() ? _st.overColor : _st.color);
|
||||
}
|
||||
|
||||
const auto textRect = inner.marginsRemoved(
|
||||
_textMargins
|
||||
|
|
@ -361,7 +381,7 @@ void RoundButton::paintEvent(QPaintEvent *e) {
|
|||
drawRect(_roundRectOver);
|
||||
}
|
||||
|
||||
paintRipple(p, rounded.x(), rounded.y());
|
||||
paintRipple(p, rounded.topLeft());
|
||||
|
||||
p.setFont(_st.font);
|
||||
const auto textTop = _st.padding.top() + _st.textTop;
|
||||
|
|
@ -392,7 +412,10 @@ void RoundButton::paintEvent(QPaintEvent *e) {
|
|||
_numbers->paint(p, textLeft, textTop, width());
|
||||
}
|
||||
if (!_st.icon.empty()) {
|
||||
_st.icon.paint(p, QPoint(iconLeft, iconTop), width());
|
||||
const auto ¤t = ((over || down) && !_st.iconOver.empty())
|
||||
? _st.iconOver
|
||||
: _st.icon;
|
||||
current.paint(p, QPoint(iconLeft, iconTop), width());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -431,7 +454,7 @@ void IconButton::setRippleColorOverride(const style::color *colorOverride) {
|
|||
void IconButton::paintEvent(QPaintEvent *e) {
|
||||
Painter p(this);
|
||||
|
||||
paintRipple(p, _st.rippleAreaPosition.x(), _st.rippleAreaPosition.y(), _rippleColorOverride ? &(*_rippleColorOverride)->c : nullptr);
|
||||
paintRipple(p, _st.rippleAreaPosition, _rippleColorOverride ? &(*_rippleColorOverride)->c : nullptr);
|
||||
|
||||
auto down = isDown();
|
||||
auto overIconOpacity = (down || forceRippled()) ? 1. : _a_over.value(isOver() ? 1. : 0.);
|
||||
|
|
@ -547,7 +570,7 @@ void CrossButton::paintEvent(QPaintEvent *e) {
|
|||
auto shown = _showAnimation.value(_shown ? 1. : 0.);
|
||||
p.setOpacity(shown);
|
||||
|
||||
paintRipple(p, _st.crossPosition.x(), _st.crossPosition.y());
|
||||
paintRipple(p, _st.crossPosition);
|
||||
|
||||
auto loading = 0.;
|
||||
if (_loadingAnimation.animating()) {
|
||||
|
|
|
|||
|
|
@ -58,7 +58,17 @@ public:
|
|||
|
||||
void clearState() override;
|
||||
|
||||
void paintRipple(QPainter &p, int x, int y, const QColor *colorOverride = nullptr);
|
||||
void paintRipple(
|
||||
QPainter &p,
|
||||
const QPoint &point,
|
||||
const QColor *colorOverride = nullptr);
|
||||
void paintRipple(
|
||||
QPainter &p,
|
||||
int x,
|
||||
int y,
|
||||
const QColor *colorOverride = nullptr);
|
||||
|
||||
void finishAnimating();
|
||||
|
||||
~RippleButton();
|
||||
|
||||
|
|
@ -84,6 +94,7 @@ public:
|
|||
|
||||
void setText(const QString &text);
|
||||
void setWidth(int w);
|
||||
void setColorOverride(std::optional<QColor> color);
|
||||
void setTextMargins(QMargins margins);
|
||||
|
||||
int32 textWidth() const;
|
||||
|
|
@ -97,6 +108,7 @@ private:
|
|||
QString _text;
|
||||
QMargins _textMargins;
|
||||
int _width = 0;
|
||||
std::optional<QColor> _colorOverride;
|
||||
|
||||
const style::FlatButton &_st;
|
||||
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ void CallButton::paintEvent(QPaintEvent *e) {
|
|||
} else {
|
||||
rippleColorInterpolated = anim::color(_stFrom->button.ripple.color, _stTo->button.ripple.color, _progress);
|
||||
}
|
||||
paintRipple(p, _stFrom->button.rippleAreaPosition.x(), _stFrom->button.rippleAreaPosition.y(), rippleColorOverride);
|
||||
paintRipple(p, _stFrom->button.rippleAreaPosition, rippleColorOverride);
|
||||
|
||||
auto positionFrom = iconPosition(_stFrom);
|
||||
if (paintFrom) {
|
||||
|
|
|
|||
|
|
@ -328,6 +328,15 @@ BlobsWidget::BlobsWidget(
|
|||
void BlobsWidget::init() {
|
||||
setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
const auto cutRect = [](Painter &p, const QRectF &r) {
|
||||
p.save();
|
||||
p.setOpacity(1.);
|
||||
p.setBrush(st::groupCallBg);
|
||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
p.drawEllipse(r);
|
||||
p.restore();
|
||||
};
|
||||
|
||||
{
|
||||
const auto s = _blobs.maxRadius() * 2 * kGlowPaddingFactor;
|
||||
resize(s, s);
|
||||
|
|
@ -351,6 +360,8 @@ void BlobsWidget::init() {
|
|||
Painter p(this);
|
||||
PainterHighQualityEnabler hq(p);
|
||||
|
||||
p.setPen(Qt::NoPen);
|
||||
|
||||
// Glow.
|
||||
const auto s = kGlowMinScale
|
||||
+ (1. - kGlowMinScale) * _blobs.currentLevel();
|
||||
|
|
@ -369,37 +380,41 @@ void BlobsWidget::init() {
|
|||
_switchConnectingProgress / kBlobPartAnimation)))
|
||||
: _blobsScaleEnter;
|
||||
_blobs.paint(p, _blobBrush, scale);
|
||||
p.translate(-_center, -_center);
|
||||
|
||||
if (scale < 1.) {
|
||||
cutRect(p, _circleRect);
|
||||
}
|
||||
|
||||
// Main circle.
|
||||
p.translate(-_center, -_center);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(_blobBrush);
|
||||
p.drawEllipse(_circleRect);
|
||||
const auto circleProgress =
|
||||
Clamp(_switchConnectingProgress - kBlobPartAnimation)
|
||||
/ kFillCirclePartAnimation;
|
||||
const auto skipColoredCircle = (circleProgress == 1.);
|
||||
|
||||
if (!skipColoredCircle) {
|
||||
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);
|
||||
if (!skipColoredCircle) {
|
||||
p.setBrush(st::callConnectingRadial.color);
|
||||
p.setOpacity(circleProgress);
|
||||
p.drawEllipse(_circleRect);
|
||||
}
|
||||
|
||||
p.setOpacity(1.);
|
||||
|
||||
cutRect(p, cutOutRect);
|
||||
|
||||
p.setBrush(st::callIconBg);
|
||||
|
||||
p.save();
|
||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
p.drawEllipse(cutOutRect);
|
||||
p.restore();
|
||||
|
||||
p.drawEllipse(cutOutRect);
|
||||
}
|
||||
}, lifetime());
|
||||
|
|
@ -553,25 +568,33 @@ void CallMuteButton::init() {
|
|||
}, _centerLabel->lifetime());
|
||||
_centerLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
_radialInfo.rawShowProgress.value(
|
||||
) | rpl::start_with_next([=](float64 value) {
|
||||
rpl::combine(
|
||||
_radialInfo.rawShowProgress.value(),
|
||||
anim::Disables()
|
||||
) | rpl::start_with_next([=](float64 value, bool disabled) {
|
||||
auto &info = _radialInfo;
|
||||
info.realShowProgress = (1. - value) / kRadialEndPartAnimation;
|
||||
|
||||
if (((value == 0.) || anim::Disabled()) && _radial) {
|
||||
const auto guard = gsl::finally([&] {
|
||||
_content->update();
|
||||
});
|
||||
|
||||
if (((value == 0.) || disabled) && _radial) {
|
||||
_radial->stop();
|
||||
_radial = nullptr;
|
||||
return;
|
||||
}
|
||||
if ((value > 0.) && !anim::Disabled() && !_radial) {
|
||||
if ((value > 0.) && !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();
|
||||
if (_radial) {
|
||||
_radial->stop(anim::type::instant);
|
||||
_radial->start();
|
||||
}
|
||||
info.state = std::nullopt;
|
||||
return;
|
||||
}
|
||||
|
|
@ -721,6 +744,8 @@ void CallMuteButton::init() {
|
|||
const auto to = r.arcFrom - kRadialFinishArcShift;
|
||||
ComputeRadialFinish(r.arcFrom, radialProgress, to);
|
||||
ComputeRadialFinish(r.arcLength, radialProgress);
|
||||
} else {
|
||||
r.arcLength = RadialState::kFull;
|
||||
}
|
||||
|
||||
const auto opacity = (radialProgress > kOverlapProgressRadialHide)
|
||||
|
|
|
|||
|
|
@ -320,7 +320,8 @@ void RadioView::paint(Painter &p, int left, int top, int outerWidth) {
|
|||
p.setBrush(_st->bg);
|
||||
//int32 skip = qCeil(_st->thickness / 2.);
|
||||
//p.drawEllipse(_checkRect.marginsRemoved(QMargins(skip, skip, skip, skip)));
|
||||
p.drawEllipse(style::rtlrect(QRectF(left, top, _st->diameter, _st->diameter).marginsRemoved(QMarginsF(_st->thickness / 2., _st->thickness / 2., _st->thickness / 2., _st->thickness / 2.)), outerWidth));
|
||||
const auto skip = (_st->outerSkip / 10.) + (_st->thickness / 2);
|
||||
p.drawEllipse(style::rtlrect(QRectF(left, top, _st->diameter, _st->diameter).marginsRemoved(QMarginsF(skip, skip, skip, skip)), outerWidth));
|
||||
|
||||
if (toggled > 0) {
|
||||
p.setPen(Qt::NoPen);
|
||||
|
|
@ -332,7 +333,7 @@ void RadioView::paint(Painter &p, int left, int top, int outerWidth) {
|
|||
? anim::brush(*_untoggledOverride, _st->toggledFg, toggled)
|
||||
: anim::brush(_st->untoggledFg, _st->toggledFg, toggled)));
|
||||
|
||||
auto skip0 = _st->diameter / 2., skip1 = _st->skip / 10., checkSkip = skip0 * (1. - toggled) + skip1 * toggled;
|
||||
const auto skip0 = _st->diameter / 2., skip1 = _st->skip / 10., checkSkip = skip0 * (1. - toggled) + skip1 * toggled;
|
||||
p.drawEllipse(style::rtlrect(QRectF(left, top, _st->diameter, _st->diameter).marginsRemoved(QMarginsF(checkSkip, checkSkip, checkSkip, checkSkip)), outerWidth));
|
||||
//int32 fskip = qFloor(checkSkip), cskip = qCeil(checkSkip);
|
||||
//if (2 * fskip < _checkRect.width()) {
|
||||
|
|
@ -569,11 +570,7 @@ void Checkbox::paintEvent(QPaintEvent *e) {
|
|||
p.setOpacity(_st.disabledOpacity);
|
||||
} else {
|
||||
auto color = anim::color(_st.rippleBg, _st.rippleBgActive, active);
|
||||
paintRipple(
|
||||
p,
|
||||
check.x() + _st.rippleAreaPosition.x(),
|
||||
check.y() + _st.rippleAreaPosition.y(),
|
||||
&color);
|
||||
paintRipple(p, check.topLeft() + _st.rippleAreaPosition, &color);
|
||||
}
|
||||
|
||||
auto realCheckRect = myrtlrect(check);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ namespace Ui {
|
|||
|
||||
DropdownMenu::DropdownMenu(QWidget *parent, const style::DropdownMenu &st) : InnerDropdown(parent, st.wrap)
|
||||
, _st(st) {
|
||||
_menu = setOwnedWidget(object_ptr<Menu>(this, _st.menu));
|
||||
_menu = setOwnedWidget(object_ptr<Menu::Menu>(this, _st.menu));
|
||||
init();
|
||||
}
|
||||
|
||||
|
|
@ -33,12 +33,15 @@ DropdownMenu::DropdownMenu(QWidget *parent, const style::DropdownMenu &st) : Inn
|
|||
void DropdownMenu::init() {
|
||||
InnerDropdown::setHiddenCallback([this] { hideFinish(); });
|
||||
|
||||
_menu->setResizedCallback([this] { resizeToContent(); });
|
||||
_menu->setActivatedCallback([this](QAction *action, int actionTop, TriggeredSource source) {
|
||||
handleActivated(action, actionTop, source);
|
||||
_menu->resizesFromInner(
|
||||
) | rpl::start_with_next([=] {
|
||||
resizeToContent();
|
||||
}, _menu->lifetime());
|
||||
_menu->setActivatedCallback([this](const Menu::CallbackData &data) {
|
||||
handleActivated(data);
|
||||
});
|
||||
_menu->setTriggeredCallback([this](QAction *action, int actionTop, TriggeredSource source) {
|
||||
handleTriggered(action, actionTop, source);
|
||||
_menu->setTriggeredCallback([this](const Menu::CallbackData &data) {
|
||||
handleTriggered(data);
|
||||
});
|
||||
_menu->setKeyPressDelegate([this](int key) { return handleKeyPress(key); });
|
||||
_menu->setMouseMoveDelegate([this](QPoint globalPosition) { handleMouseMove(globalPosition); });
|
||||
|
|
@ -50,8 +53,9 @@ void DropdownMenu::init() {
|
|||
hide();
|
||||
}
|
||||
|
||||
not_null<QAction*> DropdownMenu::addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon, const style::icon *iconOver) {
|
||||
return _menu->addAction(text, receiver, member, icon, iconOver);
|
||||
not_null<QAction*> DropdownMenu::addAction(
|
||||
base::unique_qptr<Menu::ItemBase> widget) {
|
||||
return _menu->addAction(std::move(widget));
|
||||
}
|
||||
|
||||
not_null<QAction*> DropdownMenu::addAction(const QString &text, Fn<void()> callback, const style::icon *icon, const style::icon *iconOver) {
|
||||
|
|
@ -73,9 +77,13 @@ const std::vector<not_null<QAction*>> &DropdownMenu::actions() const {
|
|||
return _menu->actions();
|
||||
}
|
||||
|
||||
void DropdownMenu::handleActivated(QAction *action, int actionTop, TriggeredSource source) {
|
||||
if (source == TriggeredSource::Mouse) {
|
||||
if (!popupSubmenuFromAction(action, actionTop, source)) {
|
||||
bool DropdownMenu::empty() const {
|
||||
return _menu->empty();
|
||||
}
|
||||
|
||||
void DropdownMenu::handleActivated(const Menu::CallbackData &data) {
|
||||
if (data.source == TriggeredSource::Mouse) {
|
||||
if (!popupSubmenuFromAction(data)) {
|
||||
if (auto currentSubmenu = base::take(_activeSubmenu)) {
|
||||
currentSubmenu->hideMenu(true);
|
||||
}
|
||||
|
|
@ -83,11 +91,11 @@ void DropdownMenu::handleActivated(QAction *action, int actionTop, TriggeredSour
|
|||
}
|
||||
}
|
||||
|
||||
void DropdownMenu::handleTriggered(QAction *action, int actionTop, TriggeredSource source) {
|
||||
if (!popupSubmenuFromAction(action, actionTop, source)) {
|
||||
void DropdownMenu::handleTriggered(const Menu::CallbackData &data) {
|
||||
if (!popupSubmenuFromAction(data)) {
|
||||
hideMenu();
|
||||
_triggering = true;
|
||||
emit action->trigger();
|
||||
emit data.action->trigger();
|
||||
_triggering = false;
|
||||
if (_deleteLater) {
|
||||
_deleteLater = false;
|
||||
|
|
@ -97,7 +105,7 @@ void DropdownMenu::handleTriggered(QAction *action, int actionTop, TriggeredSour
|
|||
}
|
||||
|
||||
// Not ready with submenus yet.
|
||||
bool DropdownMenu::popupSubmenuFromAction(QAction *action, int actionTop, TriggeredSource source) {
|
||||
bool DropdownMenu::popupSubmenuFromAction(const Menu::CallbackData &data) {
|
||||
//if (auto submenu = _submenus.value(action)) {
|
||||
// if (_activeSubmenu == submenu) {
|
||||
// submenu->hideMenu(true);
|
||||
|
|
@ -126,9 +134,9 @@ bool DropdownMenu::popupSubmenuFromAction(QAction *action, int actionTop, Trigge
|
|||
// }
|
||||
//}
|
||||
|
||||
void DropdownMenu::forwardKeyPress(int key) {
|
||||
if (!handleKeyPress(key)) {
|
||||
_menu->handleKeyPress(key);
|
||||
void DropdownMenu::forwardKeyPress(not_null<QKeyEvent*> e) {
|
||||
if (!handleKeyPress(e->key())) {
|
||||
_menu->handleKeyPress(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -185,7 +193,7 @@ void DropdownMenu::hideEvent(QHideEvent *e) {
|
|||
}
|
||||
|
||||
void DropdownMenu::keyPressEvent(QKeyEvent *e) {
|
||||
forwardKeyPress(e->key());
|
||||
forwardKeyPress(e);
|
||||
}
|
||||
|
||||
void DropdownMenu::mouseMoveEvent(QMouseEvent *e) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
#include "styles/style_widgets.h"
|
||||
#include "ui/widgets/inner_dropdown.h"
|
||||
#include "ui/widgets/menu.h"
|
||||
#include "ui/widgets/menu/menu.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ class DropdownMenu : public InnerDropdown {
|
|||
public:
|
||||
DropdownMenu(QWidget *parent, const style::DropdownMenu &st = st::defaultDropdownMenu);
|
||||
|
||||
not_null<QAction*> addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr);
|
||||
not_null<QAction*> addAction(base::unique_qptr<Menu::ItemBase> widget);
|
||||
not_null<QAction*> addAction(const QString &text, Fn<void()> callback, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr);
|
||||
not_null<QAction*> addSeparator();
|
||||
void clearActions();
|
||||
|
|
@ -28,6 +28,7 @@ public:
|
|||
}
|
||||
|
||||
const std::vector<not_null<QAction*>> &actions() const;
|
||||
bool empty() const;
|
||||
|
||||
~DropdownMenu();
|
||||
|
||||
|
|
@ -56,9 +57,9 @@ private:
|
|||
void hideFinish();
|
||||
|
||||
using TriggeredSource = Menu::TriggeredSource;
|
||||
void handleActivated(QAction *action, int actionTop, TriggeredSource source);
|
||||
void handleTriggered(QAction *action, int actionTop, TriggeredSource source);
|
||||
void forwardKeyPress(int key);
|
||||
void handleActivated(const Menu::CallbackData &data);
|
||||
void handleTriggered(const Menu::CallbackData &data);
|
||||
void forwardKeyPress(not_null<QKeyEvent*> e);
|
||||
bool handleKeyPress(int key);
|
||||
void forwardMouseMove(QPoint globalPosition) {
|
||||
_menu->handleMouseMove(globalPosition);
|
||||
|
|
@ -74,14 +75,14 @@ private:
|
|||
void handleMouseRelease(QPoint globalPosition);
|
||||
|
||||
using SubmenuPointer = QPointer<DropdownMenu>;
|
||||
bool popupSubmenuFromAction(QAction *action, int actionTop, TriggeredSource source);
|
||||
bool popupSubmenuFromAction(const Menu::CallbackData &data);
|
||||
void popupSubmenu(SubmenuPointer submenu, int actionTop, TriggeredSource source);
|
||||
void showMenu(const QPoint &p, DropdownMenu *parent, TriggeredSource source);
|
||||
|
||||
const style::DropdownMenu &_st;
|
||||
Fn<void()> _hiddenCallback;
|
||||
|
||||
QPointer<Menu> _menu;
|
||||
QPointer<Menu::Menu> _menu;
|
||||
|
||||
// Not ready with submenus yet.
|
||||
//using Submenus = QMap<QAction*, SubmenuPointer>;
|
||||
|
|
|
|||
|
|
@ -2659,12 +2659,18 @@ bool InputField::ShouldSubmit(
|
|||
}
|
||||
|
||||
void InputField::keyPressEventInner(QKeyEvent *e) {
|
||||
bool shift = e->modifiers().testFlag(Qt::ShiftModifier), alt = e->modifiers().testFlag(Qt::AltModifier);
|
||||
bool macmeta = Platform::IsMac() && e->modifiers().testFlag(Qt::ControlModifier) && !e->modifiers().testFlag(Qt::MetaModifier) && !e->modifiers().testFlag(Qt::AltModifier);
|
||||
bool ctrl = e->modifiers().testFlag(Qt::ControlModifier) || e->modifiers().testFlag(Qt::MetaModifier);
|
||||
bool enterSubmit = (_mode != Mode::MultiLine)
|
||||
const auto shift = e->modifiers().testFlag(Qt::ShiftModifier);
|
||||
const auto alt = e->modifiers().testFlag(Qt::AltModifier);
|
||||
const auto macmeta = Platform::IsMac()
|
||||
&& e->modifiers().testFlag(Qt::ControlModifier)
|
||||
&& !e->modifiers().testFlag(Qt::MetaModifier)
|
||||
&& !e->modifiers().testFlag(Qt::AltModifier);
|
||||
const auto ctrl = e->modifiers().testFlag(Qt::ControlModifier)
|
||||
|| e->modifiers().testFlag(Qt::MetaModifier);
|
||||
const auto enterSubmit = (_mode != Mode::MultiLine)
|
||||
|| ShouldSubmit(_submitSettings, e->modifiers());
|
||||
bool enter = (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return);
|
||||
const auto enter = (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return);
|
||||
const auto backspace = (e->key() == Qt::Key_Backspace);
|
||||
if (e->key() == Qt::Key_Left
|
||||
|| e->key() == Qt::Key_Right
|
||||
|| e->key() == Qt::Key_Up
|
||||
|
|
@ -2674,12 +2680,12 @@ void InputField::keyPressEventInner(QKeyEvent *e) {
|
|||
_reverseMarkdownReplacement = false;
|
||||
}
|
||||
|
||||
if (macmeta && e->key() == Qt::Key_Backspace) {
|
||||
if (macmeta && backspace) {
|
||||
QTextCursor tc(textCursor()), start(tc);
|
||||
start.movePosition(QTextCursor::StartOfLine);
|
||||
tc.setPosition(start.position(), QTextCursor::KeepAnchor);
|
||||
tc.removeSelectedText();
|
||||
} else if (e->key() == Qt::Key_Backspace
|
||||
} else if (backspace
|
||||
&& e->modifiers() == 0
|
||||
&& revertFormatReplace()) {
|
||||
e->accept();
|
||||
|
|
@ -2716,12 +2722,22 @@ void InputField::keyPressEventInner(QKeyEvent *e) {
|
|||
} else {
|
||||
const auto text = e->text();
|
||||
const auto oldPosition = textCursor().position();
|
||||
if (enter && ctrl) {
|
||||
e->setModifiers(e->modifiers() & ~Qt::ControlModifier);
|
||||
} else if (enter && shift) {
|
||||
e->setModifiers(e->modifiers() & ~Qt::ShiftModifier);
|
||||
const auto oldModifiers = e->modifiers();
|
||||
const auto allowedModifiers = (enter && ctrl)
|
||||
? (~Qt::ControlModifier)
|
||||
: (enter && shift)
|
||||
? (~Qt::ShiftModifier)
|
||||
: (backspace && Platform::IsLinux())
|
||||
? (Qt::ControlModifier)
|
||||
: oldModifiers;
|
||||
const auto changeModifiers = (oldModifiers & ~allowedModifiers) != 0;
|
||||
if (changeModifiers) {
|
||||
e->setModifiers(oldModifiers & allowedModifiers);
|
||||
}
|
||||
_inner->QTextEdit::keyPressEvent(e);
|
||||
if (changeModifiers) {
|
||||
e->setModifiers(oldModifiers);
|
||||
}
|
||||
auto cursor = textCursor();
|
||||
if (cursor.position() == oldPosition) {
|
||||
bool check = false;
|
||||
|
|
@ -4023,18 +4039,20 @@ PasswordInput::PasswordInput(
|
|||
setEchoMode(QLineEdit::Password);
|
||||
}
|
||||
|
||||
PortInput::PortInput(
|
||||
NumberInput::NumberInput(
|
||||
QWidget *parent,
|
||||
const style::InputField &st,
|
||||
rpl::producer<QString> placeholder,
|
||||
const QString &val)
|
||||
: MaskedInputField(parent, st, std::move(placeholder), val) {
|
||||
if (!val.toInt() || val.toInt() > 65535) {
|
||||
const QString &value,
|
||||
int limit)
|
||||
: MaskedInputField(parent, st, std::move(placeholder), value)
|
||||
, _limit(limit) {
|
||||
if (!value.toInt() || (limit > 0 && value.toInt() > limit)) {
|
||||
setText(QString());
|
||||
}
|
||||
}
|
||||
|
||||
void PortInput::correctValue(
|
||||
void NumberInput::correctValue(
|
||||
const QString &was,
|
||||
int wasCursor,
|
||||
QString &now,
|
||||
|
|
@ -4052,7 +4070,7 @@ void PortInput::correctValue(
|
|||
if (!newText.toInt()) {
|
||||
newText = QString();
|
||||
newPos = 0;
|
||||
} else if (newText.toInt() > 65535) {
|
||||
} else if (_limit > 0 && newText.toInt() > _limit) {
|
||||
newText = was;
|
||||
newPos = wasCursor;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -697,9 +697,14 @@ public:
|
|||
|
||||
};
|
||||
|
||||
class PortInput : public MaskedInputField {
|
||||
class NumberInput : public MaskedInputField {
|
||||
public:
|
||||
PortInput(QWidget *parent, const style::InputField &st, rpl::producer<QString> placeholder, const QString &val);
|
||||
NumberInput(
|
||||
QWidget *parent,
|
||||
const style::InputField &st,
|
||||
rpl::producer<QString> placeholder,
|
||||
const QString &value,
|
||||
int limit);
|
||||
|
||||
protected:
|
||||
void correctValue(
|
||||
|
|
@ -708,6 +713,9 @@ protected:
|
|||
QString &now,
|
||||
int &nowCursor) override;
|
||||
|
||||
private:
|
||||
int _limit = 0;
|
||||
|
||||
};
|
||||
|
||||
class HexInput : public MaskedInputField {
|
||||
|
|
|
|||
|
|
@ -637,12 +637,17 @@ void FlatLabel::showContextMenu(QContextMenuEvent *e, ContextMenuReason reason)
|
|||
_contextMenu = new PopupMenu(this);
|
||||
|
||||
if (fullSelection && !_contextCopyText.isEmpty()) {
|
||||
_contextMenu->addAction(_contextCopyText, this, SLOT(onCopyContextText()));
|
||||
_contextMenu->addAction(
|
||||
_contextCopyText,
|
||||
[=] { onCopyContextText(); });
|
||||
} else if (uponSelection && !fullSelection) {
|
||||
const auto text = Integration::Instance().phraseContextCopySelected();
|
||||
_contextMenu->addAction(text, this, SLOT(onCopySelectedText()));
|
||||
_contextMenu->addAction(
|
||||
Integration::Instance().phraseContextCopySelected(),
|
||||
[=] { onCopySelectedText(); });
|
||||
} else if (_selectable && !hasSelection && !_contextCopyText.isEmpty()) {
|
||||
_contextMenu->addAction(_contextCopyText, this, SLOT(onCopyContextText()));
|
||||
_contextMenu->addAction(
|
||||
_contextCopyText,
|
||||
[=] { onCopyContextText(); });
|
||||
}
|
||||
|
||||
if (const auto link = ClickHandler::getActive()) {
|
||||
|
|
|
|||
|
|
@ -1,523 +0,0 @@
|
|||
// 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/menu.h"
|
||||
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/text/text.h"
|
||||
|
||||
#include <QtGui/QtEvents>
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] TextWithEntities ParseMenuItem(const QString &text) {
|
||||
auto result = TextWithEntities();
|
||||
result.text.reserve(text.size());
|
||||
auto afterAmpersand = false;
|
||||
for (const auto ch : text) {
|
||||
if (afterAmpersand) {
|
||||
afterAmpersand = false;
|
||||
if (ch == '&') {
|
||||
result.text.append(ch);
|
||||
} else {
|
||||
result.entities.append(EntityInText{
|
||||
EntityType::Underline,
|
||||
result.text.size(),
|
||||
1 });
|
||||
result.text.append(ch);
|
||||
}
|
||||
} else if (ch == '&') {
|
||||
afterAmpersand = true;
|
||||
} else {
|
||||
result.text.append(ch);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
TextParseOptions MenuTextOptions = {
|
||||
TextParseLinks | TextParseRichText, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
struct Menu::ActionData {
|
||||
Text::String text;
|
||||
QString shortcut;
|
||||
const style::icon *icon = nullptr;
|
||||
const style::icon *iconOver = nullptr;
|
||||
std::unique_ptr<RippleAnimation> ripple;
|
||||
std::unique_ptr<ToggleView> toggle;
|
||||
int textWidth = 0;
|
||||
bool hasSubmenu = false;
|
||||
};
|
||||
|
||||
Menu::Menu(QWidget *parent, const style::Menu &st)
|
||||
: RpWidget(parent)
|
||||
, _st(st)
|
||||
, _itemHeight(_st.itemPadding.top() + _st.itemStyle.font->height + _st.itemPadding.bottom())
|
||||
, _separatorHeight(_st.separatorPadding.top() + _st.separatorWidth + _st.separatorPadding.bottom()) {
|
||||
init();
|
||||
}
|
||||
|
||||
Menu::Menu(QWidget *parent, QMenu *menu, const style::Menu &st)
|
||||
: RpWidget(parent)
|
||||
, _st(st)
|
||||
, _wappedMenu(menu)
|
||||
, _itemHeight(_st.itemPadding.top() + _st.itemStyle.font->height + _st.itemPadding.bottom())
|
||||
, _separatorHeight(_st.separatorPadding.top() + _st.separatorWidth + _st.separatorPadding.bottom()) {
|
||||
init();
|
||||
|
||||
_wappedMenu->setParent(this);
|
||||
for (auto action : _wappedMenu->actions()) {
|
||||
addAction(action);
|
||||
}
|
||||
_wappedMenu->hide();
|
||||
}
|
||||
|
||||
Menu::~Menu() = default;
|
||||
|
||||
void Menu::init() {
|
||||
resize(_forceWidth ? _forceWidth : _st.widthMin, _st.skip * 2);
|
||||
|
||||
setMouseTracking(true);
|
||||
|
||||
if (_st.itemBg->c.alpha() == 255) {
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
}
|
||||
}
|
||||
|
||||
not_null<QAction*> Menu::addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon, const style::icon *iconOver) {
|
||||
const auto action = addAction(new QAction(text, this), icon, iconOver);
|
||||
connect(action, SIGNAL(triggered(bool)), receiver, member, Qt::QueuedConnection);
|
||||
return action;
|
||||
}
|
||||
|
||||
not_null<QAction*> Menu::addAction(const QString &text, Fn<void()> callback, const style::icon *icon, const style::icon *iconOver) {
|
||||
const auto action = addAction(new QAction(text, this), icon, iconOver);
|
||||
connect(action, &QAction::triggered, action, std::move(callback), Qt::QueuedConnection);
|
||||
return action;
|
||||
}
|
||||
|
||||
not_null<QAction*> Menu::addAction(const QString &text, std::unique_ptr<QMenu> submenu) {
|
||||
const auto action = new QAction(text, this);
|
||||
action->setMenu(submenu.release());
|
||||
return addAction(action, nullptr, nullptr);
|
||||
}
|
||||
|
||||
not_null<QAction*> Menu::addAction(not_null<QAction*> action, const style::icon *icon, const style::icon *iconOver) {
|
||||
connect(action, &QAction::changed, this, [=] {
|
||||
actionChanged();
|
||||
});
|
||||
_actions.emplace_back(action);
|
||||
_actionsData.push_back([&] {
|
||||
auto data = ActionData();
|
||||
data.icon = icon;
|
||||
data.iconOver = iconOver ? iconOver : icon;
|
||||
data.hasSubmenu = (action->menu() != nullptr);
|
||||
return data;
|
||||
}());
|
||||
|
||||
auto newWidth = qMax(width(), _st.widthMin);
|
||||
newWidth = processAction(action, _actions.size() - 1, newWidth);
|
||||
auto newHeight = height() + (action->isSeparator() ? _separatorHeight : _itemHeight);
|
||||
resize(_forceWidth ? _forceWidth : newWidth, newHeight);
|
||||
if (_resizedCallback) {
|
||||
_resizedCallback();
|
||||
}
|
||||
updateSelected(QCursor::pos());
|
||||
update();
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
not_null<QAction*> Menu::addSeparator() {
|
||||
const auto separator = new QAction(this);
|
||||
separator->setSeparator(true);
|
||||
return addAction(separator);
|
||||
}
|
||||
|
||||
void Menu::clearActions() {
|
||||
setSelected(-1);
|
||||
setPressed(-1);
|
||||
_actionsData.clear();
|
||||
for (auto action : base::take(_actions)) {
|
||||
if (action->parent() == this) {
|
||||
delete action;
|
||||
}
|
||||
}
|
||||
resize(_forceWidth ? _forceWidth : _st.widthMin, _st.skip * 2);
|
||||
if (_resizedCallback) {
|
||||
_resizedCallback();
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::finishAnimating() {
|
||||
for (auto &data : _actionsData) {
|
||||
if (data.ripple) {
|
||||
data.ripple.reset();
|
||||
}
|
||||
if (data.toggle) {
|
||||
data.toggle->finishAnimating();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Menu::processAction(not_null<QAction*> action, int index, int width) {
|
||||
auto &data = _actionsData[index];
|
||||
if (action->isSeparator() || action->text().isEmpty()) {
|
||||
data.shortcut = QString();
|
||||
data.text.clear();
|
||||
} else {
|
||||
auto actionTextParts = action->text().split('\t');
|
||||
auto actionText = actionTextParts.empty() ? QString() : actionTextParts[0];
|
||||
auto actionShortcut = (actionTextParts.size() > 1) ? actionTextParts[1] : QString();
|
||||
data.text.setMarkedText(_st.itemStyle, ParseMenuItem(actionText), MenuTextOptions);
|
||||
const auto textw = data.text.maxWidth();
|
||||
int goodw = _st.itemPadding.left() + textw + _st.itemPadding.right();
|
||||
if (data.hasSubmenu) {
|
||||
goodw += _st.itemPadding.right() + _st.arrow.width();
|
||||
} else if (!actionShortcut.isEmpty()) {
|
||||
goodw += _st.itemPadding.right() + _st.itemStyle.font->width(actionShortcut);
|
||||
}
|
||||
if (action->isCheckable()) {
|
||||
auto updateCallback = [this, index] { updateItem(index); };
|
||||
if (data.toggle) {
|
||||
data.toggle->setUpdateCallback(updateCallback);
|
||||
data.toggle->setChecked(action->isChecked(), anim::type::normal);
|
||||
} else {
|
||||
data.toggle = std::make_unique<ToggleView>(_st.itemToggle, action->isChecked(), updateCallback);
|
||||
}
|
||||
goodw += _st.itemPadding.right() + data.toggle->getSize().width() - _st.itemToggleShift;
|
||||
} else {
|
||||
data.toggle.reset();
|
||||
}
|
||||
width = std::clamp(goodw, width, _st.widthMax);
|
||||
data.textWidth = width - (goodw - textw);
|
||||
data.shortcut = actionShortcut;
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
void Menu::setShowSource(TriggeredSource source) {
|
||||
_mouseSelection = (source == TriggeredSource::Mouse);
|
||||
setSelected((source == TriggeredSource::Mouse || _actions.empty()) ? -1 : 0);
|
||||
}
|
||||
|
||||
const std::vector<not_null<QAction*>> &Menu::actions() const {
|
||||
return _actions;
|
||||
}
|
||||
|
||||
void Menu::setForceWidth(int forceWidth) {
|
||||
_forceWidth = forceWidth;
|
||||
resize(_forceWidth, height());
|
||||
}
|
||||
|
||||
void Menu::actionChanged() {
|
||||
auto newWidth = _st.widthMin;
|
||||
auto index = 0;
|
||||
for (const auto action : _actions) {
|
||||
newWidth = processAction(action, index++, newWidth);
|
||||
}
|
||||
if (newWidth != width() && !_forceWidth) {
|
||||
resize(newWidth, height());
|
||||
if (_resizedCallback) {
|
||||
_resizedCallback();
|
||||
}
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void Menu::paintEvent(QPaintEvent *e) {
|
||||
Painter p(this);
|
||||
|
||||
auto clip = e->rect();
|
||||
|
||||
auto topskip = QRect(0, 0, width(), _st.skip);
|
||||
auto bottomskip = QRect(0, height() - _st.skip, width(), _st.skip);
|
||||
if (clip.intersects(topskip)) p.fillRect(clip.intersected(topskip), _st.itemBg);
|
||||
if (clip.intersects(bottomskip)) p.fillRect(clip.intersected(bottomskip), _st.itemBg);
|
||||
|
||||
int top = _st.skip;
|
||||
p.translate(0, top);
|
||||
p.setFont(_st.itemStyle.font);
|
||||
for (int i = 0, count = int(_actions.size()); i != count; ++i) {
|
||||
if (clip.top() + clip.height() <= top) break;
|
||||
|
||||
const auto action = _actions[i];
|
||||
auto &data = _actionsData[i];
|
||||
auto actionHeight = action->isSeparator() ? _separatorHeight : _itemHeight;
|
||||
top += actionHeight;
|
||||
if (clip.top() < top) {
|
||||
if (action->isSeparator()) {
|
||||
p.fillRect(0, 0, width(), actionHeight, _st.itemBg);
|
||||
p.fillRect(_st.separatorPadding.left(), _st.separatorPadding.top(), width() - _st.separatorPadding.left() - _st.separatorPadding.right(), _st.separatorWidth, _st.separatorFg);
|
||||
} else {
|
||||
auto enabled = action->isEnabled();
|
||||
auto selected = ((i == _selected || i == _pressed) && enabled);
|
||||
if (selected && _st.itemBgOver->c.alpha() < 255) {
|
||||
p.fillRect(0, 0, width(), actionHeight, _st.itemBg);
|
||||
}
|
||||
p.fillRect(0, 0, width(), actionHeight, selected ? _st.itemBgOver : _st.itemBg);
|
||||
if (data.ripple) {
|
||||
data.ripple->paint(p, 0, 0, width());
|
||||
if (data.ripple->empty()) {
|
||||
data.ripple.reset();
|
||||
}
|
||||
}
|
||||
if (auto icon = (selected ? data.iconOver : data.icon)) {
|
||||
icon->paint(p, _st.itemIconPosition, width());
|
||||
}
|
||||
p.setPen(selected ? _st.itemFgOver : (enabled ? _st.itemFg : _st.itemFgDisabled));
|
||||
data.text.drawLeftElided(p, _st.itemPadding.left(), _st.itemPadding.top(), data.textWidth, width());
|
||||
if (data.hasSubmenu) {
|
||||
const auto left = width() - _st.itemPadding.right() - _st.arrow.width();
|
||||
const auto top = (_itemHeight - _st.arrow.height()) / 2;
|
||||
if (enabled) {
|
||||
_st.arrow.paint(p, left, top, width());
|
||||
} else {
|
||||
_st.arrow.paint(
|
||||
p,
|
||||
left,
|
||||
top,
|
||||
width(),
|
||||
_st.itemFgDisabled->c);
|
||||
}
|
||||
} else if (!data.shortcut.isEmpty()) {
|
||||
p.setPen(selected ? _st.itemFgShortcutOver : (enabled ? _st.itemFgShortcut : _st.itemFgShortcutDisabled));
|
||||
p.drawTextRight(_st.itemPadding.right(), _st.itemPadding.top(), width(), data.shortcut);
|
||||
} else if (data.toggle) {
|
||||
auto toggleSize = data.toggle->getSize();
|
||||
data.toggle->paint(p, width() - _st.itemPadding.right() - toggleSize.width() + _st.itemToggleShift, (_itemHeight - toggleSize.height()) / 2, width());
|
||||
}
|
||||
}
|
||||
}
|
||||
p.translate(0, actionHeight);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::updateSelected(QPoint globalPosition) {
|
||||
if (!_mouseSelection) return;
|
||||
|
||||
auto p = mapFromGlobal(globalPosition) - QPoint(0, _st.skip);
|
||||
auto selected = -1, top = 0;
|
||||
while (top <= p.y() && ++selected < _actions.size()) {
|
||||
top += _actions[selected]->isSeparator() ? _separatorHeight : _itemHeight;
|
||||
}
|
||||
setSelected((selected >= 0 && selected < _actions.size() && _actions[selected]->isEnabled() && !_actions[selected]->isSeparator()) ? selected : -1);
|
||||
}
|
||||
|
||||
void Menu::itemPressed(TriggeredSource source) {
|
||||
if (source == TriggeredSource::Mouse && !_mouseSelection) {
|
||||
return;
|
||||
}
|
||||
if (_selected >= 0 && _selected < _actions.size() && _actions[_selected]->isEnabled()) {
|
||||
setPressed(_selected);
|
||||
if (source == TriggeredSource::Mouse) {
|
||||
if (!_actionsData[_pressed].ripple) {
|
||||
auto mask = RippleAnimation::rectMask(QSize(width(), _itemHeight));
|
||||
_actionsData[_pressed].ripple = std::make_unique<RippleAnimation>(_st.ripple, std::move(mask), [this, selected = _pressed] {
|
||||
updateItem(selected);
|
||||
});
|
||||
}
|
||||
_actionsData[_pressed].ripple->add(mapFromGlobal(QCursor::pos()) - QPoint(0, itemTop(_pressed)));
|
||||
} else {
|
||||
itemReleased(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::itemReleased(TriggeredSource source) {
|
||||
if (_pressed >= 0 && _pressed < _actions.size()) {
|
||||
auto pressed = _pressed;
|
||||
setPressed(-1);
|
||||
if (source == TriggeredSource::Mouse && _actionsData[pressed].ripple) {
|
||||
_actionsData[pressed].ripple->lastStop();
|
||||
}
|
||||
if (pressed == _selected && _triggeredCallback) {
|
||||
_triggeredCallback(_actions[_selected], itemTop(_selected), source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::keyPressEvent(QKeyEvent *e) {
|
||||
auto key = e->key();
|
||||
if (!_keyPressDelegate || !_keyPressDelegate(key)) {
|
||||
handleKeyPress(key);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::handleKeyPress(int key) {
|
||||
if (key == Qt::Key_Enter || key == Qt::Key_Return) {
|
||||
itemPressed(TriggeredSource::Keyboard);
|
||||
return;
|
||||
}
|
||||
if (key == (style::RightToLeft() ? Qt::Key_Left : Qt::Key_Right)) {
|
||||
if (_selected >= 0 && _actionsData[_selected].hasSubmenu) {
|
||||
itemPressed(TriggeredSource::Keyboard);
|
||||
return;
|
||||
} else if (_selected < 0 && !_actions.empty()) {
|
||||
_mouseSelection = false;
|
||||
setSelected(0);
|
||||
}
|
||||
}
|
||||
if ((key != Qt::Key_Up && key != Qt::Key_Down) || _actions.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto delta = (key == Qt::Key_Down ? 1 : -1), start = _selected;
|
||||
if (start < 0 || start >= _actions.size()) {
|
||||
start = (delta > 0) ? (_actions.size() - 1) : 0;
|
||||
}
|
||||
auto newSelected = start;
|
||||
do {
|
||||
newSelected += delta;
|
||||
if (newSelected < 0) {
|
||||
newSelected += _actions.size();
|
||||
} else if (newSelected >= _actions.size()) {
|
||||
newSelected -= _actions.size();
|
||||
}
|
||||
} while (newSelected != start && (!_actions[newSelected]->isEnabled() || _actions[newSelected]->isSeparator()));
|
||||
|
||||
if (_actions[newSelected]->isEnabled() && !_actions[newSelected]->isSeparator()) {
|
||||
_mouseSelection = false;
|
||||
setSelected(newSelected);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::clearSelection() {
|
||||
_mouseSelection = false;
|
||||
setSelected(-1);
|
||||
}
|
||||
|
||||
void Menu::clearMouseSelection() {
|
||||
if (_mouseSelection && !_childShown) {
|
||||
clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::enterEventHook(QEvent *e) {
|
||||
QPoint mouse = QCursor::pos();
|
||||
if (!rect().marginsRemoved(QMargins(0, _st.skip, 0, _st.skip)).contains(mapFromGlobal(mouse))) {
|
||||
clearMouseSelection();
|
||||
}
|
||||
return TWidget::enterEventHook(e);
|
||||
}
|
||||
|
||||
void Menu::leaveEventHook(QEvent *e) {
|
||||
clearMouseSelection();
|
||||
return TWidget::leaveEventHook(e);
|
||||
}
|
||||
|
||||
void Menu::setSelected(int selected) {
|
||||
if (selected >= _actions.size()) {
|
||||
selected = -1;
|
||||
}
|
||||
if (_selected != selected) {
|
||||
updateSelectedItem();
|
||||
if (_selected >= 0 && _selected != _pressed && _actionsData[_selected].toggle) {
|
||||
_actionsData[_selected].toggle->setStyle(_st.itemToggle);
|
||||
}
|
||||
_selected = selected;
|
||||
if (_selected >= 0 && _actionsData[_selected].toggle && _actions[_selected]->isEnabled()) {
|
||||
_actionsData[_selected].toggle->setStyle(_st.itemToggleOver);
|
||||
}
|
||||
updateSelectedItem();
|
||||
if (_activatedCallback) {
|
||||
auto source = _mouseSelection ? TriggeredSource::Mouse : TriggeredSource::Keyboard;
|
||||
_activatedCallback(
|
||||
(_selected >= 0) ? _actions[_selected].get() : nullptr,
|
||||
itemTop(_selected),
|
||||
source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::setPressed(int pressed) {
|
||||
if (pressed >= _actions.size()) {
|
||||
pressed = -1;
|
||||
}
|
||||
if (_pressed != pressed) {
|
||||
if (_pressed >= 0 && _pressed != _selected && _actionsData[_pressed].toggle) {
|
||||
_actionsData[_pressed].toggle->setStyle(_st.itemToggle);
|
||||
}
|
||||
_pressed = pressed;
|
||||
if (_pressed >= 0 && _actionsData[_pressed].toggle && _actions[_pressed]->isEnabled()) {
|
||||
_actionsData[_pressed].toggle->setStyle(_st.itemToggleOver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Menu::itemTop(int index) {
|
||||
if (index > _actions.size()) {
|
||||
index = _actions.size();
|
||||
}
|
||||
int top = _st.skip;
|
||||
for (int i = 0; i < index; ++i) {
|
||||
top += _actions.at(i)->isSeparator() ? _separatorHeight : _itemHeight;
|
||||
}
|
||||
return top;
|
||||
}
|
||||
|
||||
void Menu::updateItem(int index) {
|
||||
if (index >= 0 && index < _actions.size()) {
|
||||
update(0, itemTop(index), width(), _actions[index]->isSeparator() ? _separatorHeight : _itemHeight);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::updateSelectedItem() {
|
||||
updateItem(_selected);
|
||||
}
|
||||
|
||||
void Menu::mouseMoveEvent(QMouseEvent *e) {
|
||||
handleMouseMove(e->globalPos());
|
||||
}
|
||||
|
||||
void Menu::handleMouseMove(QPoint globalPosition) {
|
||||
auto inner = rect().marginsRemoved(QMargins(0, _st.skip, 0, _st.skip));
|
||||
auto localPosition = mapFromGlobal(globalPosition);
|
||||
if (inner.contains(localPosition)) {
|
||||
_mouseSelection = true;
|
||||
updateSelected(globalPosition);
|
||||
} else {
|
||||
clearMouseSelection();
|
||||
if (_mouseMoveDelegate) {
|
||||
_mouseMoveDelegate(globalPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::mousePressEvent(QMouseEvent *e) {
|
||||
handleMousePress(e->globalPos());
|
||||
}
|
||||
|
||||
void Menu::mouseReleaseEvent(QMouseEvent *e) {
|
||||
handleMouseRelease(e->globalPos());
|
||||
}
|
||||
|
||||
void Menu::handleMousePress(QPoint globalPosition) {
|
||||
handleMouseMove(globalPosition);
|
||||
if (rect().contains(mapFromGlobal(globalPosition))) {
|
||||
itemPressed(TriggeredSource::Mouse);
|
||||
} else if (_mousePressDelegate) {
|
||||
_mousePressDelegate(globalPosition);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::handleMouseRelease(QPoint globalPosition) {
|
||||
handleMouseMove(globalPosition);
|
||||
itemReleased(TriggeredSource::Mouse);
|
||||
if (!rect().contains(mapFromGlobal(globalPosition)) && _mouseReleaseDelegate) {
|
||||
_mouseReleaseDelegate(globalPosition);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
347
ui/widgets/menu/menu.cpp
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
// 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/menu/menu.h"
|
||||
|
||||
#include "ui/widgets/menu/menu_action.h"
|
||||
#include "ui/widgets/menu/menu_item_base.h"
|
||||
#include "ui/widgets/menu/menu_separator.h"
|
||||
|
||||
#include <QtGui/QtEvents>
|
||||
|
||||
namespace Ui::Menu {
|
||||
|
||||
Menu::Menu(QWidget *parent, const style::Menu &st)
|
||||
: RpWidget(parent)
|
||||
, _st(st) {
|
||||
init();
|
||||
}
|
||||
|
||||
Menu::Menu(QWidget *parent, QMenu *menu, const style::Menu &st)
|
||||
: RpWidget(parent)
|
||||
, _st(st)
|
||||
, _wappedMenu(menu) {
|
||||
init();
|
||||
|
||||
_wappedMenu->setParent(this);
|
||||
for (auto action : _wappedMenu->actions()) {
|
||||
addAction(action);
|
||||
}
|
||||
_wappedMenu->hide();
|
||||
}
|
||||
|
||||
Menu::~Menu() = default;
|
||||
|
||||
void Menu::init() {
|
||||
resize(_forceWidth ? _forceWidth : _st.widthMin, _st.skip * 2);
|
||||
|
||||
setMouseTracking(true);
|
||||
|
||||
if (_st.itemBg->c.alpha() == 255) {
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
}
|
||||
|
||||
paintRequest(
|
||||
) | rpl::start_with_next([=](const QRect &clip) {
|
||||
Painter p(this);
|
||||
p.fillRect(clip, _st.itemBg);
|
||||
}, lifetime());
|
||||
|
||||
positionValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
handleMouseMove(QCursor::pos());
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
not_null<QAction*> Menu::addAction(
|
||||
const QString &text,
|
||||
Fn<void()> callback,
|
||||
const style::icon *icon,
|
||||
const style::icon *iconOver) {
|
||||
auto action = CreateAction(this, text, std::move(callback));
|
||||
return addAction(std::move(action), icon, iconOver);
|
||||
}
|
||||
|
||||
not_null<QAction*> Menu::addAction(
|
||||
const QString &text,
|
||||
std::unique_ptr<QMenu> submenu) {
|
||||
const auto action = new QAction(text, this);
|
||||
action->setMenu(submenu.release());
|
||||
return addAction(action, nullptr, nullptr);
|
||||
}
|
||||
|
||||
not_null<QAction*> Menu::addAction(
|
||||
not_null<QAction*> action,
|
||||
const style::icon *icon,
|
||||
const style::icon *iconOver) {
|
||||
if (action->isSeparator()) {
|
||||
return addSeparator();
|
||||
}
|
||||
auto item = base::make_unique_q<Action>(
|
||||
this,
|
||||
_st,
|
||||
std::move(action),
|
||||
icon,
|
||||
iconOver ? iconOver : icon);
|
||||
return addAction(std::move(item));
|
||||
}
|
||||
|
||||
not_null<QAction*> Menu::addAction(base::unique_qptr<ItemBase> widget) {
|
||||
const auto action = widget->action();
|
||||
_actions.emplace_back(action);
|
||||
|
||||
widget->setParent(this);
|
||||
|
||||
const auto top = _actionWidgets.empty()
|
||||
? 0
|
||||
: _actionWidgets.back()->y() + _actionWidgets.back()->height();
|
||||
|
||||
widget->moveToLeft(0, top);
|
||||
widget->show();
|
||||
|
||||
widget->setIndex(_actionWidgets.size());
|
||||
|
||||
widget->selects(
|
||||
) | rpl::start_with_next([=](const CallbackData &data) {
|
||||
if (!data.selected) {
|
||||
return;
|
||||
}
|
||||
for (auto i = 0; i < _actionWidgets.size(); i++) {
|
||||
if (i != data.index) {
|
||||
_actionWidgets[i]->setSelected(false);
|
||||
}
|
||||
}
|
||||
if (_activatedCallback) {
|
||||
_activatedCallback(data);
|
||||
}
|
||||
}, widget->lifetime());
|
||||
|
||||
widget->clicks(
|
||||
) | rpl::start_with_next([=](const CallbackData &data) {
|
||||
if (_triggeredCallback) {
|
||||
_triggeredCallback(data);
|
||||
}
|
||||
}, widget->lifetime());
|
||||
|
||||
const auto raw = widget.get();
|
||||
_actionWidgets.push_back(std::move(widget));
|
||||
|
||||
raw->minWidthValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto newWidth = _forceWidth
|
||||
? _forceWidth
|
||||
: std::clamp(
|
||||
_actionWidgets.empty()
|
||||
? 0
|
||||
: (*ranges::max_element(
|
||||
_actionWidgets,
|
||||
std::less<>(),
|
||||
&ItemBase::minWidth))->minWidth(),
|
||||
_st.widthMin,
|
||||
_st.widthMax);
|
||||
resizeFromInner(newWidth, height());
|
||||
}, raw->lifetime());
|
||||
|
||||
const auto newHeight = ranges::accumulate(
|
||||
_actionWidgets,
|
||||
0,
|
||||
ranges::plus(),
|
||||
&ItemBase::height);
|
||||
resizeFromInner(width(), newHeight);
|
||||
updateSelected(QCursor::pos());
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
not_null<QAction*> Menu::addSeparator() {
|
||||
const auto separator = new QAction(this);
|
||||
separator->setSeparator(true);
|
||||
auto item = base::make_unique_q<Separator>(this, _st, separator);
|
||||
return addAction(std::move(item));
|
||||
}
|
||||
|
||||
void Menu::clearActions() {
|
||||
_actionWidgets.clear();
|
||||
for (auto action : base::take(_actions)) {
|
||||
if (action->parent() == this) {
|
||||
delete action;
|
||||
}
|
||||
}
|
||||
resizeFromInner(_forceWidth ? _forceWidth : _st.widthMin, _st.skip * 2);
|
||||
}
|
||||
|
||||
void Menu::finishAnimating() {
|
||||
for (const auto &widget : _actionWidgets) {
|
||||
widget->finishAnimating();
|
||||
}
|
||||
}
|
||||
|
||||
bool Menu::empty() const {
|
||||
return _actionWidgets.empty();
|
||||
}
|
||||
|
||||
void Menu::resizeFromInner(int w, int h) {
|
||||
if ((w == width()) && (h == height())) {
|
||||
return;
|
||||
}
|
||||
resize(w, h);
|
||||
_resizesFromInner.fire({});
|
||||
}
|
||||
|
||||
rpl::producer<> Menu::resizesFromInner() const {
|
||||
return _resizesFromInner.events();
|
||||
}
|
||||
|
||||
void Menu::setShowSource(TriggeredSource source) {
|
||||
const auto mouseSelection = (source == TriggeredSource::Mouse);
|
||||
setSelected(
|
||||
(mouseSelection || _actions.empty()) ? -1 : 0,
|
||||
mouseSelection);
|
||||
}
|
||||
|
||||
const std::vector<not_null<QAction*>> &Menu::actions() const {
|
||||
return _actions;
|
||||
}
|
||||
|
||||
void Menu::setForceWidth(int forceWidth) {
|
||||
_forceWidth = forceWidth;
|
||||
resizeFromInner(_forceWidth, height());
|
||||
}
|
||||
|
||||
void Menu::updateSelected(QPoint globalPosition) {
|
||||
const auto p = mapFromGlobal(globalPosition) - QPoint(0, _st.skip);
|
||||
for (const auto &widget : _actionWidgets) {
|
||||
const auto widgetRect = QRect(widget->pos(), widget->size());
|
||||
if (widgetRect.contains(p)) {
|
||||
widget->setSelected(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::itemPressed(TriggeredSource source) {
|
||||
if (const auto action = findSelectedAction()) {
|
||||
if (action->lastTriggeredSource() == source) {
|
||||
action->setClicked(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::keyPressEvent(QKeyEvent *e) {
|
||||
const auto key = e->key();
|
||||
if (!_keyPressDelegate || !_keyPressDelegate(key)) {
|
||||
handleKeyPress(e);
|
||||
}
|
||||
}
|
||||
|
||||
ItemBase *Menu::findSelectedAction() const {
|
||||
const auto it = ranges::find_if(_actionWidgets, &ItemBase::isSelected);
|
||||
return (it == end(_actionWidgets)) ? nullptr : it->get();
|
||||
}
|
||||
|
||||
void Menu::handleKeyPress(not_null<QKeyEvent*> e) {
|
||||
const auto key = e->key();
|
||||
const auto selected = findSelectedAction();
|
||||
if ((key != Qt::Key_Up && key != Qt::Key_Down) || _actions.empty()) {
|
||||
if (selected) {
|
||||
selected->handleKeyPress(e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const auto delta = (key == Qt::Key_Down ? 1 : -1);
|
||||
auto start = selected ? selected->index() : -1;
|
||||
if (start < 0 || start >= _actions.size()) {
|
||||
start = (delta > 0) ? (_actions.size() - 1) : 0;
|
||||
}
|
||||
auto newSelected = start;
|
||||
do {
|
||||
newSelected += delta;
|
||||
if (newSelected < 0) {
|
||||
newSelected += _actions.size();
|
||||
} else if (newSelected >= _actions.size()) {
|
||||
newSelected -= _actions.size();
|
||||
}
|
||||
} while (newSelected != start && (!_actionWidgets[newSelected]->isEnabled()));
|
||||
|
||||
if (_actionWidgets[newSelected]->isEnabled()) {
|
||||
setSelected(newSelected, false);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::clearSelection() {
|
||||
setSelected(-1, false);
|
||||
}
|
||||
|
||||
void Menu::clearMouseSelection() {
|
||||
const auto selected = findSelectedAction();
|
||||
const auto mouseSelection = selected
|
||||
? (selected->lastTriggeredSource() == TriggeredSource::Mouse)
|
||||
: false;
|
||||
if (mouseSelection && !_childShown) {
|
||||
clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::setSelected(int selected, bool isMouseSelection) {
|
||||
if (selected >= _actionWidgets.size()) {
|
||||
selected = -1;
|
||||
}
|
||||
const auto source = isMouseSelection
|
||||
? TriggeredSource::Mouse
|
||||
: TriggeredSource::Keyboard;
|
||||
if (const auto selectedItem = findSelectedAction()) {
|
||||
if (selectedItem->index() == selected) {
|
||||
return;
|
||||
}
|
||||
selectedItem->setSelected(false, source);
|
||||
}
|
||||
if (selected >= 0) {
|
||||
_actionWidgets[selected]->setSelected(true, source);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::mouseMoveEvent(QMouseEvent *e) {
|
||||
handleMouseMove(e->globalPos());
|
||||
}
|
||||
|
||||
void Menu::handleMouseMove(QPoint globalPosition) {
|
||||
const auto margins = style::margins(0, _st.skip, 0, _st.skip);
|
||||
const auto inner = rect().marginsRemoved(margins);
|
||||
const auto localPosition = mapFromGlobal(globalPosition);
|
||||
if (inner.contains(localPosition)) {
|
||||
updateSelected(globalPosition);
|
||||
} else {
|
||||
clearMouseSelection();
|
||||
if (_mouseMoveDelegate) {
|
||||
_mouseMoveDelegate(globalPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::mousePressEvent(QMouseEvent *e) {
|
||||
handleMousePress(e->globalPos());
|
||||
}
|
||||
|
||||
void Menu::mouseReleaseEvent(QMouseEvent *e) {
|
||||
handleMouseRelease(e->globalPos());
|
||||
}
|
||||
|
||||
void Menu::handleMousePress(QPoint globalPosition) {
|
||||
handleMouseMove(globalPosition);
|
||||
if (_mousePressDelegate) {
|
||||
_mousePressDelegate(globalPosition);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::handleMouseRelease(QPoint globalPosition) {
|
||||
if (!rect().contains(mapFromGlobal(globalPosition))
|
||||
&& _mouseReleaseDelegate) {
|
||||
_mouseReleaseDelegate(globalPosition);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Ui::Menu
|
||||
|
|
@ -6,14 +6,16 @@
|
|||
//
|
||||
#pragma once
|
||||
|
||||
#include "base/unique_qptr.h"
|
||||
#include "ui/rp_widget.h"
|
||||
#include "ui/widgets/menu/menu_common.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
#include <QtWidgets/QMenu>
|
||||
|
||||
namespace Ui {
|
||||
namespace Ui::Menu {
|
||||
|
||||
class ToggleView;
|
||||
class ItemBase;
|
||||
class RippleAnimation;
|
||||
|
||||
class Menu : public RpWidget {
|
||||
|
|
@ -22,19 +24,27 @@ public:
|
|||
Menu(QWidget *parent, QMenu *menu, const style::Menu &st = st::defaultMenu);
|
||||
~Menu();
|
||||
|
||||
not_null<QAction*> addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr);
|
||||
not_null<QAction*> addAction(const QString &text, Fn<void()> callback, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr);
|
||||
not_null<QAction*> addAction(const QString &text, std::unique_ptr<QMenu> submenu);
|
||||
[[nodiscard]] const style::Menu &st() const {
|
||||
return _st;
|
||||
}
|
||||
|
||||
not_null<QAction*> addAction(base::unique_qptr<ItemBase> widget);
|
||||
not_null<QAction*> addAction(
|
||||
const QString &text,
|
||||
Fn<void()> callback,
|
||||
const style::icon *icon = nullptr,
|
||||
const style::icon *iconOver = nullptr);
|
||||
not_null<QAction*> addAction(
|
||||
const QString &text,
|
||||
std::unique_ptr<QMenu> submenu);
|
||||
not_null<QAction*> addSeparator();
|
||||
void clearActions();
|
||||
void finishAnimating();
|
||||
|
||||
bool empty() const;
|
||||
|
||||
void clearSelection();
|
||||
|
||||
enum class TriggeredSource {
|
||||
Mouse,
|
||||
Keyboard,
|
||||
};
|
||||
void setChildShown(bool shown) {
|
||||
_childShown = shown;
|
||||
}
|
||||
|
|
@ -43,21 +53,17 @@ public:
|
|||
|
||||
const std::vector<not_null<QAction*>> &actions() const;
|
||||
|
||||
void setResizedCallback(Fn<void()> callback) {
|
||||
_resizedCallback = std::move(callback);
|
||||
}
|
||||
|
||||
void setActivatedCallback(Fn<void(QAction *action, int actionTop, TriggeredSource source)> callback) {
|
||||
void setActivatedCallback(Fn<void(const CallbackData &data)> callback) {
|
||||
_activatedCallback = std::move(callback);
|
||||
}
|
||||
void setTriggeredCallback(Fn<void(QAction *action, int actionTop, TriggeredSource source)> callback) {
|
||||
void setTriggeredCallback(Fn<void(const CallbackData &data)> callback) {
|
||||
_triggeredCallback = std::move(callback);
|
||||
}
|
||||
|
||||
void setKeyPressDelegate(Fn<bool(int key)> delegate) {
|
||||
_keyPressDelegate = std::move(delegate);
|
||||
}
|
||||
void handleKeyPress(int key);
|
||||
void handleKeyPress(not_null<QKeyEvent*> e);
|
||||
|
||||
void setMouseMoveDelegate(Fn<void(QPoint globalPosition)> delegate) {
|
||||
_mouseMoveDelegate = std::move(delegate);
|
||||
|
|
@ -74,41 +80,36 @@ public:
|
|||
}
|
||||
void handleMouseRelease(QPoint globalPosition);
|
||||
|
||||
rpl::producer<> resizesFromInner() const;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
void enterEventHook(QEvent *e) override;
|
||||
void leaveEventHook(QEvent *e) override;
|
||||
|
||||
private:
|
||||
struct ActionData;
|
||||
|
||||
void updateSelected(QPoint globalPosition);
|
||||
void actionChanged();
|
||||
void init();
|
||||
|
||||
// Returns the new width.
|
||||
int processAction(not_null<QAction*> action, int index, int width);
|
||||
not_null<QAction*> addAction(not_null<QAction*> action, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr);
|
||||
not_null<QAction*> addAction(
|
||||
not_null<QAction*> action,
|
||||
const style::icon *icon = nullptr,
|
||||
const style::icon *iconOver = nullptr);
|
||||
|
||||
void setSelected(int selected);
|
||||
void setPressed(int pressed);
|
||||
void setSelected(int selected, bool isMouseSelection);
|
||||
void clearMouseSelection();
|
||||
|
||||
int itemTop(int index);
|
||||
void updateItem(int index);
|
||||
void updateSelectedItem();
|
||||
void itemPressed(TriggeredSource source);
|
||||
void itemReleased(TriggeredSource source);
|
||||
|
||||
ItemBase *findSelectedAction() const;
|
||||
|
||||
void resizeFromInner(int w, int h);
|
||||
|
||||
const style::Menu &_st;
|
||||
|
||||
Fn<void()> _resizedCallback;
|
||||
Fn<void(QAction *action, int actionTop, TriggeredSource source)> _activatedCallback;
|
||||
Fn<void(QAction *action, int actionTop, TriggeredSource source)> _triggeredCallback;
|
||||
Fn<void(const CallbackData &data)> _activatedCallback;
|
||||
Fn<void(const CallbackData &data)> _triggeredCallback;
|
||||
Fn<bool(int key)> _keyPressDelegate;
|
||||
Fn<void(QPoint globalPosition)> _mouseMoveDelegate;
|
||||
Fn<void(QPoint globalPosition)> _mousePressDelegate;
|
||||
|
|
@ -116,17 +117,14 @@ private:
|
|||
|
||||
QMenu *_wappedMenu = nullptr;
|
||||
std::vector<not_null<QAction*>> _actions;
|
||||
std::vector<ActionData> _actionsData;
|
||||
std::vector<base::unique_qptr<ItemBase>> _actionWidgets;
|
||||
|
||||
int _forceWidth = 0;
|
||||
int _itemHeight, _separatorHeight;
|
||||
|
||||
bool _mouseSelection = false;
|
||||
|
||||
int _selected = -1;
|
||||
int _pressed = -1;
|
||||
bool _childShown = false;
|
||||
|
||||
rpl::event_stream<> _resizesFromInner;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui
|
||||
} // namespace Ui::Menu
|
||||
200
ui/widgets/menu/menu_action.cpp
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
// 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/menu/menu_action.h"
|
||||
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/painter.h"
|
||||
|
||||
#include <QtGui/QtEvents>
|
||||
|
||||
namespace Ui::Menu {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] TextWithEntities ParseMenuItem(const QString &text) {
|
||||
auto result = TextWithEntities();
|
||||
result.text.reserve(text.size());
|
||||
auto afterAmpersand = false;
|
||||
for (const auto ch : text) {
|
||||
if (afterAmpersand) {
|
||||
afterAmpersand = false;
|
||||
if (ch == '&') {
|
||||
result.text.append(ch);
|
||||
} else {
|
||||
result.entities.append(EntityInText{
|
||||
EntityType::Underline,
|
||||
result.text.size(),
|
||||
1 });
|
||||
result.text.append(ch);
|
||||
}
|
||||
} else if (ch == '&') {
|
||||
afterAmpersand = true;
|
||||
} else {
|
||||
result.text.append(ch);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
TextParseOptions MenuTextOptions = {
|
||||
TextParseLinks | TextParseRichText, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Action::Action(
|
||||
not_null<RpWidget*> parent,
|
||||
const style::Menu &st,
|
||||
not_null<QAction*> action,
|
||||
const style::icon *icon,
|
||||
const style::icon *iconOver)
|
||||
: ItemBase(parent, st)
|
||||
, _action(action)
|
||||
, _st(st)
|
||||
, _icon(icon)
|
||||
, _iconOver(iconOver)
|
||||
, _height(_st.itemPadding.top()
|
||||
+ _st.itemStyle.font->height
|
||||
+ _st.itemPadding.bottom()) {
|
||||
|
||||
setAcceptBoth(true);
|
||||
|
||||
initResizeHook(parent->sizeValue());
|
||||
processAction();
|
||||
|
||||
paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
Painter p(this);
|
||||
paint(p);
|
||||
}, lifetime());
|
||||
|
||||
enableMouseSelecting();
|
||||
|
||||
connect(_action, &QAction::changed, [=] { processAction(); });
|
||||
}
|
||||
|
||||
bool Action::hasSubmenu() const {
|
||||
return _action->menu() != nullptr;
|
||||
}
|
||||
|
||||
void Action::paint(Painter &p) {
|
||||
const auto enabled = _action->isEnabled();
|
||||
const auto selected = isSelected();
|
||||
if (selected && _st.itemBgOver->c.alpha() < 255) {
|
||||
p.fillRect(0, 0, width(), _height, _st.itemBg);
|
||||
}
|
||||
p.fillRect(0, 0, width(), _height, selected ? _st.itemBgOver : _st.itemBg);
|
||||
if (isEnabled()) {
|
||||
paintRipple(p, 0, 0);
|
||||
}
|
||||
if (const auto icon = (selected ? _iconOver : _icon)) {
|
||||
icon->paint(p, _st.itemIconPosition, width());
|
||||
}
|
||||
p.setPen(selected ? _st.itemFgOver : (enabled ? _st.itemFg : _st.itemFgDisabled));
|
||||
_text.drawLeftElided(p, _st.itemPadding.left(), _st.itemPadding.top(), _textWidth, width());
|
||||
if (hasSubmenu()) {
|
||||
const auto left = width() - _st.itemPadding.right() - _st.arrow.width();
|
||||
const auto top = (_height - _st.arrow.height()) / 2;
|
||||
if (enabled) {
|
||||
_st.arrow.paint(p, left, top, width());
|
||||
} else {
|
||||
_st.arrow.paint(
|
||||
p,
|
||||
left,
|
||||
top,
|
||||
width(),
|
||||
_st.itemFgDisabled->c);
|
||||
}
|
||||
} else if (!_shortcut.isEmpty()) {
|
||||
p.setPen(selected
|
||||
? _st.itemFgShortcutOver
|
||||
: (enabled ? _st.itemFgShortcut : _st.itemFgShortcutDisabled));
|
||||
p.drawTextRight(
|
||||
_st.itemPadding.right(),
|
||||
_st.itemPadding.top(),
|
||||
width(),
|
||||
_shortcut);
|
||||
}
|
||||
}
|
||||
|
||||
void Action::processAction() {
|
||||
if (_action->text().isEmpty()) {
|
||||
_shortcut = QString();
|
||||
_text.clear();
|
||||
return;
|
||||
}
|
||||
const auto actionTextParts = _action->text().split('\t');
|
||||
const auto actionText = actionTextParts.empty()
|
||||
? QString()
|
||||
: actionTextParts[0];
|
||||
const auto actionShortcut = (actionTextParts.size() > 1)
|
||||
? actionTextParts[1]
|
||||
: QString();
|
||||
_text.setMarkedText(
|
||||
_st.itemStyle,
|
||||
ParseMenuItem(actionText),
|
||||
MenuTextOptions);
|
||||
const auto textWidth = _text.maxWidth();
|
||||
const auto &padding = _st.itemPadding;
|
||||
|
||||
const auto additionalWidth = hasSubmenu()
|
||||
? padding.right() + _st.arrow.width()
|
||||
: (!actionShortcut.isEmpty())
|
||||
? (padding.right() + _st.itemStyle.font->width(actionShortcut))
|
||||
: 0;
|
||||
const auto goodWidth = padding.left()
|
||||
+ textWidth
|
||||
+ padding.right()
|
||||
+ additionalWidth;
|
||||
|
||||
const auto w = std::clamp(goodWidth, _st.widthMin, _st.widthMax);
|
||||
_textWidth = w - (goodWidth - textWidth);
|
||||
_shortcut = actionShortcut;
|
||||
setMinWidth(w);
|
||||
update();
|
||||
}
|
||||
|
||||
bool Action::isEnabled() const {
|
||||
return _action->isEnabled();
|
||||
}
|
||||
|
||||
not_null<QAction*> Action::action() const {
|
||||
return _action;
|
||||
}
|
||||
|
||||
QPoint Action::prepareRippleStartPosition() const {
|
||||
return mapFromGlobal(QCursor::pos());
|
||||
}
|
||||
|
||||
QImage Action::prepareRippleMask() const {
|
||||
return Ui::RippleAnimation::rectMask(size());
|
||||
}
|
||||
|
||||
int Action::contentHeight() const {
|
||||
return _height;
|
||||
}
|
||||
|
||||
void Action::handleKeyPress(not_null<QKeyEvent*> e) {
|
||||
if (!isSelected()) {
|
||||
return;
|
||||
}
|
||||
const auto key = e->key();
|
||||
if (key == Qt::Key_Enter || key == Qt::Key_Return) {
|
||||
setClicked(TriggeredSource::Keyboard);
|
||||
return;
|
||||
}
|
||||
if (key == (style::RightToLeft() ? Qt::Key_Left : Qt::Key_Right)) {
|
||||
if (hasSubmenu()) {
|
||||
setClicked(TriggeredSource::Keyboard);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Ui::Menu
|
||||
55
ui/widgets/menu/menu_action.h
Normal file
|
|
@ -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 "ui/text/text.h"
|
||||
#include "ui/widgets/menu/menu_item_base.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
class Painter;
|
||||
|
||||
namespace Ui::Menu {
|
||||
|
||||
class Action : public ItemBase {
|
||||
public:
|
||||
Action(
|
||||
not_null<RpWidget*> parent,
|
||||
const style::Menu &st,
|
||||
not_null<QAction*> action,
|
||||
const style::icon *icon,
|
||||
const style::icon *iconOver);
|
||||
|
||||
bool isEnabled() const override;
|
||||
not_null<QAction*> action() const override;
|
||||
|
||||
void handleKeyPress(not_null<QKeyEvent*> e) override;
|
||||
|
||||
protected:
|
||||
QPoint prepareRippleStartPosition() const override;
|
||||
QImage prepareRippleMask() const override;
|
||||
|
||||
int contentHeight() const override;
|
||||
|
||||
private:
|
||||
void processAction();
|
||||
void paint(Painter &p);
|
||||
|
||||
bool hasSubmenu() const;
|
||||
|
||||
Text::String _text;
|
||||
QString _shortcut;
|
||||
const not_null<QAction*> _action;
|
||||
const style::Menu &_st;
|
||||
const style::icon *_icon;
|
||||
const style::icon *_iconOver;
|
||||
// std::unique_ptr<ToggleView> _toggle;
|
||||
int _textWidth = 0;
|
||||
const int _height;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui::Menu
|
||||
27
ui/widgets/menu/menu_common.cpp
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// 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/menu/menu_common.h"
|
||||
|
||||
#include <QtWidgets/QAction>
|
||||
|
||||
namespace Ui::Menu {
|
||||
|
||||
not_null<QAction*> CreateAction(
|
||||
QWidget *parent,
|
||||
const QString &text,
|
||||
Fn<void()> &&callback) {
|
||||
const auto action = new QAction(text, parent);
|
||||
parent->connect(
|
||||
action,
|
||||
&QAction::triggered,
|
||||
action,
|
||||
std::move(callback),
|
||||
Qt::QueuedConnection);
|
||||
return action;
|
||||
}
|
||||
|
||||
} // namespace Ui::Menu
|
||||
29
ui/widgets/menu/menu_common.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// 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
|
||||
|
||||
namespace Ui::Menu {
|
||||
|
||||
enum class TriggeredSource {
|
||||
Mouse,
|
||||
Keyboard,
|
||||
};
|
||||
|
||||
struct CallbackData {
|
||||
QAction *action;
|
||||
int actionTop = 0;
|
||||
TriggeredSource source;
|
||||
int index = 0;
|
||||
bool selected = false;
|
||||
};
|
||||
|
||||
not_null<QAction*> CreateAction(
|
||||
QWidget *parent,
|
||||
const QString &text,
|
||||
Fn<void()> &&callback);
|
||||
|
||||
} // namespace Ui::Menu
|
||||
119
ui/widgets/menu/menu_item_base.cpp
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
// 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/menu/menu_item_base.h"
|
||||
|
||||
namespace Ui::Menu {
|
||||
|
||||
ItemBase::ItemBase(
|
||||
not_null<RpWidget*> parent,
|
||||
const style::Menu &st)
|
||||
: RippleButton(parent, st.ripple) {
|
||||
}
|
||||
|
||||
void ItemBase::setSelected(
|
||||
bool selected,
|
||||
TriggeredSource source) {
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
}
|
||||
if (_selected.current() != selected) {
|
||||
setMouseTracking(!selected);
|
||||
_lastTriggeredSource = source;
|
||||
_selected = selected;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
bool ItemBase::isSelected() const {
|
||||
return _selected.current();
|
||||
}
|
||||
|
||||
rpl::producer<CallbackData> ItemBase::selects() const {
|
||||
return _selected.changes(
|
||||
) | rpl::map([=](bool selected) -> CallbackData {
|
||||
return { action(), y(), _lastTriggeredSource, _index, selected };
|
||||
});
|
||||
}
|
||||
|
||||
TriggeredSource ItemBase::lastTriggeredSource() const {
|
||||
return _lastTriggeredSource;
|
||||
}
|
||||
|
||||
int ItemBase::index() const {
|
||||
return _index;
|
||||
}
|
||||
|
||||
void ItemBase::setIndex(int index) {
|
||||
_index = index;
|
||||
}
|
||||
|
||||
void ItemBase::setClicked(TriggeredSource source) {
|
||||
if (isEnabled()) {
|
||||
_lastTriggeredSource = source;
|
||||
_clicks.fire({});
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<CallbackData> ItemBase::clicks() const {
|
||||
return rpl::merge(
|
||||
AbstractButton::clicks() | rpl::to_empty,
|
||||
_clicks.events()
|
||||
) | rpl::filter([=] {
|
||||
return isEnabled();
|
||||
}) | rpl::map([=]() -> CallbackData {
|
||||
return { action(), y(), _lastTriggeredSource, _index, true };
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<int> ItemBase::minWidthValue() const {
|
||||
return _minWidth.value();
|
||||
}
|
||||
|
||||
int ItemBase::minWidth() const {
|
||||
return _minWidth.current();
|
||||
}
|
||||
|
||||
void ItemBase::initResizeHook(rpl::producer<QSize> &&size) {
|
||||
std::move(
|
||||
size
|
||||
) | rpl::start_with_next([=](QSize s) {
|
||||
resize(s.width(), contentHeight());
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void ItemBase::setMinWidth(int w) {
|
||||
_minWidth = w;
|
||||
}
|
||||
|
||||
void ItemBase::finishAnimating() {
|
||||
RippleButton::finishAnimating();
|
||||
}
|
||||
|
||||
void ItemBase::enableMouseSelecting() {
|
||||
enableMouseSelecting(this);
|
||||
}
|
||||
|
||||
void ItemBase::enableMouseSelecting(not_null<RpWidget*> widget) {
|
||||
widget->events(
|
||||
) | rpl::start_with_next([=](not_null<QEvent*> e) {
|
||||
const auto type = e->type();
|
||||
if (((type == QEvent::Leave)
|
||||
|| (type == QEvent::Enter)
|
||||
|| (type == QEvent::MouseMove)) && action()->isEnabled()) {
|
||||
setSelected(e->type() != QEvent::Leave);
|
||||
} else if ((type == QEvent::MouseButtonRelease)
|
||||
&& isEnabled()
|
||||
&& isSelected()) {
|
||||
const auto point = mapFromGlobal(QCursor::pos());
|
||||
if (!rect().contains(point)) {
|
||||
setSelected(false);
|
||||
}
|
||||
}
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
} // namespace Ui::Menu
|
||||
67
ui/widgets/menu/menu_item_base.h
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
// 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/widgets/buttons.h"
|
||||
#include "ui/widgets/menu/menu.h"
|
||||
#include "ui/widgets/menu/menu_common.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
namespace Ui::Menu {
|
||||
|
||||
class ItemBase : public RippleButton {
|
||||
public:
|
||||
ItemBase(not_null<RpWidget*> parent, const style::Menu &st);
|
||||
|
||||
TriggeredSource lastTriggeredSource() const;
|
||||
|
||||
rpl::producer<CallbackData> selects() const;
|
||||
void setSelected(
|
||||
bool selected,
|
||||
TriggeredSource source = TriggeredSource::Mouse);
|
||||
bool isSelected() const;
|
||||
|
||||
int index() const;
|
||||
void setIndex(int index);
|
||||
|
||||
void setClicked(TriggeredSource source = TriggeredSource::Mouse);
|
||||
|
||||
rpl::producer<CallbackData> clicks() const;
|
||||
|
||||
rpl::producer<int> minWidthValue() const;
|
||||
int minWidth() const;
|
||||
void setMinWidth(int w);
|
||||
|
||||
virtual void handleKeyPress(not_null<QKeyEvent*> e) {
|
||||
}
|
||||
|
||||
virtual not_null<QAction*> action() const = 0;
|
||||
virtual bool isEnabled() const = 0;
|
||||
|
||||
virtual void finishAnimating();
|
||||
|
||||
protected:
|
||||
void initResizeHook(rpl::producer<QSize> &&size);
|
||||
|
||||
void enableMouseSelecting();
|
||||
void enableMouseSelecting(not_null<RpWidget*> widget);
|
||||
|
||||
virtual int contentHeight() const = 0;
|
||||
|
||||
private:
|
||||
int _index = -1;
|
||||
|
||||
rpl::variable<bool> _selected = false;
|
||||
rpl::event_stream<> _clicks;
|
||||
|
||||
rpl::variable<int> _minWidth = 0;
|
||||
|
||||
TriggeredSource _lastTriggeredSource = TriggeredSource::Mouse;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui::Menu
|
||||
52
ui/widgets/menu/menu_separator.cpp
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
// 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/menu/menu_separator.h"
|
||||
|
||||
#include "ui/painter.h"
|
||||
|
||||
namespace Ui::Menu {
|
||||
|
||||
Separator::Separator(
|
||||
not_null<RpWidget*> parent,
|
||||
const style::Menu &st,
|
||||
not_null<QAction*> action)
|
||||
: ItemBase(parent, st)
|
||||
, _lineWidth(st.separatorWidth)
|
||||
, _padding(st.separatorPadding)
|
||||
, _fg(st.separatorFg)
|
||||
, _bg(st.itemBg)
|
||||
, _height(_padding.top() + _lineWidth + _padding.bottom())
|
||||
, _action(action) {
|
||||
|
||||
initResizeHook(parent->sizeValue());
|
||||
paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
Painter p(this);
|
||||
|
||||
p.fillRect(0, 0, width(), _height, _bg);
|
||||
p.fillRect(
|
||||
_padding.left(),
|
||||
_padding.top(),
|
||||
width() - _padding.left() - _padding.right(),
|
||||
_lineWidth,
|
||||
_fg);
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
not_null<QAction*> Separator::action() const {
|
||||
return _action;
|
||||
}
|
||||
|
||||
bool Separator::isEnabled() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
int Separator::contentHeight() const {
|
||||
return _height;
|
||||
}
|
||||
|
||||
} // namespace Ui::Menu
|
||||
39
ui/widgets/menu/menu_separator.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
// 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/widgets/menu/menu_item_base.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
class Painter;
|
||||
|
||||
namespace Ui::Menu {
|
||||
|
||||
class Separator : public ItemBase {
|
||||
public:
|
||||
Separator(
|
||||
not_null<RpWidget*> parent,
|
||||
const style::Menu &st,
|
||||
not_null<QAction*> action);
|
||||
|
||||
not_null<QAction*> action() const override;
|
||||
bool isEnabled() const override;
|
||||
|
||||
protected:
|
||||
int contentHeight() const override;
|
||||
|
||||
private:
|
||||
const int _lineWidth;
|
||||
const style::margins &_padding;
|
||||
const style::color &_fg;
|
||||
const style::color &_bg;
|
||||
const int _height;
|
||||
const not_null<QAction*> _action;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui::Menu
|
||||
77
ui/widgets/menu/menu_toggle.cpp
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
// 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/menu/menu_toggle.h"
|
||||
|
||||
#include "ui/widgets/checkbox.h"
|
||||
|
||||
namespace Ui::Menu {
|
||||
|
||||
Toggle::Toggle(
|
||||
not_null<RpWidget*> parent,
|
||||
const style::Menu &st,
|
||||
const QString &text,
|
||||
Fn<void()> &&callback,
|
||||
const style::icon *icon,
|
||||
const style::icon *iconOver)
|
||||
: Action(
|
||||
parent,
|
||||
st,
|
||||
CreateAction(parent, text, std::move(callback)),
|
||||
icon,
|
||||
iconOver)
|
||||
, _padding(st.itemPadding)
|
||||
, _toggleShift(st.itemToggleShift)
|
||||
, _itemToggle(st.itemToggle)
|
||||
, _itemToggleOver(st.itemToggleOver) {
|
||||
|
||||
const auto processAction = [=] {
|
||||
if (!action()->isCheckable()) {
|
||||
_toggle.reset();
|
||||
return;
|
||||
}
|
||||
if (_toggle) {
|
||||
_toggle->setChecked(action()->isChecked(), anim::type::normal);
|
||||
} else {
|
||||
_toggle = std::make_unique<ToggleView>(
|
||||
st.itemToggle,
|
||||
action()->isChecked(),
|
||||
[=] { update(); });
|
||||
}
|
||||
};
|
||||
processAction();
|
||||
connect(action(), &QAction::changed, [=] { processAction(); });
|
||||
|
||||
selects(
|
||||
) | rpl::start_with_next([=](const CallbackData &data) {
|
||||
if (!_toggle) {
|
||||
return;
|
||||
}
|
||||
_toggle->setStyle(data.selected ? _itemToggleOver : _itemToggle);
|
||||
}, lifetime());
|
||||
|
||||
}
|
||||
|
||||
void Toggle::paintEvent(QPaintEvent *e) {
|
||||
Action::paintEvent(e);
|
||||
if (_toggle) {
|
||||
Painter p(this);
|
||||
const auto toggleSize = _toggle->getSize();
|
||||
_toggle->paint(
|
||||
p,
|
||||
width() - _padding.right() - toggleSize.width() + _toggleShift,
|
||||
(contentHeight() - toggleSize.height()) / 2, width());
|
||||
}
|
||||
}
|
||||
|
||||
void Toggle::finishAnimating() {
|
||||
ItemBase::finishAnimating();
|
||||
if (_toggle) {
|
||||
_toggle->finishAnimating();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Ui::Menu
|
||||
42
ui/widgets/menu/menu_toggle.h
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// 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/widgets/menu/menu_action.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
namespace Ui {
|
||||
class ToggleView;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Ui::Menu {
|
||||
|
||||
class Toggle : public Action {
|
||||
public:
|
||||
Toggle(
|
||||
not_null<RpWidget*> parent,
|
||||
const style::Menu &st,
|
||||
const QString &text,
|
||||
Fn<void()> &&callback,
|
||||
const style::icon *icon,
|
||||
const style::icon *iconOver);
|
||||
|
||||
void finishAnimating() override;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
private:
|
||||
const style::margins &_padding;
|
||||
const int _toggleShift;
|
||||
const style::Toggle &_itemToggle;
|
||||
const style::Toggle &_itemToggleOver;
|
||||
std::unique_ptr<ToggleView> _toggle;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui::Menu
|
||||
|
|
@ -52,12 +52,15 @@ void PopupMenu::init() {
|
|||
hideMenu(true);
|
||||
}, lifetime());
|
||||
|
||||
_menu->setResizedCallback([this] { handleMenuResize(); });
|
||||
_menu->setActivatedCallback([this](QAction *action, int actionTop, TriggeredSource source) {
|
||||
handleActivated(action, actionTop, source);
|
||||
_menu->resizesFromInner(
|
||||
) | rpl::start_with_next([=] {
|
||||
handleMenuResize();
|
||||
}, _menu->lifetime());
|
||||
_menu->setActivatedCallback([this](const Menu::CallbackData &data) {
|
||||
handleActivated(data);
|
||||
});
|
||||
_menu->setTriggeredCallback([this](QAction *action, int actionTop, TriggeredSource source) {
|
||||
handleTriggered(action, actionTop, source);
|
||||
_menu->setTriggeredCallback([this](const Menu::CallbackData &data) {
|
||||
handleTriggered(data);
|
||||
});
|
||||
_menu->setKeyPressDelegate([this](int key) { return handleKeyPress(key); });
|
||||
_menu->setMouseMoveDelegate([this](QPoint globalPosition) { handleMouseMove(globalPosition); });
|
||||
|
|
@ -88,8 +91,9 @@ void PopupMenu::handleMenuResize() {
|
|||
_inner = rect().marginsRemoved(_padding);
|
||||
}
|
||||
|
||||
not_null<QAction*> PopupMenu::addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon, const style::icon *iconOver) {
|
||||
return _menu->addAction(text, receiver, member, icon, iconOver);
|
||||
not_null<QAction*> PopupMenu::addAction(
|
||||
base::unique_qptr<Menu::ItemBase> widget) {
|
||||
return _menu->addAction(std::move(widget));
|
||||
}
|
||||
|
||||
not_null<QAction*> PopupMenu::addAction(const QString &text, Fn<void()> callback, const style::icon *icon, const style::icon *iconOver) {
|
||||
|
|
@ -119,6 +123,10 @@ const std::vector<not_null<QAction*>> &PopupMenu::actions() const {
|
|||
return _menu->actions();
|
||||
}
|
||||
|
||||
bool PopupMenu::empty() const {
|
||||
return _menu->empty();
|
||||
}
|
||||
|
||||
void PopupMenu::paintEvent(QPaintEvent *e) {
|
||||
QPainter p(this);
|
||||
|
||||
|
|
@ -153,9 +161,9 @@ void PopupMenu::paintBg(QPainter &p) {
|
|||
}
|
||||
}
|
||||
|
||||
void PopupMenu::handleActivated(QAction *action, int actionTop, TriggeredSource source) {
|
||||
if (source == TriggeredSource::Mouse) {
|
||||
if (!popupSubmenuFromAction(action, actionTop, source)) {
|
||||
void PopupMenu::handleActivated(const Menu::CallbackData &data) {
|
||||
if (data.source == TriggeredSource::Mouse) {
|
||||
if (!popupSubmenuFromAction(data)) {
|
||||
if (auto currentSubmenu = base::take(_activeSubmenu)) {
|
||||
currentSubmenu->hideMenu(true);
|
||||
}
|
||||
|
|
@ -163,11 +171,11 @@ void PopupMenu::handleActivated(QAction *action, int actionTop, TriggeredSource
|
|||
}
|
||||
}
|
||||
|
||||
void PopupMenu::handleTriggered(QAction *action, int actionTop, TriggeredSource source) {
|
||||
if (!popupSubmenuFromAction(action, actionTop, source)) {
|
||||
void PopupMenu::handleTriggered(const Menu::CallbackData &data) {
|
||||
if (!popupSubmenuFromAction(data)) {
|
||||
_triggering = true;
|
||||
hideMenu();
|
||||
emit action->trigger();
|
||||
emit data.action->trigger();
|
||||
_triggering = false;
|
||||
if (_deleteLater) {
|
||||
_deleteLater = false;
|
||||
|
|
@ -176,12 +184,12 @@ void PopupMenu::handleTriggered(QAction *action, int actionTop, TriggeredSource
|
|||
}
|
||||
}
|
||||
|
||||
bool PopupMenu::popupSubmenuFromAction(QAction *action, int actionTop, TriggeredSource source) {
|
||||
if (auto submenu = _submenus.value(action)) {
|
||||
bool PopupMenu::popupSubmenuFromAction(const Menu::CallbackData &data) {
|
||||
if (auto submenu = _submenus.value(data.action)) {
|
||||
if (_activeSubmenu == submenu) {
|
||||
submenu->hideMenu(true);
|
||||
} else {
|
||||
popupSubmenu(submenu, actionTop, source);
|
||||
popupSubmenu(submenu, data.actionTop, data.source);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
@ -203,9 +211,9 @@ void PopupMenu::popupSubmenu(SubmenuPointer submenu, int actionTop, TriggeredSou
|
|||
}
|
||||
}
|
||||
|
||||
void PopupMenu::forwardKeyPress(int key) {
|
||||
if (!handleKeyPress(key)) {
|
||||
_menu->handleKeyPress(key);
|
||||
void PopupMenu::forwardKeyPress(not_null<QKeyEvent*> e) {
|
||||
if (!handleKeyPress(e->key())) {
|
||||
_menu->handleKeyPress(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -262,7 +270,7 @@ void PopupMenu::hideEvent(QHideEvent *e) {
|
|||
}
|
||||
|
||||
void PopupMenu::keyPressEvent(QKeyEvent *e) {
|
||||
forwardKeyPress(e->key());
|
||||
forwardKeyPress(e);
|
||||
}
|
||||
|
||||
void PopupMenu::mouseMoveEvent(QMouseEvent *e) {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "styles/style_widgets.h"
|
||||
#include "ui/widgets/menu.h"
|
||||
#include "ui/widgets/menu/menu.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/effects/panel_animation.h"
|
||||
#include "ui/round_rect.h"
|
||||
|
|
@ -21,13 +21,18 @@ public:
|
|||
PopupMenu(QWidget *parent, const style::PopupMenu &st = st::defaultPopupMenu);
|
||||
PopupMenu(QWidget *parent, QMenu *menu, const style::PopupMenu &st = st::defaultPopupMenu);
|
||||
|
||||
not_null<QAction*> addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr);
|
||||
[[nodiscard]] const style::PopupMenu &st() const {
|
||||
return _st;
|
||||
}
|
||||
|
||||
not_null<QAction*> addAction(base::unique_qptr<Menu::ItemBase> widget);
|
||||
not_null<QAction*> addAction(const QString &text, Fn<void()> callback, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr);
|
||||
not_null<QAction*> addAction(const QString &text, std::unique_ptr<PopupMenu> submenu);
|
||||
not_null<QAction*> addSeparator();
|
||||
void clearActions();
|
||||
|
||||
const std::vector<not_null<QAction*>> &actions() const;
|
||||
bool empty() const;
|
||||
|
||||
void deleteOnHide(bool del);
|
||||
void popup(const QPoint &p);
|
||||
|
|
@ -41,6 +46,10 @@ public:
|
|||
_reactivateParent = false;
|
||||
}
|
||||
|
||||
[[nodiscard]] not_null<Menu::Menu*> menu() const {
|
||||
return _menu.data();
|
||||
}
|
||||
|
||||
~PopupMenu();
|
||||
|
||||
protected:
|
||||
|
|
@ -75,9 +84,9 @@ private:
|
|||
using TriggeredSource = Menu::TriggeredSource;
|
||||
void handleCompositingUpdate();
|
||||
void handleMenuResize();
|
||||
void handleActivated(QAction *action, int actionTop, TriggeredSource source);
|
||||
void handleTriggered(QAction *action, int actionTop, TriggeredSource source);
|
||||
void forwardKeyPress(int key);
|
||||
void handleActivated(const Menu::CallbackData &data);
|
||||
void handleTriggered(const Menu::CallbackData &data);
|
||||
void forwardKeyPress(not_null<QKeyEvent*> e);
|
||||
bool handleKeyPress(int key);
|
||||
void forwardMouseMove(QPoint globalPosition) {
|
||||
_menu->handleMouseMove(globalPosition);
|
||||
|
|
@ -93,14 +102,14 @@ private:
|
|||
void handleMouseRelease(QPoint globalPosition);
|
||||
|
||||
using SubmenuPointer = QPointer<PopupMenu>;
|
||||
bool popupSubmenuFromAction(QAction *action, int actionTop, TriggeredSource source);
|
||||
bool popupSubmenuFromAction(const Menu::CallbackData &data);
|
||||
void popupSubmenu(SubmenuPointer submenu, int actionTop, TriggeredSource source);
|
||||
void showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource source);
|
||||
|
||||
const style::PopupMenu &_st;
|
||||
|
||||
RoundRect _roundRect;
|
||||
object_ptr<Menu> _menu;
|
||||
object_ptr<Menu::Menu> _menu;
|
||||
|
||||
using Submenus = QMap<QAction*, SubmenuPointer>;
|
||||
Submenus _submenus;
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ RoundButton {
|
|||
textTop: pixels;
|
||||
|
||||
icon: icon;
|
||||
iconOver: icon;
|
||||
iconPosition: point;
|
||||
|
||||
font: font;
|
||||
|
|
@ -135,6 +136,7 @@ Radio {
|
|||
toggledFg: color;
|
||||
diameter: pixels;
|
||||
thickness: pixels;
|
||||
outerSkip: pixels;
|
||||
skip: pixels;
|
||||
duration: int;
|
||||
rippleAreaPadding: pixels;
|
||||
|
|
@ -408,6 +410,18 @@ CrossLineAnimation {
|
|||
stroke: pixels;
|
||||
}
|
||||
|
||||
ArcsAnimation {
|
||||
fg: color;
|
||||
stroke: pixels;
|
||||
space: pixels;
|
||||
duration: int;
|
||||
deltaAngle: int;
|
||||
deltaHeight: pixels;
|
||||
deltaWidth: pixels;
|
||||
startHeight: pixels;
|
||||
startWidth: pixels;
|
||||
}
|
||||
|
||||
MultiSelectItem {
|
||||
padding: margins;
|
||||
maxWidth: pixels;
|
||||
|
|
@ -741,7 +755,8 @@ defaultRadio: Radio {
|
|||
toggledFg: windowBgActive;
|
||||
diameter: 22px;
|
||||
thickness: 2px;
|
||||
skip: 65px; // * 0.1
|
||||
outerSkip: 10px; // * 0.1
|
||||
skip: 60px; // * 0.1
|
||||
duration: 120;
|
||||
rippleAreaPadding: 8px;
|
||||
}
|
||||
|
|
@ -1569,3 +1584,17 @@ defaultWindowTitle: WindowTitle {
|
|||
|
||||
windowShadow: icon {{ "window_shadow", windowShadowFg }};
|
||||
windowShadowShift: 1px;
|
||||
|
||||
callRadius: 6px;
|
||||
callShadow: Shadow {
|
||||
left: icon {{ "calls/call_shadow_left", windowShadowFg }};
|
||||
topLeft: icon {{ "calls/call_shadow_top_left", windowShadowFg }};
|
||||
top: icon {{ "calls/call_shadow_top", windowShadowFg }};
|
||||
topRight: icon {{ "calls/call_shadow_top_left-flip_horizontal", windowShadowFg }};
|
||||
right: icon {{ "calls/call_shadow_left-flip_horizontal", windowShadowFg }};
|
||||
bottomRight: icon {{ "calls/call_shadow_top_left-flip_vertical-flip_horizontal", windowShadowFg }};
|
||||
bottom: icon {{ "calls/call_shadow_top-flip_vertical", windowShadowFg }};
|
||||
bottomLeft: icon {{ "calls/call_shadow_top_left-flip_vertical", windowShadowFg }};
|
||||
extend: margins(9px, 8px, 9px, 10px);
|
||||
fallback: windowShadowFgFallback;
|
||||
}
|
||||
|
|
|
|||