overlay_widget_mac.mm 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "platform/mac/overlay_widget_mac.h"
  8. #include "base/object_ptr.h"
  9. #include "ui/platform/ui_platform_window_title.h"
  10. #include "ui/widgets/buttons.h"
  11. #include "ui/widgets/rp_window.h"
  12. #include "styles/style_media_view.h"
  13. #include <QtGui/QWindow>
  14. #include <Cocoa/Cocoa.h>
  15. namespace Platform {
  16. namespace {
  17. using namespace Media::View;
  18. } // namespace
  19. struct MacOverlayWidgetHelper::Data {
  20. const not_null<Ui::RpWindow*> window;
  21. const Fn<void(bool)> maximize;
  22. object_ptr<Ui::AbstractButton> buttonClose = { nullptr };
  23. object_ptr<Ui::AbstractButton> buttonMinimize = { nullptr };
  24. object_ptr<Ui::AbstractButton> buttonMaximize = { nullptr };
  25. rpl::event_stream<> activations;
  26. rpl::variable<float64> masterOpacity = 1.;
  27. rpl::variable<bool> maximized = false;
  28. rpl::event_stream<> clearStateRequests;
  29. bool anyOver = false;
  30. NSWindow * __weak native = nil;
  31. rpl::variable<int> topNotchSkip;
  32. };
  33. MacOverlayWidgetHelper::MacOverlayWidgetHelper(
  34. not_null<Ui::RpWindow*> window,
  35. Fn<void(bool)> maximize)
  36. : _data(std::make_unique<Data>(Data{
  37. .window = window,
  38. .maximize = std::move(maximize),
  39. })) {
  40. _data->buttonClose = create(window, Control::Close);
  41. _data->buttonMinimize = create(window, Control::Minimize);
  42. _data->buttonMaximize = create(window, Control::Maximize);
  43. }
  44. MacOverlayWidgetHelper::~MacOverlayWidgetHelper() = default;
  45. void MacOverlayWidgetHelper::activate(Control control) {
  46. const auto fullscreen = (_data->window->windowHandle()->flags() & Qt::FramelessWindowHint);
  47. switch (control) {
  48. case Control::Close: _data->window->close(); return;
  49. case Control::Minimize: [_data->native miniaturize:_data->native]; return;
  50. case Control::Maximize: _data->maximize(!fullscreen); return;
  51. }
  52. }
  53. void MacOverlayWidgetHelper::beforeShow(bool fullscreen) {
  54. _data->window->setAttribute(Qt::WA_MacAlwaysShowToolWindow, !fullscreen);
  55. _data->window->windowHandle()->setFlag(Qt::FramelessWindowHint, fullscreen);
  56. updateStyles(fullscreen);
  57. clearState();
  58. }
  59. void MacOverlayWidgetHelper::afterShow(bool fullscreen) {
  60. updateStyles(fullscreen);
  61. refreshButtons(fullscreen);
  62. _data->window->activateWindow();
  63. }
  64. void MacOverlayWidgetHelper::resolveNative() {
  65. if (const auto handle = _data->window->winId()) {
  66. _data->native = [reinterpret_cast<NSView*>(handle) window];
  67. }
  68. }
  69. void MacOverlayWidgetHelper::updateStyles(bool fullscreen) {
  70. _data->maximized = fullscreen;
  71. resolveNative();
  72. if (!_data->native) {
  73. return;
  74. }
  75. const auto window = _data->native;
  76. const auto level = !fullscreen
  77. ? NSNormalWindowLevel
  78. : NSPopUpMenuWindowLevel;
  79. [window setLevel:level];
  80. [window setHidesOnDeactivate:!_data->window->testAttribute(Qt::WA_MacAlwaysShowToolWindow)];
  81. [window setTitleVisibility:NSWindowTitleHidden];
  82. [window setTitlebarAppearsTransparent:YES];
  83. [window setStyleMask:[window styleMask] | NSWindowStyleMaskFullSizeContentView];
  84. if (@available(macOS 12.0, *)) {
  85. _data->topNotchSkip = [[window screen] safeAreaInsets].top;
  86. }
  87. }
  88. void MacOverlayWidgetHelper::refreshButtons(bool fullscreen) {
  89. Expects(_data->native != nullptr);
  90. const auto window = _data->native;
  91. const auto process = [&](NSWindowButton type) {
  92. if (const auto button = [window standardWindowButton:type]) {
  93. [button setHidden:YES];
  94. }
  95. };
  96. process(NSWindowCloseButton);
  97. process(NSWindowMiniaturizeButton);
  98. process(NSWindowZoomButton);
  99. _data->buttonClose->moveToLeft(0, 0);
  100. _data->buttonClose->raise();
  101. _data->buttonClose->show();
  102. _data->buttonMinimize->moveToLeft(_data->buttonClose->width(), 0);
  103. _data->buttonMinimize->raise();
  104. _data->buttonMinimize->show();
  105. _data->buttonMaximize->moveToLeft(_data->buttonClose->width() + _data->buttonMinimize->width(), 0);
  106. _data->buttonMaximize->raise();
  107. _data->buttonMaximize->show();
  108. }
  109. void MacOverlayWidgetHelper::notifyFileDialogShown(bool shown) {
  110. resolveNative();
  111. if (_data->native && !_data->window->isHidden()) {
  112. const auto level = [_data->native level];
  113. if (level != NSNormalWindowLevel) {
  114. const auto level = shown
  115. ? NSModalPanelWindowLevel
  116. : NSPopUpMenuWindowLevel;
  117. [_data->native setLevel:level];
  118. }
  119. }
  120. }
  121. void MacOverlayWidgetHelper::minimize(not_null<Ui::RpWindow*> window) {
  122. resolveNative();
  123. if (_data->native) {
  124. [_data->native miniaturize:_data->native];
  125. }
  126. }
  127. void MacOverlayWidgetHelper::clearState() {
  128. _data->clearStateRequests.fire({});
  129. }
  130. void MacOverlayWidgetHelper::setControlsOpacity(float64 opacity) {
  131. _data->masterOpacity = opacity;
  132. }
  133. rpl::producer<bool> MacOverlayWidgetHelper::controlsSideRightValue() {
  134. return rpl::single(false);
  135. }
  136. rpl::producer<int> MacOverlayWidgetHelper::topNotchSkipValue() {
  137. return _data->topNotchSkip.value();
  138. }
  139. object_ptr<Ui::AbstractButton> MacOverlayWidgetHelper::create(
  140. not_null<QWidget*> parent,
  141. Control control) {
  142. auto result = object_ptr<Ui::AbstractButton>(parent);
  143. const auto raw = result.data();
  144. raw->setClickedCallback([=] { activate(control); });
  145. struct State {
  146. Ui::Animations::Simple animation;
  147. float64 progress = -1.;
  148. QImage frame;
  149. bool maximized = false;
  150. bool anyOver = false;
  151. bool over = false;
  152. };
  153. const auto state = raw->lifetime().make_state<State>();
  154. rpl::merge(
  155. _data->masterOpacity.changes() | rpl::to_empty,
  156. _data->maximized.changes() | rpl::to_empty
  157. ) | rpl::start_with_next([=] {
  158. raw->update();
  159. }, raw->lifetime());
  160. _data->clearStateRequests.events(
  161. ) | rpl::start_with_next([=] {
  162. raw->clearState();
  163. raw->update();
  164. state->over = raw->isOver();
  165. _data->anyOver = false;
  166. state->animation.stop();
  167. }, raw->lifetime());
  168. struct Info {
  169. const style::icon *icon = nullptr;
  170. style::margins padding;
  171. };
  172. const auto info = [&]() -> Info {
  173. switch (control) {
  174. case Control::Minimize:
  175. return { &st::mediaviewTitleMinimizeMac, st::mediaviewTitleMinimizeMacPadding };
  176. case Control::Maximize:
  177. return { &st::mediaviewTitleMaximizeMac, st::mediaviewTitleMaximizeMacPadding };
  178. case Control::Close:
  179. return { &st::mediaviewTitleCloseMac, st::mediaviewTitleCloseMacPadding };
  180. }
  181. Unexpected("Value in DefaultOverlayWidgetHelper::Buttons::create.");
  182. }();
  183. const auto icon = info.icon;
  184. raw->resize(QRect(QPoint(), icon->size()).marginsAdded(info.padding).size());
  185. state->frame = QImage(
  186. icon->size() * style::DevicePixelRatio(),
  187. QImage::Format_ARGB32_Premultiplied);
  188. state->frame.setDevicePixelRatio(style::DevicePixelRatio());
  189. const auto updateOver = [=] {
  190. const auto over = raw->isOver();
  191. if (state->over == over) {
  192. return;
  193. }
  194. state->over = over;
  195. const auto anyOver = over
  196. || _data->buttonClose->isOver()
  197. || _data->buttonMinimize->isOver()
  198. || _data->buttonMaximize->isOver();
  199. if (_data->anyOver != anyOver) {
  200. _data->anyOver = anyOver;
  201. _data->buttonClose->update();
  202. _data->buttonMinimize->update();
  203. _data->buttonMaximize->update();
  204. }
  205. state->animation.start(
  206. [=] { raw->update(); },
  207. state->over ? 0. : 1.,
  208. state->over ? 1. : 0.,
  209. st::mediaviewFadeDuration);
  210. };
  211. const auto prepareFrame = [=] {
  212. const auto progress = state->animation.value(state->over ? 1. : 0.);
  213. const auto maximized = _data->maximized.current();
  214. const auto anyOver = _data->anyOver;
  215. if (state->progress == progress
  216. && state->maximized == maximized
  217. && state->anyOver == anyOver) {
  218. return;
  219. }
  220. state->progress = progress;
  221. state->maximized = maximized;
  222. state->anyOver = anyOver;
  223. auto current = icon;
  224. if (control == Control::Maximize) {
  225. current = maximized ? &st::mediaviewTitleRestoreMac : icon;
  226. }
  227. state->frame.fill(Qt::transparent);
  228. auto q = QPainter(&state->frame);
  229. const auto normal = maximized
  230. ? kMaximizedIconOpacity
  231. : kNormalIconOpacity;
  232. q.setOpacity(progress + (1 - progress) * normal);
  233. st::mediaviewTitleButtonMac.paint(q, 0, 0, raw->width());
  234. if (anyOver) {
  235. q.setOpacity(1.);
  236. current->paint(q, 0, 0, raw->width());
  237. }
  238. q.end();
  239. };
  240. raw->paintRequest(
  241. ) | rpl::start_with_next([=, padding = info.padding] {
  242. updateOver();
  243. prepareFrame();
  244. auto p = QPainter(raw);
  245. p.setOpacity(_data->masterOpacity.current());
  246. p.drawImage(padding.left(), padding.top(), state->frame);
  247. }, raw->lifetime());
  248. return result;
  249. }
  250. std::unique_ptr<OverlayWidgetHelper> CreateOverlayWidgetHelper(
  251. not_null<Ui::RpWindow*> window,
  252. Fn<void(bool)> maximize) {
  253. return std::make_unique<MacOverlayWidgetHelper>(
  254. window,
  255. std::move(maximize));
  256. }
  257. } // namespace Platform