ui_utility.cpp 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. // This file is part of Desktop App Toolkit,
  2. // a set of libraries for developing nice desktop applications.
  3. //
  4. // For license and copyright information please follow this link:
  5. // https://github.com/desktop-app/legal/blob/master/LEGAL
  6. //
  7. #include "ui/ui_utility.h"
  8. #include "base/platform/base_platform_info.h"
  9. #include "ui/integration.h"
  10. #include "ui/platform/ui_platform_utility.h"
  11. #include "ui/style/style_core.h"
  12. #include <QtWidgets/QApplication>
  13. #include <QtGui/QWindow>
  14. #include <QtGui/QtEvents>
  15. #include <QWheelEvent>
  16. #include <array>
  17. namespace Ui {
  18. namespace {
  19. constexpr auto kDefaultWheelScrollLines = 3;
  20. constexpr auto kMagicScrollMultiplier = 2.5;
  21. class WidgetCreator : public QWidget {
  22. public:
  23. static void Create(not_null<QWidget*> widget) {
  24. volatile auto unknown = widget.get();
  25. static_cast<WidgetCreator*>(unknown)->create();
  26. }
  27. };
  28. void CreateWidgetStateRecursive(not_null<QWidget*> target) {
  29. if (!target->testAttribute(Qt::WA_WState_Created)) {
  30. if (!target->isWindow()) {
  31. CreateWidgetStateRecursive(target->parentWidget());
  32. WidgetCreator::Create(target);
  33. }
  34. }
  35. }
  36. void SendPendingEventsRecursive(QWidget *target, bool parentHiddenFlag) {
  37. auto wasVisible = target->isVisible();
  38. if (!wasVisible) {
  39. target->setAttribute(Qt::WA_WState_Visible, true);
  40. }
  41. if (target->testAttribute(Qt::WA_PendingMoveEvent)) {
  42. target->setAttribute(Qt::WA_PendingMoveEvent, false);
  43. QMoveEvent e(target->pos(), QPoint());
  44. QCoreApplication::sendEvent(target, &e);
  45. }
  46. if (target->testAttribute(Qt::WA_PendingResizeEvent)) {
  47. target->setAttribute(Qt::WA_PendingResizeEvent, false);
  48. QResizeEvent e(target->size(), QSize());
  49. QCoreApplication::sendEvent(target, &e);
  50. }
  51. auto removeVisibleFlag = [&] {
  52. return parentHiddenFlag
  53. || target->testAttribute(Qt::WA_WState_Hidden);
  54. };
  55. auto &children = target->children();
  56. for (auto i = 0; i < children.size(); ++i) {
  57. auto child = children[i];
  58. if (child->isWidgetType()) {
  59. auto widget = static_cast<QWidget*>(child);
  60. if (!widget->isWindow()) {
  61. if (!widget->testAttribute(Qt::WA_WState_Created)) {
  62. WidgetCreator::Create(widget);
  63. }
  64. SendPendingEventsRecursive(widget, removeVisibleFlag());
  65. }
  66. }
  67. }
  68. if (removeVisibleFlag()) {
  69. target->setAttribute(Qt::WA_WState_Visible, false);
  70. }
  71. }
  72. } // namespace
  73. bool AppInFocus() {
  74. return QApplication::focusWidget() != nullptr;
  75. }
  76. bool InFocusChain(not_null<const QWidget*> widget) {
  77. if (const auto top = widget->window()) {
  78. if (auto focused = top->focusWidget()) {
  79. return !widget->isHidden()
  80. && (focused == widget
  81. || widget->isAncestorOf(focused));
  82. }
  83. }
  84. return false;
  85. }
  86. void SendPendingMoveResizeEvents(not_null<QWidget*> target) {
  87. CreateWidgetStateRecursive(target);
  88. SendPendingEventsRecursive(target, !target->isVisible());
  89. }
  90. void MarkDirtyOpaqueChildrenRecursive(not_null<QWidget*> target) {
  91. target->resize(target->size()); // Calls setDirtyOpaqueRegion().
  92. for (const auto child : target->children()) {
  93. if (const auto widget = qobject_cast<QWidget*>(child)) {
  94. MarkDirtyOpaqueChildrenRecursive(widget);
  95. }
  96. }
  97. }
  98. QPixmap GrabWidget(not_null<QWidget*> target, QRect rect, QColor bg) {
  99. SendPendingMoveResizeEvents(target);
  100. if (rect.isNull()) {
  101. rect = target->rect();
  102. }
  103. auto result = QPixmap(rect.size() * style::DevicePixelRatio());
  104. result.setDevicePixelRatio(style::DevicePixelRatio());
  105. if (!target->testAttribute(Qt::WA_OpaquePaintEvent)) {
  106. result.fill(bg);
  107. }
  108. {
  109. QPainter p(&result);
  110. RenderWidget(p, target, QPoint(), rect);
  111. }
  112. return result;
  113. }
  114. QImage GrabWidgetToImage(not_null<QWidget*> target, QRect rect, QColor bg) {
  115. SendPendingMoveResizeEvents(target);
  116. if (rect.isNull()) {
  117. rect = target->rect();
  118. }
  119. auto result = QImage(
  120. rect.size() * style::DevicePixelRatio(),
  121. QImage::Format_ARGB32_Premultiplied);
  122. result.setDevicePixelRatio(style::DevicePixelRatio());
  123. if (!target->testAttribute(Qt::WA_OpaquePaintEvent)) {
  124. result.fill(bg);
  125. }
  126. if (rect.isValid()) {
  127. QPainter p(&result);
  128. RenderWidget(p, target, QPoint(), rect);
  129. }
  130. return result;
  131. }
  132. void RenderWidget(
  133. QPainter &painter,
  134. not_null<QWidget*> source,
  135. const QPoint &targetOffset,
  136. const QRegion &sourceRegion,
  137. QWidget::RenderFlags renderFlags) {
  138. const auto visible = source->isVisible();
  139. source->render(&painter, targetOffset, sourceRegion, renderFlags);
  140. if (!visible) {
  141. MarkDirtyOpaqueChildrenRecursive(source);
  142. }
  143. }
  144. void ForceFullRepaint(not_null<QWidget*> widget) {
  145. const auto refresher = std::make_unique<QWidget>(widget);
  146. refresher->setGeometry(widget->rect());
  147. refresher->show();
  148. }
  149. void ForceFullRepaintSync(not_null<QWidget*> widget) {
  150. const auto wm = widget->testAttribute(Qt::WA_Mapped);
  151. const auto wv = widget->testAttribute(Qt::WA_WState_Visible);
  152. if (!wm) widget->setAttribute(Qt::WA_Mapped, true);
  153. if (!wv) widget->setAttribute(Qt::WA_WState_Visible, true);
  154. ForceFullRepaint(widget);
  155. QEvent e(QEvent::UpdateRequest);
  156. QGuiApplication::sendEvent(widget, &e);
  157. if (!wm) widget->setAttribute(Qt::WA_Mapped, false);
  158. if (!wv) widget->setAttribute(Qt::WA_WState_Visible, false);
  159. }
  160. void PostponeCall(FnMut<void()> &&callable) {
  161. Integration::Instance().postponeCall(std::move(callable));
  162. }
  163. void SendSynteticMouseEvent(QWidget *widget, QEvent::Type type, Qt::MouseButton button, const QPoint &globalPoint) {
  164. if (const auto windowHandle = widget->window()->windowHandle()) {
  165. const auto localPoint = windowHandle->mapFromGlobal(globalPoint);
  166. QMouseEvent ev(type
  167. , localPoint
  168. , localPoint
  169. , globalPoint
  170. , button
  171. , type == QEvent::MouseButtonRelease
  172. ? QGuiApplication::mouseButtons() ^ button
  173. : QGuiApplication::mouseButtons() | button
  174. , QGuiApplication::keyboardModifiers()
  175. , Qt::MouseEventSynthesizedByApplication
  176. );
  177. ev.setTimestamp(crl::now());
  178. QGuiApplication::sendEvent(windowHandle, &ev);
  179. }
  180. }
  181. QPixmap PixmapFromImage(QImage &&image) {
  182. return QPixmap::fromImage(std::move(image), Qt::ColorOnly);
  183. }
  184. bool IsContentVisible(
  185. not_null<QWidget*> widget,
  186. const QRect &rect) {
  187. Expects(widget->window()->windowHandle());
  188. const auto activeOrNotOverlapped = [&] {
  189. if (const auto active = widget->isActiveWindow()) {
  190. return active;
  191. } else if (Integration::Instance().screenIsLocked()) {
  192. return false;
  193. }
  194. const auto mappedRect = rect.isNull()
  195. ? QRect(
  196. widget->mapTo(widget->window(), QPoint()),
  197. widget->size())
  198. : QRect(
  199. widget->mapTo(widget->window(), rect.topLeft()),
  200. rect.size());
  201. const auto overlapped = Platform::IsOverlapped(
  202. widget->window(),
  203. mappedRect);
  204. return overlapped.has_value() && !*overlapped;
  205. }();
  206. return activeOrNotOverlapped
  207. && widget->isVisible()
  208. && !widget->window()->isMinimized()
  209. && widget->window()->windowHandle()->isExposed();
  210. }
  211. int WheelDirection(not_null<QWheelEvent*> e) {
  212. // Only a mouse wheel is accepted.
  213. constexpr auto step = static_cast<int>(QWheelEvent::DefaultDeltasPerStep);
  214. const auto delta = e->angleDelta().y();
  215. const auto absDelta = std::abs(delta);
  216. if (absDelta != step) {
  217. return 0;
  218. }
  219. return (delta / absDelta);
  220. }
  221. QPoint MapFrom(
  222. not_null<QWidget*> to,
  223. not_null<QWidget*> from,
  224. QPoint point) {
  225. return (to->window() != from->window())
  226. ? to->mapFromGlobal(from->mapToGlobal(point))
  227. : to->mapFrom(to->window(), from->mapTo(from->window(), point));
  228. }
  229. [[nodiscard]] QRect MapFrom(
  230. not_null<QWidget*> to,
  231. not_null<QWidget*> from,
  232. QRect rect) {
  233. return { MapFrom(to, from, rect.topLeft()), rect.size() };
  234. }
  235. void SetGeometryAndScreen(
  236. not_null<QWidget*> widget,
  237. QRect geometry) {
  238. if (const auto screen = QGuiApplication::screenAt(geometry.center())) {
  239. #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
  240. widget->setScreen(screen);
  241. #else // Qt >= 6.0.0
  242. widget->createWinId();
  243. widget->windowHandle()->setScreen(screen);
  244. #endif // Qt < 6.0.0
  245. }
  246. widget->setGeometry(geometry);
  247. }
  248. QPointF ScrollDeltaF(not_null<QWheelEvent*> e, bool touch) {
  249. const auto convert = [](QPointF point) {
  250. return QPointF(
  251. style::ConvertScaleExact(point.x()),
  252. style::ConvertScaleExact(point.y()));
  253. };
  254. if (!e->pixelDelta().isNull()) {
  255. return convert(e->pixelDelta())
  256. * ((::Platform::IsWayland() && !touch)
  257. ? kMagicScrollMultiplier
  258. : 1.);
  259. }
  260. return (convert(e->angleDelta()) * QApplication::wheelScrollLines())
  261. / float64(kPixelToAngleDelta * kDefaultWheelScrollLines);
  262. }
  263. QPoint ScrollDelta(not_null<QWheelEvent*> e, bool touch) {
  264. return ScrollDeltaF(e, touch).toPoint();
  265. }
  266. } // namespace Ui