Updated lib_ui sources to TDesktop version 2.6
|
|
@ -69,12 +69,16 @@ PRIVATE
|
||||||
ui/layers/layer_manager.h
|
ui/layers/layer_manager.h
|
||||||
ui/layers/layer_widget.cpp
|
ui/layers/layer_widget.cpp
|
||||||
ui/layers/layer_widget.h
|
ui/layers/layer_widget.h
|
||||||
|
ui/paint/arcs.cpp
|
||||||
|
ui/paint/arcs.h
|
||||||
ui/paint/blob.cpp
|
ui/paint/blob.cpp
|
||||||
ui/paint/blob.h
|
ui/paint/blob.h
|
||||||
ui/paint/blobs.cpp
|
ui/paint/blobs.cpp
|
||||||
ui/paint/blobs.h
|
ui/paint/blobs.h
|
||||||
ui/paint/blobs_linear.cpp
|
ui/paint/blobs_linear.cpp
|
||||||
ui/paint/blobs_linear.h
|
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.cpp
|
||||||
ui/platform/linux/ui_window_linux.h
|
ui/platform/linux/ui_window_linux.h
|
||||||
ui/platform/linux/ui_utility_linux.cpp
|
ui/platform/linux/ui_utility_linux.cpp
|
||||||
|
|
@ -93,8 +97,11 @@ PRIVATE
|
||||||
ui/platform/win/ui_window_win.h
|
ui/platform/win/ui_window_win.h
|
||||||
ui/platform/win/ui_utility_win.cpp
|
ui/platform/win/ui_utility_win.cpp
|
||||||
ui/platform/win/ui_utility_win.h
|
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.cpp
|
||||||
ui/platform/ui_platform_window.h
|
ui/platform/ui_platform_window.h
|
||||||
|
ui/platform/ui_platform_utility.cpp
|
||||||
ui/platform/ui_platform_utility.h
|
ui/platform/ui_platform_utility.h
|
||||||
ui/style/style_core.cpp
|
ui/style/style_core.cpp
|
||||||
ui/style/style_core.h
|
ui/style/style_core.h
|
||||||
|
|
@ -146,8 +153,18 @@ PRIVATE
|
||||||
ui/widgets/input_fields.h
|
ui/widgets/input_fields.h
|
||||||
ui/widgets/labels.cpp
|
ui/widgets/labels.cpp
|
||||||
ui/widgets/labels.h
|
ui/widgets/labels.h
|
||||||
ui/widgets/menu.cpp
|
ui/widgets/menu/menu.cpp
|
||||||
ui/widgets/menu.h
|
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.cpp
|
||||||
ui/widgets/popup_menu.h
|
ui/widgets/popup_menu.h
|
||||||
ui/widgets/scroll_area.cpp
|
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)
|
nice_target_sources(lib_ui ${src_loc} PRIVATE fonts/fonts.qrc)
|
||||||
endif()
|
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
|
target_include_directories(lib_ui
|
||||||
PUBLIC
|
PUBLIC
|
||||||
${src_loc}
|
${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
|
sideBarBadgeBgMuted: #8393a3; // filters side bar unimportant badge background
|
||||||
sideBarBadgeFg: #ffffff; // filters side bar badge text
|
sideBarBadgeFg: #ffffff; // filters side bar badge text
|
||||||
|
|
||||||
|
songCoverOverlayFg: #00000066; // song cover overlay
|
||||||
|
|
||||||
// kotatogram
|
// kotatogram
|
||||||
ktgTopBarBg: topBarBg; // Kotatogram: top bar background
|
ktgTopBarBg: topBarBg; // Kotatogram: top bar background
|
||||||
ktgTopBarNameFg: dialogsNameFg; // Kotatogram: top bar name text
|
ktgTopBarNameFg: dialogsNameFg; // Kotatogram: top bar name text
|
||||||
|
|
|
||||||
|
|
@ -43,11 +43,14 @@ void CrossLineAnimation::paint(
|
||||||
_st.icon.paint(p, left, top, _st.icon.width());
|
_st.icon.paint(p, left, top, _st.icon.width());
|
||||||
}
|
}
|
||||||
} else if (progress == 1.) {
|
} else if (progress == 1.) {
|
||||||
if (_completeCross.isNull()) {
|
auto &complete = colorOverride
|
||||||
|
? _completeCrossOverride
|
||||||
|
: _completeCross;
|
||||||
|
if (complete.isNull()) {
|
||||||
fillFrame(progress, colorOverride);
|
fillFrame(progress, colorOverride);
|
||||||
_completeCross = _frame;
|
complete = _frame;
|
||||||
}
|
}
|
||||||
p.drawImage(left, top, _completeCross);
|
p.drawImage(left, top, complete);
|
||||||
} else {
|
} else {
|
||||||
fillFrame(progress, colorOverride);
|
fillFrame(progress, colorOverride);
|
||||||
p.drawImage(left, top, _frame);
|
p.drawImage(left, top, _frame);
|
||||||
|
|
@ -94,6 +97,8 @@ void CrossLineAnimation::fillFrame(
|
||||||
|
|
||||||
void CrossLineAnimation::invalidate() {
|
void CrossLineAnimation::invalidate() {
|
||||||
_completeCross = QImage();
|
_completeCross = QImage();
|
||||||
|
_completeCrossOverride = QImage();
|
||||||
|
_strokePen = QPen(_st.fg, _st.stroke, Qt::SolidLine, Qt::RoundCap);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
||||||
|
|
@ -39,10 +39,11 @@ private:
|
||||||
const style::CrossLineAnimation &_st;
|
const style::CrossLineAnimation &_st;
|
||||||
const bool _reversed;
|
const bool _reversed;
|
||||||
const QPen _transparentPen;
|
const QPen _transparentPen;
|
||||||
const QPen _strokePen;
|
QPen _strokePen;
|
||||||
QLineF _line;
|
QLineF _line;
|
||||||
QImage _frame;
|
QImage _frame;
|
||||||
QImage _completeCross;
|
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());
|
+ 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++) {
|
for (auto i = 0; i < _segmentsCount; i++) {
|
||||||
auto &segment = segmentAt(i);
|
auto &segment = segmentAt(i);
|
||||||
segment.progress += (segment.speed * _minSpeed)
|
segment.progress += (_minSpeed + level * _maxSpeed * speedScale)
|
||||||
+ level * segment.speed * _maxSpeed * speedScale;
|
* segment.speed
|
||||||
|
* rate;
|
||||||
if (segment.progress >= 1) {
|
if (segment.progress >= 1) {
|
||||||
generateSingleValues(i);
|
generateSingleValues(i);
|
||||||
generateTwoValues(i);
|
generateTwoValues(i);
|
||||||
|
|
@ -152,9 +153,9 @@ void RadialBlob::generateTwoValues(int i) {
|
||||||
radius.setNext(_radiuses.min + std::abs(RandomAdditional()) * radDiff);
|
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;
|
_scale = level;
|
||||||
Blob::update(level, speedScale);
|
Blob::update(level, speedScale, rate);
|
||||||
}
|
}
|
||||||
|
|
||||||
Blob::Segment &RadialBlob::segmentAt(int i) {
|
Blob::Segment &RadialBlob::segmentAt(int i) {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ public:
|
||||||
Blob(int n, float minSpeed = 0, float maxSpeed = 0);
|
Blob(int n, float minSpeed = 0, float maxSpeed = 0);
|
||||||
virtual ~Blob() = default;
|
virtual ~Blob() = default;
|
||||||
|
|
||||||
void update(float level, float speedScale);
|
void update(float level, float speedScale, float64 rate);
|
||||||
void generateBlob();
|
void generateBlob();
|
||||||
|
|
||||||
void setRadiuses(Radiuses values);
|
void setRadiuses(Radiuses values);
|
||||||
|
|
@ -59,7 +59,7 @@ public:
|
||||||
RadialBlob(int n, float minScale, float minSpeed = 0, float maxSpeed = 0);
|
RadialBlob(int n, float minScale, float minSpeed = 0, float maxSpeed = 0);
|
||||||
|
|
||||||
void paint(Painter &p, const QBrush &brush, float outerScale = 1.);
|
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:
|
private:
|
||||||
struct Segment : Blob::Segment {
|
struct Segment : Blob::Segment {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,13 @@
|
||||||
|
|
||||||
namespace Ui::Paint {
|
namespace Ui::Paint {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kRateLimitF = 1000. / 60.;
|
||||||
|
constexpr auto kRateLimit = int(kRateLimitF + 0.5); // Round.
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
Blobs::Blobs(
|
Blobs::Blobs(
|
||||||
std::vector<BlobData> blobDatas,
|
std::vector<BlobData> blobDatas,
|
||||||
float levelDuration,
|
float levelDuration,
|
||||||
|
|
@ -76,7 +83,6 @@ void Blobs::resetLevel() {
|
||||||
void Blobs::paint(Painter &p, const QBrush &brush, float outerScale) {
|
void Blobs::paint(Painter &p, const QBrush &brush, float outerScale) {
|
||||||
const auto opacity = p.opacity();
|
const auto opacity = p.opacity();
|
||||||
for (auto i = 0; i < _blobs.size(); i++) {
|
for (auto i = 0; i < _blobs.size(); i++) {
|
||||||
_blobs[i].update(_levelValue.current(), _blobDatas[i].speedScale);
|
|
||||||
const auto alpha = _blobDatas[i].alpha;
|
const auto alpha = _blobDatas[i].alpha;
|
||||||
if (alpha != 1.) {
|
if (alpha != 1.) {
|
||||||
p.setOpacity(opacity * alpha);
|
p.setOpacity(opacity * alpha);
|
||||||
|
|
@ -89,7 +95,15 @@ void Blobs::paint(Painter &p, const QBrush &brush, float outerScale) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Blobs::updateLevel(crl::time dt) {
|
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 {
|
float64 Blobs::currentLevel() const {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,13 @@
|
||||||
|
|
||||||
namespace Ui::Paint {
|
namespace Ui::Paint {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kRateLimitF = 1000. / 60.;
|
||||||
|
constexpr auto kRateLimit = int(kRateLimitF + 0.5); // Round.
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
LinearBlobs::LinearBlobs(
|
LinearBlobs::LinearBlobs(
|
||||||
std::vector<BlobData> blobDatas,
|
std::vector<BlobData> blobDatas,
|
||||||
float levelDuration,
|
float levelDuration,
|
||||||
|
|
@ -75,7 +82,6 @@ void LinearBlobs::paint(Painter &p, const QBrush &brush, int width) {
|
||||||
PainterHighQualityEnabler hq(p);
|
PainterHighQualityEnabler hq(p);
|
||||||
const auto opacity = p.opacity();
|
const auto opacity = p.opacity();
|
||||||
for (auto i = 0; i < _blobs.size(); i++) {
|
for (auto i = 0; i < _blobs.size(); i++) {
|
||||||
_blobs[i].update(_levelValue.current(), _blobDatas[i].speedScale);
|
|
||||||
const auto alpha = _blobDatas[i].alpha;
|
const auto alpha = _blobDatas[i].alpha;
|
||||||
if (alpha != 1.) {
|
if (alpha != 1.) {
|
||||||
p.setOpacity(opacity * alpha);
|
p.setOpacity(opacity * alpha);
|
||||||
|
|
@ -88,8 +94,8 @@ void LinearBlobs::paint(Painter &p, const QBrush &brush, int width) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void LinearBlobs::updateLevel(crl::time dt) {
|
void LinearBlobs::updateLevel(crl::time dt) {
|
||||||
const auto d = (dt > 20) ? 17 : dt;
|
const auto limitedDt = (dt > 20) ? kRateLimit : dt;
|
||||||
_levelValue.update(d);
|
_levelValue.update(limitedDt);
|
||||||
|
|
||||||
const auto level = (float)currentLevel();
|
const auto level = (float)currentLevel();
|
||||||
for (auto i = 0; i < _blobs.size(); i++) {
|
for (auto i = 0; i < _blobs.size(); i++) {
|
||||||
|
|
@ -97,6 +103,10 @@ void LinearBlobs::updateLevel(crl::time dt) {
|
||||||
_blobs[i].setRadiuses({
|
_blobs[i].setRadiuses({
|
||||||
data.minRadius,
|
data.minRadius,
|
||||||
data.idleRadius + (data.maxRadius - data.idleRadius) * level });
|
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 "ui/ui_log.h"
|
||||||
#include "base/platform/base_platform_info.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/qt_adapters.h"
|
||||||
#include "base/flat_set.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 <QtCore/QPoint>
|
||||||
#include <QtGui/QScreen>
|
#include <QtGui/QScreen>
|
||||||
|
#include <QtGui/QWindow>
|
||||||
#include <QtWidgets/QApplication>
|
#include <QtWidgets/QApplication>
|
||||||
#include <qpa/qplatformnativeinterface.h>
|
#include <qpa/qplatformnativeinterface.h>
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(QMargins);
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
namespace Platform {
|
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() {
|
bool IsApplicationActive() {
|
||||||
return QApplication::activeWindow() != nullptr;
|
return QApplication::activeWindow() != nullptr;
|
||||||
|
|
@ -50,5 +184,130 @@ bool TranslucentWindowsSupported(QPoint globalPosition) {
|
||||||
void IgnoreAllActivation(not_null<QWidget*> widget) {
|
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 Platform
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@
|
||||||
//
|
//
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/platform/ui_platform_utility.h"
|
||||||
|
|
||||||
class QPainter;
|
class QPainter;
|
||||||
class QPaintEvent;
|
class QPaintEvent;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
//
|
//
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/platform/ui_platform_utility.h"
|
||||||
#include "base/platform/base_platform_info.h"
|
#include "base/platform/base_platform_info.h"
|
||||||
#include <QtCore/QPoint>
|
#include <QtCore/QPoint>
|
||||||
|
|
||||||
|
|
@ -23,5 +24,21 @@ inline constexpr bool UseMainQueueGeneric() {
|
||||||
return ::Platform::IsMacStoreBuild();
|
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 Platform
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
||||||
|
|
@ -99,5 +99,15 @@ void DrainMainQueue() {
|
||||||
void IgnoreAllActivation(not_null<QWidget*> widget) {
|
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 Platform
|
||||||
} // namespace Ui
|
} // 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
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/platform/ui_platform_window_title.h"
|
||||||
|
|
||||||
class QPoint;
|
class QPoint;
|
||||||
class QPainter;
|
class QPainter;
|
||||||
class QPaintEvent;
|
class QPaintEvent;
|
||||||
|
|
@ -30,6 +32,15 @@ void IgnoreAllActivation(not_null<QWidget*> widget);
|
||||||
[[nodiscard]] constexpr bool UseMainQueueGeneric();
|
[[nodiscard]] constexpr bool UseMainQueueGeneric();
|
||||||
void DrainMainQueue(); // Needed only if UseMainQueueGeneric() is false.
|
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 Platform
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,21 +6,31 @@
|
||||||
//
|
//
|
||||||
#include "ui/platform/ui_platform_window.h"
|
#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/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/QWindow>
|
||||||
#include <QtGui/QtEvents>
|
#include <QtGui/QtEvents>
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
namespace Platform {
|
namespace Platform {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
[[nodiscard]] const style::Shadow &Shadow() {
|
||||||
|
return st::callShadow;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
BasicWindowHelper::BasicWindowHelper(not_null<RpWidget*> window)
|
BasicWindowHelper::BasicWindowHelper(not_null<RpWidget*> window)
|
||||||
: _window(window) {
|
: _window(window) {
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
|
|
||||||
_window->setWindowFlag(Qt::Window);
|
_window->setWindowFlag(Qt::Window);
|
||||||
#else // Qt >= 5.9
|
|
||||||
_window->setWindowFlags(_window->windowFlags() | Qt::Window);
|
|
||||||
#endif // Qt >= 5.9
|
|
||||||
}
|
}
|
||||||
|
|
||||||
not_null<RpWidget*> BasicWindowHelper::body() {
|
not_null<RpWidget*> BasicWindowHelper::body() {
|
||||||
|
|
@ -100,8 +110,6 @@ void BasicWindowHelper::setupBodyTitleAreaEvents() {
|
||||||
&& (static_cast<QMouseEvent*>(e.get())->button()
|
&& (static_cast<QMouseEvent*>(e.get())->button()
|
||||||
== Qt::LeftButton)) {
|
== Qt::LeftButton)) {
|
||||||
_mousePressed = true;
|
_mousePressed = true;
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) || defined DESKTOP_APP_QT_PATCHED
|
|
||||||
} else if (e->type() == QEvent::MouseMove) {
|
} else if (e->type() == QEvent::MouseMove) {
|
||||||
const auto mouseEvent = static_cast<QMouseEvent*>(e.get());
|
const auto mouseEvent = static_cast<QMouseEvent*>(e.get());
|
||||||
if (_mousePressed
|
if (_mousePressed
|
||||||
|
|
@ -109,7 +117,6 @@ void BasicWindowHelper::setupBodyTitleAreaEvents() {
|
||||||
&& !_window->isFullScreen()
|
&& !_window->isFullScreen()
|
||||||
#endif // !Q_OS_WIN
|
#endif // !Q_OS_WIN
|
||||||
&& (hitTest() & WindowTitleHitTestFlag::Move)) {
|
&& (hitTest() & WindowTitleHitTestFlag::Move)) {
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
if (_window->isFullScreen()) {
|
if (_window->isFullScreen()) {
|
||||||
// On Windows we just jump out of fullscreen
|
// On Windows we just jump out of fullscreen
|
||||||
|
|
@ -121,10 +128,270 @@ void BasicWindowHelper::setupBodyTitleAreaEvents() {
|
||||||
_mousePressed = false;
|
_mousePressed = false;
|
||||||
_window->windowHandle()->startSystemMove();
|
_window->windowHandle()->startSystemMove();
|
||||||
}
|
}
|
||||||
#endif // Qt >= 5.15 || DESKTOP_APP_QT_PATCHED
|
|
||||||
}
|
}
|
||||||
}, body()->lifetime());
|
}, 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 Platform
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ using WindowTitleHitTestFlags = base::flags<WindowTitleHitTestFlag>;
|
||||||
|
|
||||||
namespace Platform {
|
namespace Platform {
|
||||||
|
|
||||||
|
class DefaultTitleWidget;
|
||||||
|
|
||||||
class BasicWindowHelper {
|
class BasicWindowHelper {
|
||||||
public:
|
public:
|
||||||
explicit BasicWindowHelper(not_null<RpWidget*> window);
|
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(
|
[[nodiscard]] std::unique_ptr<BasicWindowHelper> CreateSpecialWindowHelper(
|
||||||
not_null<RpWidget*> window);
|
not_null<RpWidget*> window);
|
||||||
|
|
||||||
|
|
@ -65,7 +96,7 @@ private:
|
||||||
if (auto special = CreateSpecialWindowHelper(window)) {
|
if (auto special = CreateSpecialWindowHelper(window)) {
|
||||||
return special;
|
return special;
|
||||||
}
|
}
|
||||||
return std::make_unique<BasicWindowHelper>(window);
|
return std::make_unique<DefaultWindowHelper>(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Platform
|
} // 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 "ui/platform/win/ui_utility_win.h"
|
||||||
|
|
||||||
#include <QtWidgets/QApplication>
|
|
||||||
|
|
||||||
#include "base/platform/win/base_windows_h.h"
|
#include "base/platform/win/base_windows_h.h"
|
||||||
|
|
||||||
|
#include <QtWidgets/QApplication>
|
||||||
|
#include <QtGui/QWindow>
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
namespace Platform {
|
namespace Platform {
|
||||||
|
|
||||||
|
|
@ -44,5 +45,27 @@ void IgnoreAllActivation(not_null<QWidget*> widget) {
|
||||||
ShowWindow(handle, SW_SHOW);
|
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 Platform
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@
|
||||||
//
|
//
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/platform/ui_platform_utility.h"
|
||||||
|
|
||||||
#include <QtCore/QPoint>
|
#include <QtCore/QPoint>
|
||||||
|
|
||||||
class QPainter;
|
class QPainter;
|
||||||
|
|
@ -40,5 +42,17 @@ inline constexpr bool UseMainQueueGeneric() {
|
||||||
return true;
|
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 Platform
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
||||||
|
|
@ -19,170 +19,6 @@
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
namespace Platform {
|
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)
|
TitleWidget::TitleWidget(not_null<RpWidget*> parent)
|
||||||
: RpWidget(parent)
|
: RpWidget(parent)
|
||||||
, _controls(this, st::defaultWindowTitle)
|
, _controls(this, st::defaultWindowTitle)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
//
|
//
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/platform/ui_platform_window_title.h"
|
||||||
#include "ui/rp_widget.h"
|
#include "ui/rp_widget.h"
|
||||||
#include "base/object_ptr.h"
|
#include "base/object_ptr.h"
|
||||||
|
|
||||||
|
|
@ -38,41 +39,6 @@ enum class HitTestResult {
|
||||||
TopLeft,
|
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 {
|
class TitleWidget : public RpWidget {
|
||||||
public:
|
public:
|
||||||
explicit TitleWidget(not_null<RpWidget*> parent);
|
explicit TitleWidget(not_null<RpWidget*> parent);
|
||||||
|
|
|
||||||
|
|
@ -160,8 +160,10 @@ bool RpWidgetMethods::handleEvent(QEvent *event) {
|
||||||
return eventHook(event);
|
return eventHook(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
RpWidgetMethods::Initer::Initer(QWidget *parent) {
|
RpWidgetMethods::Initer::Initer(QWidget *parent, bool setZeroGeometry) {
|
||||||
parent->setGeometry(0, 0, 0, 0);
|
if (setZeroGeometry) {
|
||||||
|
parent->setGeometry(0, 0, 0, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RpWidgetMethods::visibilityChangedHook(bool wasVisible, bool nowVisible) {
|
void RpWidgetMethods::visibilityChangedHook(bool wasVisible, bool nowVisible) {
|
||||||
|
|
|
||||||
|
|
@ -243,7 +243,7 @@ using RpWidgetParent = std::conditional_t<
|
||||||
TWidget,
|
TWidget,
|
||||||
TWidgetHelper<Widget>>;
|
TWidgetHelper<Widget>>;
|
||||||
|
|
||||||
template <typename Widget>
|
template <typename Widget, typename Traits>
|
||||||
class RpWidgetWrap;
|
class RpWidgetWrap;
|
||||||
|
|
||||||
class RpWidgetMethods {
|
class RpWidgetMethods {
|
||||||
|
|
@ -281,7 +281,7 @@ protected:
|
||||||
virtual bool eventHook(QEvent *event) = 0;
|
virtual bool eventHook(QEvent *event) = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template <typename Widget>
|
template <typename Widget, typename Traits>
|
||||||
friend class RpWidgetWrap;
|
friend class RpWidgetWrap;
|
||||||
|
|
||||||
struct EventStreams {
|
struct EventStreams {
|
||||||
|
|
@ -292,7 +292,7 @@ private:
|
||||||
rpl::event_stream<> alive;
|
rpl::event_stream<> alive;
|
||||||
};
|
};
|
||||||
struct Initer {
|
struct Initer {
|
||||||
Initer(QWidget *parent);
|
Initer(QWidget *parent, bool setZeroGeometry);
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual void callSetVisible(bool visible) = 0;
|
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
|
class RpWidgetWrap
|
||||||
: public RpWidgetParent<Widget>
|
: public RpWidgetParent<Widget>
|
||||||
, public RpWidgetMethods {
|
, public RpWidgetMethods {
|
||||||
using Self = RpWidgetWrap<Widget>;
|
using Self = RpWidgetWrap<Widget, Traits>;
|
||||||
using Parent = RpWidgetParent<Widget>;
|
using Parent = RpWidgetParent<Widget>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
@ -362,7 +366,7 @@ private:
|
||||||
return this->isHidden();
|
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() {
|
void RippleButton::clearState() {
|
||||||
AbstractButton::clearState();
|
AbstractButton::clearState();
|
||||||
|
finishAnimating();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RippleButton::finishAnimating() {
|
||||||
if (_ripple) {
|
if (_ripple) {
|
||||||
_ripple.reset();
|
_ripple.reset();
|
||||||
update();
|
update();
|
||||||
|
|
@ -116,6 +120,13 @@ void RippleButton::setForceRippled(
|
||||||
update();
|
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) {
|
void RippleButton::paintRipple(QPainter &p, int x, int y, const QColor *colorOverride) {
|
||||||
if (_ripple) {
|
if (_ripple) {
|
||||||
_ripple->paint(p, x, y, width(), colorOverride);
|
_ripple->paint(p, x, y, width(), colorOverride);
|
||||||
|
|
@ -195,6 +206,11 @@ void FlatButton::setWidth(int w) {
|
||||||
resize(_width, height());
|
resize(_width, height());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FlatButton::setColorOverride(std::optional<QColor> color) {
|
||||||
|
_colorOverride = color;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
int32 FlatButton::textWidth() const {
|
int32 FlatButton::textWidth() const {
|
||||||
return _st.font->width(_text);
|
return _st.font->width(_text);
|
||||||
}
|
}
|
||||||
|
|
@ -214,7 +230,11 @@ void FlatButton::paintEvent(QPaintEvent *e) {
|
||||||
|
|
||||||
p.setFont(isOver() ? _st.overFont : _st.font);
|
p.setFont(isOver() ? _st.overFont : _st.font);
|
||||||
p.setRenderHint(QPainter::TextAntialiasing);
|
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(
|
const auto textRect = inner.marginsRemoved(
|
||||||
_textMargins
|
_textMargins
|
||||||
|
|
@ -361,7 +381,7 @@ void RoundButton::paintEvent(QPaintEvent *e) {
|
||||||
drawRect(_roundRectOver);
|
drawRect(_roundRectOver);
|
||||||
}
|
}
|
||||||
|
|
||||||
paintRipple(p, rounded.x(), rounded.y());
|
paintRipple(p, rounded.topLeft());
|
||||||
|
|
||||||
p.setFont(_st.font);
|
p.setFont(_st.font);
|
||||||
const auto textTop = _st.padding.top() + _st.textTop;
|
const auto textTop = _st.padding.top() + _st.textTop;
|
||||||
|
|
@ -392,7 +412,10 @@ void RoundButton::paintEvent(QPaintEvent *e) {
|
||||||
_numbers->paint(p, textLeft, textTop, width());
|
_numbers->paint(p, textLeft, textTop, width());
|
||||||
}
|
}
|
||||||
if (!_st.icon.empty()) {
|
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) {
|
void IconButton::paintEvent(QPaintEvent *e) {
|
||||||
Painter p(this);
|
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 down = isDown();
|
||||||
auto overIconOpacity = (down || forceRippled()) ? 1. : _a_over.value(isOver() ? 1. : 0.);
|
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.);
|
auto shown = _showAnimation.value(_shown ? 1. : 0.);
|
||||||
p.setOpacity(shown);
|
p.setOpacity(shown);
|
||||||
|
|
||||||
paintRipple(p, _st.crossPosition.x(), _st.crossPosition.y());
|
paintRipple(p, _st.crossPosition);
|
||||||
|
|
||||||
auto loading = 0.;
|
auto loading = 0.;
|
||||||
if (_loadingAnimation.animating()) {
|
if (_loadingAnimation.animating()) {
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,17 @@ public:
|
||||||
|
|
||||||
void clearState() override;
|
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();
|
~RippleButton();
|
||||||
|
|
||||||
|
|
@ -84,6 +94,7 @@ public:
|
||||||
|
|
||||||
void setText(const QString &text);
|
void setText(const QString &text);
|
||||||
void setWidth(int w);
|
void setWidth(int w);
|
||||||
|
void setColorOverride(std::optional<QColor> color);
|
||||||
void setTextMargins(QMargins margins);
|
void setTextMargins(QMargins margins);
|
||||||
|
|
||||||
int32 textWidth() const;
|
int32 textWidth() const;
|
||||||
|
|
@ -97,6 +108,7 @@ private:
|
||||||
QString _text;
|
QString _text;
|
||||||
QMargins _textMargins;
|
QMargins _textMargins;
|
||||||
int _width = 0;
|
int _width = 0;
|
||||||
|
std::optional<QColor> _colorOverride;
|
||||||
|
|
||||||
const style::FlatButton &_st;
|
const style::FlatButton &_st;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,7 @@ void CallButton::paintEvent(QPaintEvent *e) {
|
||||||
} else {
|
} else {
|
||||||
rippleColorInterpolated = anim::color(_stFrom->button.ripple.color, _stTo->button.ripple.color, _progress);
|
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);
|
auto positionFrom = iconPosition(_stFrom);
|
||||||
if (paintFrom) {
|
if (paintFrom) {
|
||||||
|
|
|
||||||
|
|
@ -328,6 +328,15 @@ BlobsWidget::BlobsWidget(
|
||||||
void BlobsWidget::init() {
|
void BlobsWidget::init() {
|
||||||
setAttribute(Qt::WA_TransparentForMouseEvents);
|
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;
|
const auto s = _blobs.maxRadius() * 2 * kGlowPaddingFactor;
|
||||||
resize(s, s);
|
resize(s, s);
|
||||||
|
|
@ -351,6 +360,8 @@ void BlobsWidget::init() {
|
||||||
Painter p(this);
|
Painter p(this);
|
||||||
PainterHighQualityEnabler hq(p);
|
PainterHighQualityEnabler hq(p);
|
||||||
|
|
||||||
|
p.setPen(Qt::NoPen);
|
||||||
|
|
||||||
// Glow.
|
// Glow.
|
||||||
const auto s = kGlowMinScale
|
const auto s = kGlowMinScale
|
||||||
+ (1. - kGlowMinScale) * _blobs.currentLevel();
|
+ (1. - kGlowMinScale) * _blobs.currentLevel();
|
||||||
|
|
@ -369,37 +380,41 @@ void BlobsWidget::init() {
|
||||||
_switchConnectingProgress / kBlobPartAnimation)))
|
_switchConnectingProgress / kBlobPartAnimation)))
|
||||||
: _blobsScaleEnter;
|
: _blobsScaleEnter;
|
||||||
_blobs.paint(p, _blobBrush, scale);
|
_blobs.paint(p, _blobBrush, scale);
|
||||||
|
p.translate(-_center, -_center);
|
||||||
|
|
||||||
|
if (scale < 1.) {
|
||||||
|
cutRect(p, _circleRect);
|
||||||
|
}
|
||||||
|
|
||||||
// Main circle.
|
// Main circle.
|
||||||
p.translate(-_center, -_center);
|
const auto circleProgress =
|
||||||
p.setPen(Qt::NoPen);
|
Clamp(_switchConnectingProgress - kBlobPartAnimation)
|
||||||
p.setBrush(_blobBrush);
|
/ kFillCirclePartAnimation;
|
||||||
p.drawEllipse(_circleRect);
|
const auto skipColoredCircle = (circleProgress == 1.);
|
||||||
|
|
||||||
|
if (!skipColoredCircle) {
|
||||||
|
p.setBrush(_blobBrush);
|
||||||
|
p.drawEllipse(_circleRect);
|
||||||
|
}
|
||||||
|
|
||||||
if (_switchConnectingProgress > 0.) {
|
if (_switchConnectingProgress > 0.) {
|
||||||
p.resetTransform();
|
p.resetTransform();
|
||||||
|
|
||||||
const auto circleProgress =
|
|
||||||
Clamp(_switchConnectingProgress - kBlobPartAnimation)
|
|
||||||
/ kFillCirclePartAnimation;
|
|
||||||
|
|
||||||
const auto mF = (_circleRect.width() / 2) * (1. - circleProgress);
|
const auto mF = (_circleRect.width() / 2) * (1. - circleProgress);
|
||||||
const auto cutOutRect = _circleRect.marginsRemoved(
|
const auto cutOutRect = _circleRect.marginsRemoved(
|
||||||
QMarginsF(mF, mF, mF, mF));
|
QMarginsF(mF, mF, mF, mF));
|
||||||
|
|
||||||
p.setPen(Qt::NoPen);
|
if (!skipColoredCircle) {
|
||||||
p.setBrush(st::callConnectingRadial.color);
|
p.setBrush(st::callConnectingRadial.color);
|
||||||
p.setOpacity(circleProgress);
|
p.setOpacity(circleProgress);
|
||||||
p.drawEllipse(_circleRect);
|
p.drawEllipse(_circleRect);
|
||||||
|
}
|
||||||
|
|
||||||
p.setOpacity(1.);
|
p.setOpacity(1.);
|
||||||
|
|
||||||
|
cutRect(p, cutOutRect);
|
||||||
|
|
||||||
p.setBrush(st::callIconBg);
|
p.setBrush(st::callIconBg);
|
||||||
|
|
||||||
p.save();
|
|
||||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
|
||||||
p.drawEllipse(cutOutRect);
|
|
||||||
p.restore();
|
|
||||||
|
|
||||||
p.drawEllipse(cutOutRect);
|
p.drawEllipse(cutOutRect);
|
||||||
}
|
}
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
@ -553,25 +568,33 @@ void CallMuteButton::init() {
|
||||||
}, _centerLabel->lifetime());
|
}, _centerLabel->lifetime());
|
||||||
_centerLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
_centerLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
|
||||||
_radialInfo.rawShowProgress.value(
|
rpl::combine(
|
||||||
) | rpl::start_with_next([=](float64 value) {
|
_radialInfo.rawShowProgress.value(),
|
||||||
|
anim::Disables()
|
||||||
|
) | rpl::start_with_next([=](float64 value, bool disabled) {
|
||||||
auto &info = _radialInfo;
|
auto &info = _radialInfo;
|
||||||
info.realShowProgress = (1. - value) / kRadialEndPartAnimation;
|
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->stop();
|
||||||
_radial = nullptr;
|
_radial = nullptr;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((value > 0.) && !anim::Disabled() && !_radial) {
|
if ((value > 0.) && !disabled && !_radial) {
|
||||||
_radial = std::make_unique<InfiniteRadialAnimation>(
|
_radial = std::make_unique<InfiniteRadialAnimation>(
|
||||||
[=] { _content->update(); },
|
[=] { _content->update(); },
|
||||||
_radialInfo.st);
|
_radialInfo.st);
|
||||||
_radial->start();
|
_radial->start();
|
||||||
}
|
}
|
||||||
if ((info.realShowProgress < 1.) && !info.isDirectionToShow) {
|
if ((info.realShowProgress < 1.) && !info.isDirectionToShow) {
|
||||||
_radial->stop(anim::type::instant);
|
if (_radial) {
|
||||||
_radial->start();
|
_radial->stop(anim::type::instant);
|
||||||
|
_radial->start();
|
||||||
|
}
|
||||||
info.state = std::nullopt;
|
info.state = std::nullopt;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -721,6 +744,8 @@ void CallMuteButton::init() {
|
||||||
const auto to = r.arcFrom - kRadialFinishArcShift;
|
const auto to = r.arcFrom - kRadialFinishArcShift;
|
||||||
ComputeRadialFinish(r.arcFrom, radialProgress, to);
|
ComputeRadialFinish(r.arcFrom, radialProgress, to);
|
||||||
ComputeRadialFinish(r.arcLength, radialProgress);
|
ComputeRadialFinish(r.arcLength, radialProgress);
|
||||||
|
} else {
|
||||||
|
r.arcLength = RadialState::kFull;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto opacity = (radialProgress > kOverlapProgressRadialHide)
|
const auto opacity = (radialProgress > kOverlapProgressRadialHide)
|
||||||
|
|
|
||||||
|
|
@ -320,7 +320,8 @@ void RadioView::paint(Painter &p, int left, int top, int outerWidth) {
|
||||||
p.setBrush(_st->bg);
|
p.setBrush(_st->bg);
|
||||||
//int32 skip = qCeil(_st->thickness / 2.);
|
//int32 skip = qCeil(_st->thickness / 2.);
|
||||||
//p.drawEllipse(_checkRect.marginsRemoved(QMargins(skip, skip, skip, skip)));
|
//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) {
|
if (toggled > 0) {
|
||||||
p.setPen(Qt::NoPen);
|
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(*_untoggledOverride, _st->toggledFg, toggled)
|
||||||
: anim::brush(_st->untoggledFg, _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));
|
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);
|
//int32 fskip = qFloor(checkSkip), cskip = qCeil(checkSkip);
|
||||||
//if (2 * fskip < _checkRect.width()) {
|
//if (2 * fskip < _checkRect.width()) {
|
||||||
|
|
@ -569,11 +570,7 @@ void Checkbox::paintEvent(QPaintEvent *e) {
|
||||||
p.setOpacity(_st.disabledOpacity);
|
p.setOpacity(_st.disabledOpacity);
|
||||||
} else {
|
} else {
|
||||||
auto color = anim::color(_st.rippleBg, _st.rippleBgActive, active);
|
auto color = anim::color(_st.rippleBg, _st.rippleBgActive, active);
|
||||||
paintRipple(
|
paintRipple(p, check.topLeft() + _st.rippleAreaPosition, &color);
|
||||||
p,
|
|
||||||
check.x() + _st.rippleAreaPosition.x(),
|
|
||||||
check.y() + _st.rippleAreaPosition.y(),
|
|
||||||
&color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto realCheckRect = myrtlrect(check);
|
auto realCheckRect = myrtlrect(check);
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ namespace Ui {
|
||||||
|
|
||||||
DropdownMenu::DropdownMenu(QWidget *parent, const style::DropdownMenu &st) : InnerDropdown(parent, st.wrap)
|
DropdownMenu::DropdownMenu(QWidget *parent, const style::DropdownMenu &st) : InnerDropdown(parent, st.wrap)
|
||||||
, _st(st) {
|
, _st(st) {
|
||||||
_menu = setOwnedWidget(object_ptr<Menu>(this, _st.menu));
|
_menu = setOwnedWidget(object_ptr<Menu::Menu>(this, _st.menu));
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,12 +33,15 @@ DropdownMenu::DropdownMenu(QWidget *parent, const style::DropdownMenu &st) : Inn
|
||||||
void DropdownMenu::init() {
|
void DropdownMenu::init() {
|
||||||
InnerDropdown::setHiddenCallback([this] { hideFinish(); });
|
InnerDropdown::setHiddenCallback([this] { hideFinish(); });
|
||||||
|
|
||||||
_menu->setResizedCallback([this] { resizeToContent(); });
|
_menu->resizesFromInner(
|
||||||
_menu->setActivatedCallback([this](QAction *action, int actionTop, TriggeredSource source) {
|
) | rpl::start_with_next([=] {
|
||||||
handleActivated(action, actionTop, source);
|
resizeToContent();
|
||||||
|
}, _menu->lifetime());
|
||||||
|
_menu->setActivatedCallback([this](const Menu::CallbackData &data) {
|
||||||
|
handleActivated(data);
|
||||||
});
|
});
|
||||||
_menu->setTriggeredCallback([this](QAction *action, int actionTop, TriggeredSource source) {
|
_menu->setTriggeredCallback([this](const Menu::CallbackData &data) {
|
||||||
handleTriggered(action, actionTop, source);
|
handleTriggered(data);
|
||||||
});
|
});
|
||||||
_menu->setKeyPressDelegate([this](int key) { return handleKeyPress(key); });
|
_menu->setKeyPressDelegate([this](int key) { return handleKeyPress(key); });
|
||||||
_menu->setMouseMoveDelegate([this](QPoint globalPosition) { handleMouseMove(globalPosition); });
|
_menu->setMouseMoveDelegate([this](QPoint globalPosition) { handleMouseMove(globalPosition); });
|
||||||
|
|
@ -50,8 +53,9 @@ void DropdownMenu::init() {
|
||||||
hide();
|
hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
not_null<QAction*> DropdownMenu::addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon, const style::icon *iconOver) {
|
not_null<QAction*> DropdownMenu::addAction(
|
||||||
return _menu->addAction(text, receiver, member, icon, iconOver);
|
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) {
|
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();
|
return _menu->actions();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DropdownMenu::handleActivated(QAction *action, int actionTop, TriggeredSource source) {
|
bool DropdownMenu::empty() const {
|
||||||
if (source == TriggeredSource::Mouse) {
|
return _menu->empty();
|
||||||
if (!popupSubmenuFromAction(action, actionTop, source)) {
|
}
|
||||||
|
|
||||||
|
void DropdownMenu::handleActivated(const Menu::CallbackData &data) {
|
||||||
|
if (data.source == TriggeredSource::Mouse) {
|
||||||
|
if (!popupSubmenuFromAction(data)) {
|
||||||
if (auto currentSubmenu = base::take(_activeSubmenu)) {
|
if (auto currentSubmenu = base::take(_activeSubmenu)) {
|
||||||
currentSubmenu->hideMenu(true);
|
currentSubmenu->hideMenu(true);
|
||||||
}
|
}
|
||||||
|
|
@ -83,11 +91,11 @@ void DropdownMenu::handleActivated(QAction *action, int actionTop, TriggeredSour
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DropdownMenu::handleTriggered(QAction *action, int actionTop, TriggeredSource source) {
|
void DropdownMenu::handleTriggered(const Menu::CallbackData &data) {
|
||||||
if (!popupSubmenuFromAction(action, actionTop, source)) {
|
if (!popupSubmenuFromAction(data)) {
|
||||||
hideMenu();
|
hideMenu();
|
||||||
_triggering = true;
|
_triggering = true;
|
||||||
emit action->trigger();
|
emit data.action->trigger();
|
||||||
_triggering = false;
|
_triggering = false;
|
||||||
if (_deleteLater) {
|
if (_deleteLater) {
|
||||||
_deleteLater = false;
|
_deleteLater = false;
|
||||||
|
|
@ -97,7 +105,7 @@ void DropdownMenu::handleTriggered(QAction *action, int actionTop, TriggeredSour
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not ready with submenus yet.
|
// 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 (auto submenu = _submenus.value(action)) {
|
||||||
// if (_activeSubmenu == submenu) {
|
// if (_activeSubmenu == submenu) {
|
||||||
// submenu->hideMenu(true);
|
// submenu->hideMenu(true);
|
||||||
|
|
@ -126,9 +134,9 @@ bool DropdownMenu::popupSubmenuFromAction(QAction *action, int actionTop, Trigge
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
|
||||||
void DropdownMenu::forwardKeyPress(int key) {
|
void DropdownMenu::forwardKeyPress(not_null<QKeyEvent*> e) {
|
||||||
if (!handleKeyPress(key)) {
|
if (!handleKeyPress(e->key())) {
|
||||||
_menu->handleKeyPress(key);
|
_menu->handleKeyPress(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -185,7 +193,7 @@ void DropdownMenu::hideEvent(QHideEvent *e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DropdownMenu::keyPressEvent(QKeyEvent *e) {
|
void DropdownMenu::keyPressEvent(QKeyEvent *e) {
|
||||||
forwardKeyPress(e->key());
|
forwardKeyPress(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DropdownMenu::mouseMoveEvent(QMouseEvent *e) {
|
void DropdownMenu::mouseMoveEvent(QMouseEvent *e) {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
#include "styles/style_widgets.h"
|
#include "styles/style_widgets.h"
|
||||||
#include "ui/widgets/inner_dropdown.h"
|
#include "ui/widgets/inner_dropdown.h"
|
||||||
#include "ui/widgets/menu.h"
|
#include "ui/widgets/menu/menu.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
|
|
||||||
|
|
@ -18,7 +18,7 @@ class DropdownMenu : public InnerDropdown {
|
||||||
public:
|
public:
|
||||||
DropdownMenu(QWidget *parent, const style::DropdownMenu &st = st::defaultDropdownMenu);
|
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*> addAction(const QString &text, Fn<void()> callback, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr);
|
||||||
not_null<QAction*> addSeparator();
|
not_null<QAction*> addSeparator();
|
||||||
void clearActions();
|
void clearActions();
|
||||||
|
|
@ -28,6 +28,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<not_null<QAction*>> &actions() const;
|
const std::vector<not_null<QAction*>> &actions() const;
|
||||||
|
bool empty() const;
|
||||||
|
|
||||||
~DropdownMenu();
|
~DropdownMenu();
|
||||||
|
|
||||||
|
|
@ -56,9 +57,9 @@ private:
|
||||||
void hideFinish();
|
void hideFinish();
|
||||||
|
|
||||||
using TriggeredSource = Menu::TriggeredSource;
|
using TriggeredSource = Menu::TriggeredSource;
|
||||||
void handleActivated(QAction *action, int actionTop, TriggeredSource source);
|
void handleActivated(const Menu::CallbackData &data);
|
||||||
void handleTriggered(QAction *action, int actionTop, TriggeredSource source);
|
void handleTriggered(const Menu::CallbackData &data);
|
||||||
void forwardKeyPress(int key);
|
void forwardKeyPress(not_null<QKeyEvent*> e);
|
||||||
bool handleKeyPress(int key);
|
bool handleKeyPress(int key);
|
||||||
void forwardMouseMove(QPoint globalPosition) {
|
void forwardMouseMove(QPoint globalPosition) {
|
||||||
_menu->handleMouseMove(globalPosition);
|
_menu->handleMouseMove(globalPosition);
|
||||||
|
|
@ -74,14 +75,14 @@ private:
|
||||||
void handleMouseRelease(QPoint globalPosition);
|
void handleMouseRelease(QPoint globalPosition);
|
||||||
|
|
||||||
using SubmenuPointer = QPointer<DropdownMenu>;
|
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 popupSubmenu(SubmenuPointer submenu, int actionTop, TriggeredSource source);
|
||||||
void showMenu(const QPoint &p, DropdownMenu *parent, TriggeredSource source);
|
void showMenu(const QPoint &p, DropdownMenu *parent, TriggeredSource source);
|
||||||
|
|
||||||
const style::DropdownMenu &_st;
|
const style::DropdownMenu &_st;
|
||||||
Fn<void()> _hiddenCallback;
|
Fn<void()> _hiddenCallback;
|
||||||
|
|
||||||
QPointer<Menu> _menu;
|
QPointer<Menu::Menu> _menu;
|
||||||
|
|
||||||
// Not ready with submenus yet.
|
// Not ready with submenus yet.
|
||||||
//using Submenus = QMap<QAction*, SubmenuPointer>;
|
//using Submenus = QMap<QAction*, SubmenuPointer>;
|
||||||
|
|
|
||||||
|
|
@ -2659,12 +2659,18 @@ bool InputField::ShouldSubmit(
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputField::keyPressEventInner(QKeyEvent *e) {
|
void InputField::keyPressEventInner(QKeyEvent *e) {
|
||||||
bool shift = e->modifiers().testFlag(Qt::ShiftModifier), alt = e->modifiers().testFlag(Qt::AltModifier);
|
const auto shift = e->modifiers().testFlag(Qt::ShiftModifier);
|
||||||
bool macmeta = Platform::IsMac() && e->modifiers().testFlag(Qt::ControlModifier) && !e->modifiers().testFlag(Qt::MetaModifier) && !e->modifiers().testFlag(Qt::AltModifier);
|
const auto alt = e->modifiers().testFlag(Qt::AltModifier);
|
||||||
bool ctrl = e->modifiers().testFlag(Qt::ControlModifier) || e->modifiers().testFlag(Qt::MetaModifier);
|
const auto macmeta = Platform::IsMac()
|
||||||
bool enterSubmit = (_mode != Mode::MultiLine)
|
&& 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());
|
|| 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
|
if (e->key() == Qt::Key_Left
|
||||||
|| e->key() == Qt::Key_Right
|
|| e->key() == Qt::Key_Right
|
||||||
|| e->key() == Qt::Key_Up
|
|| e->key() == Qt::Key_Up
|
||||||
|
|
@ -2674,12 +2680,12 @@ void InputField::keyPressEventInner(QKeyEvent *e) {
|
||||||
_reverseMarkdownReplacement = false;
|
_reverseMarkdownReplacement = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (macmeta && e->key() == Qt::Key_Backspace) {
|
if (macmeta && backspace) {
|
||||||
QTextCursor tc(textCursor()), start(tc);
|
QTextCursor tc(textCursor()), start(tc);
|
||||||
start.movePosition(QTextCursor::StartOfLine);
|
start.movePosition(QTextCursor::StartOfLine);
|
||||||
tc.setPosition(start.position(), QTextCursor::KeepAnchor);
|
tc.setPosition(start.position(), QTextCursor::KeepAnchor);
|
||||||
tc.removeSelectedText();
|
tc.removeSelectedText();
|
||||||
} else if (e->key() == Qt::Key_Backspace
|
} else if (backspace
|
||||||
&& e->modifiers() == 0
|
&& e->modifiers() == 0
|
||||||
&& revertFormatReplace()) {
|
&& revertFormatReplace()) {
|
||||||
e->accept();
|
e->accept();
|
||||||
|
|
@ -2716,12 +2722,22 @@ void InputField::keyPressEventInner(QKeyEvent *e) {
|
||||||
} else {
|
} else {
|
||||||
const auto text = e->text();
|
const auto text = e->text();
|
||||||
const auto oldPosition = textCursor().position();
|
const auto oldPosition = textCursor().position();
|
||||||
if (enter && ctrl) {
|
const auto oldModifiers = e->modifiers();
|
||||||
e->setModifiers(e->modifiers() & ~Qt::ControlModifier);
|
const auto allowedModifiers = (enter && ctrl)
|
||||||
} else if (enter && shift) {
|
? (~Qt::ControlModifier)
|
||||||
e->setModifiers(e->modifiers() & ~Qt::ShiftModifier);
|
: (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);
|
_inner->QTextEdit::keyPressEvent(e);
|
||||||
|
if (changeModifiers) {
|
||||||
|
e->setModifiers(oldModifiers);
|
||||||
|
}
|
||||||
auto cursor = textCursor();
|
auto cursor = textCursor();
|
||||||
if (cursor.position() == oldPosition) {
|
if (cursor.position() == oldPosition) {
|
||||||
bool check = false;
|
bool check = false;
|
||||||
|
|
@ -4023,18 +4039,20 @@ PasswordInput::PasswordInput(
|
||||||
setEchoMode(QLineEdit::Password);
|
setEchoMode(QLineEdit::Password);
|
||||||
}
|
}
|
||||||
|
|
||||||
PortInput::PortInput(
|
NumberInput::NumberInput(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
const style::InputField &st,
|
const style::InputField &st,
|
||||||
rpl::producer<QString> placeholder,
|
rpl::producer<QString> placeholder,
|
||||||
const QString &val)
|
const QString &value,
|
||||||
: MaskedInputField(parent, st, std::move(placeholder), val) {
|
int limit)
|
||||||
if (!val.toInt() || val.toInt() > 65535) {
|
: MaskedInputField(parent, st, std::move(placeholder), value)
|
||||||
|
, _limit(limit) {
|
||||||
|
if (!value.toInt() || (limit > 0 && value.toInt() > limit)) {
|
||||||
setText(QString());
|
setText(QString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PortInput::correctValue(
|
void NumberInput::correctValue(
|
||||||
const QString &was,
|
const QString &was,
|
||||||
int wasCursor,
|
int wasCursor,
|
||||||
QString &now,
|
QString &now,
|
||||||
|
|
@ -4052,7 +4070,7 @@ void PortInput::correctValue(
|
||||||
if (!newText.toInt()) {
|
if (!newText.toInt()) {
|
||||||
newText = QString();
|
newText = QString();
|
||||||
newPos = 0;
|
newPos = 0;
|
||||||
} else if (newText.toInt() > 65535) {
|
} else if (_limit > 0 && newText.toInt() > _limit) {
|
||||||
newText = was;
|
newText = was;
|
||||||
newPos = wasCursor;
|
newPos = wasCursor;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -697,9 +697,14 @@ public:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class PortInput : public MaskedInputField {
|
class NumberInput : public MaskedInputField {
|
||||||
public:
|
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:
|
protected:
|
||||||
void correctValue(
|
void correctValue(
|
||||||
|
|
@ -708,6 +713,9 @@ protected:
|
||||||
QString &now,
|
QString &now,
|
||||||
int &nowCursor) override;
|
int &nowCursor) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int _limit = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class HexInput : public MaskedInputField {
|
class HexInput : public MaskedInputField {
|
||||||
|
|
|
||||||
|
|
@ -637,12 +637,17 @@ void FlatLabel::showContextMenu(QContextMenuEvent *e, ContextMenuReason reason)
|
||||||
_contextMenu = new PopupMenu(this);
|
_contextMenu = new PopupMenu(this);
|
||||||
|
|
||||||
if (fullSelection && !_contextCopyText.isEmpty()) {
|
if (fullSelection && !_contextCopyText.isEmpty()) {
|
||||||
_contextMenu->addAction(_contextCopyText, this, SLOT(onCopyContextText()));
|
_contextMenu->addAction(
|
||||||
|
_contextCopyText,
|
||||||
|
[=] { onCopyContextText(); });
|
||||||
} else if (uponSelection && !fullSelection) {
|
} else if (uponSelection && !fullSelection) {
|
||||||
const auto text = Integration::Instance().phraseContextCopySelected();
|
_contextMenu->addAction(
|
||||||
_contextMenu->addAction(text, this, SLOT(onCopySelectedText()));
|
Integration::Instance().phraseContextCopySelected(),
|
||||||
|
[=] { onCopySelectedText(); });
|
||||||
} else if (_selectable && !hasSelection && !_contextCopyText.isEmpty()) {
|
} else if (_selectable && !hasSelection && !_contextCopyText.isEmpty()) {
|
||||||
_contextMenu->addAction(_contextCopyText, this, SLOT(onCopyContextText()));
|
_contextMenu->addAction(
|
||||||
|
_contextCopyText,
|
||||||
|
[=] { onCopyContextText(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (const auto link = ClickHandler::getActive()) {
|
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
|
#pragma once
|
||||||
|
|
||||||
|
#include "base/unique_qptr.h"
|
||||||
#include "ui/rp_widget.h"
|
#include "ui/rp_widget.h"
|
||||||
|
#include "ui/widgets/menu/menu_common.h"
|
||||||
#include "styles/style_widgets.h"
|
#include "styles/style_widgets.h"
|
||||||
|
|
||||||
#include <QtWidgets/QMenu>
|
#include <QtWidgets/QMenu>
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui::Menu {
|
||||||
|
|
||||||
class ToggleView;
|
class ItemBase;
|
||||||
class RippleAnimation;
|
class RippleAnimation;
|
||||||
|
|
||||||
class Menu : public RpWidget {
|
class Menu : public RpWidget {
|
||||||
|
|
@ -22,19 +24,27 @@ public:
|
||||||
Menu(QWidget *parent, QMenu *menu, const style::Menu &st = st::defaultMenu);
|
Menu(QWidget *parent, QMenu *menu, const style::Menu &st = st::defaultMenu);
|
||||||
~Menu();
|
~Menu();
|
||||||
|
|
||||||
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::Menu &st() const {
|
||||||
not_null<QAction*> addAction(const QString &text, Fn<void()> callback, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr);
|
return _st;
|
||||||
not_null<QAction*> addAction(const QString &text, std::unique_ptr<QMenu> submenu);
|
}
|
||||||
|
|
||||||
|
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();
|
not_null<QAction*> addSeparator();
|
||||||
void clearActions();
|
void clearActions();
|
||||||
void finishAnimating();
|
void finishAnimating();
|
||||||
|
|
||||||
|
bool empty() const;
|
||||||
|
|
||||||
void clearSelection();
|
void clearSelection();
|
||||||
|
|
||||||
enum class TriggeredSource {
|
|
||||||
Mouse,
|
|
||||||
Keyboard,
|
|
||||||
};
|
|
||||||
void setChildShown(bool shown) {
|
void setChildShown(bool shown) {
|
||||||
_childShown = shown;
|
_childShown = shown;
|
||||||
}
|
}
|
||||||
|
|
@ -43,21 +53,17 @@ public:
|
||||||
|
|
||||||
const std::vector<not_null<QAction*>> &actions() const;
|
const std::vector<not_null<QAction*>> &actions() const;
|
||||||
|
|
||||||
void setResizedCallback(Fn<void()> callback) {
|
void setActivatedCallback(Fn<void(const CallbackData &data)> callback) {
|
||||||
_resizedCallback = std::move(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setActivatedCallback(Fn<void(QAction *action, int actionTop, TriggeredSource source)> callback) {
|
|
||||||
_activatedCallback = std::move(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);
|
_triggeredCallback = std::move(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setKeyPressDelegate(Fn<bool(int key)> delegate) {
|
void setKeyPressDelegate(Fn<bool(int key)> delegate) {
|
||||||
_keyPressDelegate = std::move(delegate);
|
_keyPressDelegate = std::move(delegate);
|
||||||
}
|
}
|
||||||
void handleKeyPress(int key);
|
void handleKeyPress(not_null<QKeyEvent*> e);
|
||||||
|
|
||||||
void setMouseMoveDelegate(Fn<void(QPoint globalPosition)> delegate) {
|
void setMouseMoveDelegate(Fn<void(QPoint globalPosition)> delegate) {
|
||||||
_mouseMoveDelegate = std::move(delegate);
|
_mouseMoveDelegate = std::move(delegate);
|
||||||
|
|
@ -74,41 +80,36 @@ public:
|
||||||
}
|
}
|
||||||
void handleMouseRelease(QPoint globalPosition);
|
void handleMouseRelease(QPoint globalPosition);
|
||||||
|
|
||||||
|
rpl::producer<> resizesFromInner() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void paintEvent(QPaintEvent *e) override;
|
|
||||||
void keyPressEvent(QKeyEvent *e) override;
|
void keyPressEvent(QKeyEvent *e) override;
|
||||||
void mouseMoveEvent(QMouseEvent *e) override;
|
void mouseMoveEvent(QMouseEvent *e) override;
|
||||||
void mousePressEvent(QMouseEvent *e) override;
|
void mousePressEvent(QMouseEvent *e) override;
|
||||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||||
void enterEventHook(QEvent *e) override;
|
|
||||||
void leaveEventHook(QEvent *e) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct ActionData;
|
|
||||||
|
|
||||||
void updateSelected(QPoint globalPosition);
|
void updateSelected(QPoint globalPosition);
|
||||||
void actionChanged();
|
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
// Returns the new width.
|
not_null<QAction*> addAction(
|
||||||
int processAction(not_null<QAction*> action, int index, int width);
|
not_null<QAction*> action,
|
||||||
not_null<QAction*> addAction(not_null<QAction*> action, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr);
|
const style::icon *icon = nullptr,
|
||||||
|
const style::icon *iconOver = nullptr);
|
||||||
|
|
||||||
void setSelected(int selected);
|
void setSelected(int selected, bool isMouseSelection);
|
||||||
void setPressed(int pressed);
|
|
||||||
void clearMouseSelection();
|
void clearMouseSelection();
|
||||||
|
|
||||||
int itemTop(int index);
|
|
||||||
void updateItem(int index);
|
|
||||||
void updateSelectedItem();
|
|
||||||
void itemPressed(TriggeredSource source);
|
void itemPressed(TriggeredSource source);
|
||||||
void itemReleased(TriggeredSource source);
|
|
||||||
|
ItemBase *findSelectedAction() const;
|
||||||
|
|
||||||
|
void resizeFromInner(int w, int h);
|
||||||
|
|
||||||
const style::Menu &_st;
|
const style::Menu &_st;
|
||||||
|
|
||||||
Fn<void()> _resizedCallback;
|
Fn<void(const CallbackData &data)> _activatedCallback;
|
||||||
Fn<void(QAction *action, int actionTop, TriggeredSource source)> _activatedCallback;
|
Fn<void(const CallbackData &data)> _triggeredCallback;
|
||||||
Fn<void(QAction *action, int actionTop, TriggeredSource source)> _triggeredCallback;
|
|
||||||
Fn<bool(int key)> _keyPressDelegate;
|
Fn<bool(int key)> _keyPressDelegate;
|
||||||
Fn<void(QPoint globalPosition)> _mouseMoveDelegate;
|
Fn<void(QPoint globalPosition)> _mouseMoveDelegate;
|
||||||
Fn<void(QPoint globalPosition)> _mousePressDelegate;
|
Fn<void(QPoint globalPosition)> _mousePressDelegate;
|
||||||
|
|
@ -116,17 +117,14 @@ private:
|
||||||
|
|
||||||
QMenu *_wappedMenu = nullptr;
|
QMenu *_wappedMenu = nullptr;
|
||||||
std::vector<not_null<QAction*>> _actions;
|
std::vector<not_null<QAction*>> _actions;
|
||||||
std::vector<ActionData> _actionsData;
|
std::vector<base::unique_qptr<ItemBase>> _actionWidgets;
|
||||||
|
|
||||||
int _forceWidth = 0;
|
int _forceWidth = 0;
|
||||||
int _itemHeight, _separatorHeight;
|
|
||||||
|
|
||||||
bool _mouseSelection = false;
|
|
||||||
|
|
||||||
int _selected = -1;
|
|
||||||
int _pressed = -1;
|
|
||||||
bool _childShown = false;
|
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);
|
hideMenu(true);
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
_menu->setResizedCallback([this] { handleMenuResize(); });
|
_menu->resizesFromInner(
|
||||||
_menu->setActivatedCallback([this](QAction *action, int actionTop, TriggeredSource source) {
|
) | rpl::start_with_next([=] {
|
||||||
handleActivated(action, actionTop, source);
|
handleMenuResize();
|
||||||
|
}, _menu->lifetime());
|
||||||
|
_menu->setActivatedCallback([this](const Menu::CallbackData &data) {
|
||||||
|
handleActivated(data);
|
||||||
});
|
});
|
||||||
_menu->setTriggeredCallback([this](QAction *action, int actionTop, TriggeredSource source) {
|
_menu->setTriggeredCallback([this](const Menu::CallbackData &data) {
|
||||||
handleTriggered(action, actionTop, source);
|
handleTriggered(data);
|
||||||
});
|
});
|
||||||
_menu->setKeyPressDelegate([this](int key) { return handleKeyPress(key); });
|
_menu->setKeyPressDelegate([this](int key) { return handleKeyPress(key); });
|
||||||
_menu->setMouseMoveDelegate([this](QPoint globalPosition) { handleMouseMove(globalPosition); });
|
_menu->setMouseMoveDelegate([this](QPoint globalPosition) { handleMouseMove(globalPosition); });
|
||||||
|
|
@ -88,8 +91,9 @@ void PopupMenu::handleMenuResize() {
|
||||||
_inner = rect().marginsRemoved(_padding);
|
_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) {
|
not_null<QAction*> PopupMenu::addAction(
|
||||||
return _menu->addAction(text, receiver, member, icon, iconOver);
|
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) {
|
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();
|
return _menu->actions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PopupMenu::empty() const {
|
||||||
|
return _menu->empty();
|
||||||
|
}
|
||||||
|
|
||||||
void PopupMenu::paintEvent(QPaintEvent *e) {
|
void PopupMenu::paintEvent(QPaintEvent *e) {
|
||||||
QPainter p(this);
|
QPainter p(this);
|
||||||
|
|
||||||
|
|
@ -153,9 +161,9 @@ void PopupMenu::paintBg(QPainter &p) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PopupMenu::handleActivated(QAction *action, int actionTop, TriggeredSource source) {
|
void PopupMenu::handleActivated(const Menu::CallbackData &data) {
|
||||||
if (source == TriggeredSource::Mouse) {
|
if (data.source == TriggeredSource::Mouse) {
|
||||||
if (!popupSubmenuFromAction(action, actionTop, source)) {
|
if (!popupSubmenuFromAction(data)) {
|
||||||
if (auto currentSubmenu = base::take(_activeSubmenu)) {
|
if (auto currentSubmenu = base::take(_activeSubmenu)) {
|
||||||
currentSubmenu->hideMenu(true);
|
currentSubmenu->hideMenu(true);
|
||||||
}
|
}
|
||||||
|
|
@ -163,11 +171,11 @@ void PopupMenu::handleActivated(QAction *action, int actionTop, TriggeredSource
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PopupMenu::handleTriggered(QAction *action, int actionTop, TriggeredSource source) {
|
void PopupMenu::handleTriggered(const Menu::CallbackData &data) {
|
||||||
if (!popupSubmenuFromAction(action, actionTop, source)) {
|
if (!popupSubmenuFromAction(data)) {
|
||||||
_triggering = true;
|
_triggering = true;
|
||||||
hideMenu();
|
hideMenu();
|
||||||
emit action->trigger();
|
emit data.action->trigger();
|
||||||
_triggering = false;
|
_triggering = false;
|
||||||
if (_deleteLater) {
|
if (_deleteLater) {
|
||||||
_deleteLater = false;
|
_deleteLater = false;
|
||||||
|
|
@ -176,12 +184,12 @@ void PopupMenu::handleTriggered(QAction *action, int actionTop, TriggeredSource
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PopupMenu::popupSubmenuFromAction(QAction *action, int actionTop, TriggeredSource source) {
|
bool PopupMenu::popupSubmenuFromAction(const Menu::CallbackData &data) {
|
||||||
if (auto submenu = _submenus.value(action)) {
|
if (auto submenu = _submenus.value(data.action)) {
|
||||||
if (_activeSubmenu == submenu) {
|
if (_activeSubmenu == submenu) {
|
||||||
submenu->hideMenu(true);
|
submenu->hideMenu(true);
|
||||||
} else {
|
} else {
|
||||||
popupSubmenu(submenu, actionTop, source);
|
popupSubmenu(submenu, data.actionTop, data.source);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -203,9 +211,9 @@ void PopupMenu::popupSubmenu(SubmenuPointer submenu, int actionTop, TriggeredSou
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PopupMenu::forwardKeyPress(int key) {
|
void PopupMenu::forwardKeyPress(not_null<QKeyEvent*> e) {
|
||||||
if (!handleKeyPress(key)) {
|
if (!handleKeyPress(e->key())) {
|
||||||
_menu->handleKeyPress(key);
|
_menu->handleKeyPress(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -262,7 +270,7 @@ void PopupMenu::hideEvent(QHideEvent *e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void PopupMenu::keyPressEvent(QKeyEvent *e) {
|
void PopupMenu::keyPressEvent(QKeyEvent *e) {
|
||||||
forwardKeyPress(e->key());
|
forwardKeyPress(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PopupMenu::mouseMoveEvent(QMouseEvent *e) {
|
void PopupMenu::mouseMoveEvent(QMouseEvent *e) {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "styles/style_widgets.h"
|
#include "styles/style_widgets.h"
|
||||||
#include "ui/widgets/menu.h"
|
#include "ui/widgets/menu/menu.h"
|
||||||
#include "ui/effects/animations.h"
|
#include "ui/effects/animations.h"
|
||||||
#include "ui/effects/panel_animation.h"
|
#include "ui/effects/panel_animation.h"
|
||||||
#include "ui/round_rect.h"
|
#include "ui/round_rect.h"
|
||||||
|
|
@ -21,13 +21,18 @@ public:
|
||||||
PopupMenu(QWidget *parent, const style::PopupMenu &st = st::defaultPopupMenu);
|
PopupMenu(QWidget *parent, const style::PopupMenu &st = st::defaultPopupMenu);
|
||||||
PopupMenu(QWidget *parent, QMenu *menu, 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, 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*> addAction(const QString &text, std::unique_ptr<PopupMenu> submenu);
|
||||||
not_null<QAction*> addSeparator();
|
not_null<QAction*> addSeparator();
|
||||||
void clearActions();
|
void clearActions();
|
||||||
|
|
||||||
const std::vector<not_null<QAction*>> &actions() const;
|
const std::vector<not_null<QAction*>> &actions() const;
|
||||||
|
bool empty() const;
|
||||||
|
|
||||||
void deleteOnHide(bool del);
|
void deleteOnHide(bool del);
|
||||||
void popup(const QPoint &p);
|
void popup(const QPoint &p);
|
||||||
|
|
@ -41,6 +46,10 @@ public:
|
||||||
_reactivateParent = false;
|
_reactivateParent = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] not_null<Menu::Menu*> menu() const {
|
||||||
|
return _menu.data();
|
||||||
|
}
|
||||||
|
|
||||||
~PopupMenu();
|
~PopupMenu();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
@ -75,9 +84,9 @@ private:
|
||||||
using TriggeredSource = Menu::TriggeredSource;
|
using TriggeredSource = Menu::TriggeredSource;
|
||||||
void handleCompositingUpdate();
|
void handleCompositingUpdate();
|
||||||
void handleMenuResize();
|
void handleMenuResize();
|
||||||
void handleActivated(QAction *action, int actionTop, TriggeredSource source);
|
void handleActivated(const Menu::CallbackData &data);
|
||||||
void handleTriggered(QAction *action, int actionTop, TriggeredSource source);
|
void handleTriggered(const Menu::CallbackData &data);
|
||||||
void forwardKeyPress(int key);
|
void forwardKeyPress(not_null<QKeyEvent*> e);
|
||||||
bool handleKeyPress(int key);
|
bool handleKeyPress(int key);
|
||||||
void forwardMouseMove(QPoint globalPosition) {
|
void forwardMouseMove(QPoint globalPosition) {
|
||||||
_menu->handleMouseMove(globalPosition);
|
_menu->handleMouseMove(globalPosition);
|
||||||
|
|
@ -93,14 +102,14 @@ private:
|
||||||
void handleMouseRelease(QPoint globalPosition);
|
void handleMouseRelease(QPoint globalPosition);
|
||||||
|
|
||||||
using SubmenuPointer = QPointer<PopupMenu>;
|
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 popupSubmenu(SubmenuPointer submenu, int actionTop, TriggeredSource source);
|
||||||
void showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource source);
|
void showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource source);
|
||||||
|
|
||||||
const style::PopupMenu &_st;
|
const style::PopupMenu &_st;
|
||||||
|
|
||||||
RoundRect _roundRect;
|
RoundRect _roundRect;
|
||||||
object_ptr<Menu> _menu;
|
object_ptr<Menu::Menu> _menu;
|
||||||
|
|
||||||
using Submenus = QMap<QAction*, SubmenuPointer>;
|
using Submenus = QMap<QAction*, SubmenuPointer>;
|
||||||
Submenus _submenus;
|
Submenus _submenus;
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,7 @@ RoundButton {
|
||||||
textTop: pixels;
|
textTop: pixels;
|
||||||
|
|
||||||
icon: icon;
|
icon: icon;
|
||||||
|
iconOver: icon;
|
||||||
iconPosition: point;
|
iconPosition: point;
|
||||||
|
|
||||||
font: font;
|
font: font;
|
||||||
|
|
@ -135,6 +136,7 @@ Radio {
|
||||||
toggledFg: color;
|
toggledFg: color;
|
||||||
diameter: pixels;
|
diameter: pixels;
|
||||||
thickness: pixels;
|
thickness: pixels;
|
||||||
|
outerSkip: pixels;
|
||||||
skip: pixels;
|
skip: pixels;
|
||||||
duration: int;
|
duration: int;
|
||||||
rippleAreaPadding: pixels;
|
rippleAreaPadding: pixels;
|
||||||
|
|
@ -408,6 +410,18 @@ CrossLineAnimation {
|
||||||
stroke: pixels;
|
stroke: pixels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ArcsAnimation {
|
||||||
|
fg: color;
|
||||||
|
stroke: pixels;
|
||||||
|
space: pixels;
|
||||||
|
duration: int;
|
||||||
|
deltaAngle: int;
|
||||||
|
deltaHeight: pixels;
|
||||||
|
deltaWidth: pixels;
|
||||||
|
startHeight: pixels;
|
||||||
|
startWidth: pixels;
|
||||||
|
}
|
||||||
|
|
||||||
MultiSelectItem {
|
MultiSelectItem {
|
||||||
padding: margins;
|
padding: margins;
|
||||||
maxWidth: pixels;
|
maxWidth: pixels;
|
||||||
|
|
@ -741,7 +755,8 @@ defaultRadio: Radio {
|
||||||
toggledFg: windowBgActive;
|
toggledFg: windowBgActive;
|
||||||
diameter: 22px;
|
diameter: 22px;
|
||||||
thickness: 2px;
|
thickness: 2px;
|
||||||
skip: 65px; // * 0.1
|
outerSkip: 10px; // * 0.1
|
||||||
|
skip: 60px; // * 0.1
|
||||||
duration: 120;
|
duration: 120;
|
||||||
rippleAreaPadding: 8px;
|
rippleAreaPadding: 8px;
|
||||||
}
|
}
|
||||||
|
|
@ -1569,3 +1584,17 @@ defaultWindowTitle: WindowTitle {
|
||||||
|
|
||||||
windowShadow: icon {{ "window_shadow", windowShadowFg }};
|
windowShadow: icon {{ "window_shadow", windowShadowFg }};
|
||||||
windowShadowShift: 1px;
|
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;
|
||||||
|
}
|
||||||
|
|
|
||||||