mac_touchbar_controls.mm 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  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/touchbar/mac_touchbar_controls.h"
  8. #include "base/platform/mac/base_utilities_mac.h" // Q2NSString()
  9. #include "core/sandbox.h" // Sandbox::customEnterFromEventLoop()
  10. #include "ui/text/format_values.h" // Ui::FormatDurationText()
  11. #include "media/audio/media_audio.h"
  12. #include "platform/mac/touchbar/mac_touchbar_common.h"
  13. #import <AppKit/NSButton.h>
  14. #import <AppKit/NSCustomTouchBarItem.h>
  15. #import <AppKit/NSImage.h>
  16. #import <AppKit/NSImageView.h>
  17. #import <AppKit/NSSlider.h>
  18. #import <AppKit/NSSliderTouchBarItem.h>
  19. using namespace TouchBar;
  20. namespace {
  21. constexpr auto kPadding = 7;
  22. inline NSImage *Icon(const style::icon &icon) {
  23. return CreateNSImageFromStyleIcon(icon, kCircleDiameter / 2);
  24. }
  25. inline NSDictionary *Attributes() {
  26. return @{
  27. NSFontAttributeName: [NSFont systemFontOfSize:14],
  28. NSParagraphStyleAttributeName:
  29. [NSMutableParagraphStyle defaultParagraphStyle],
  30. NSForegroundColorAttributeName: [NSColor whiteColor]
  31. };
  32. }
  33. inline NSString *FormatTime(TimeId time) {
  34. return Platform::Q2NSString(Ui::FormatDurationText(time));
  35. }
  36. } // namespace
  37. #pragma mark - TrackPosition
  38. @interface TrackPosition : NSImageView
  39. @end // @interface TrackPosition
  40. @implementation TrackPosition {
  41. NSMutableString *_text;
  42. double _width;
  43. double _height;
  44. rpl::lifetime _lifetime;
  45. }
  46. - (id)init:(rpl::producer< Media::Player::TrackState>)trackState {
  47. self = [super init];
  48. const auto textLength = _lifetime.make_state<rpl::variable<int>>(0);
  49. _width = _height = 0;
  50. _text = [[NSMutableString alloc] initWithCapacity:13];
  51. rpl::combine(
  52. rpl::duplicate(
  53. trackState
  54. ) | rpl::map([](const auto &state) {
  55. return state.position / 1000;
  56. }) | rpl::distinct_until_changed(),
  57. std::move(
  58. trackState
  59. ) | rpl::map([](const auto &state) {
  60. return state.length / 1000;
  61. }) | rpl::distinct_until_changed()
  62. ) | rpl::start_with_next([=](int position, int length) {
  63. [_text setString:[NSString stringWithFormat:@"%@ / %@",
  64. FormatTime(position),
  65. FormatTime(length)]];
  66. *textLength = _text.length;
  67. [self display];
  68. }, _lifetime);
  69. textLength->changes(
  70. ) | rpl::start_with_next([=] {
  71. const auto size = [_text sizeWithAttributes:Attributes()];
  72. _width = size.width + kPadding * 2;
  73. _height = size.height;
  74. if (self.image) {
  75. [self.image release];
  76. }
  77. self.image = [[NSImage alloc] initWithSize:NSMakeSize(
  78. _width,
  79. kCircleDiameter)];
  80. }, _lifetime);
  81. return self;
  82. }
  83. - (void)drawRect:(NSRect)dirtyRect {
  84. if (!(_text && _text.length && _width && _height)) {
  85. return;
  86. }
  87. const auto size = [_text sizeWithAttributes:Attributes()];
  88. const auto rect = CGRectMake(
  89. (_width - size.width) / 2,
  90. -(kCircleDiameter - _height) / 2,
  91. _width,
  92. kCircleDiameter);
  93. [_text drawInRect:rect withAttributes:Attributes()];
  94. }
  95. - (void)dealloc {
  96. if (self.image) {
  97. [self.image release];
  98. }
  99. if (_text) {
  100. [_text release];
  101. }
  102. [super dealloc];
  103. }
  104. @end // @implementation TrackPosition
  105. namespace TouchBar {
  106. NSButton *CreateTouchBarButton(
  107. // const style::icon &icon,
  108. NSImage *image,
  109. rpl::lifetime &lifetime,
  110. Fn<void()> callback) {
  111. id block = [^{
  112. Core::Sandbox::Instance().customEnterFromEventLoop(callback);
  113. } copy];
  114. NSButton* button = [NSButton
  115. buttonWithImage:image
  116. target:block
  117. action:@selector(invoke)];
  118. lifetime.add([=] {
  119. [block release];
  120. });
  121. return button;
  122. }
  123. NSButton *CreateTouchBarButton(
  124. const style::icon &icon,
  125. rpl::lifetime &lifetime,
  126. Fn<void()> callback) {
  127. return CreateTouchBarButton(Icon(icon), lifetime, std::move(callback));
  128. }
  129. NSButton *CreateTouchBarButtonWithTwoStates(
  130. NSImage *icon1,
  131. NSImage *icon2,
  132. rpl::lifetime &lifetime,
  133. Fn<void(bool)> callback,
  134. bool firstState,
  135. rpl::producer<bool> stateChanged) {
  136. NSButton* button = [NSButton
  137. buttonWithImage:(firstState ? icon2 : icon1)
  138. target:nil
  139. action:nil];
  140. const auto isFirstState = lifetime.make_state<bool>(firstState);
  141. id block = [^{
  142. const auto state = *isFirstState;
  143. button.image = state ? icon1 : icon2;
  144. *isFirstState = !state;
  145. Core::Sandbox::Instance().customEnterFromEventLoop([=] {
  146. callback(state);
  147. });
  148. } copy];
  149. button.target = block;
  150. button.action = @selector(invoke);
  151. std::move(
  152. stateChanged
  153. ) | rpl::start_with_next([=](bool isChangedToFirstState) {
  154. button.image = isChangedToFirstState ? icon1 : icon2;
  155. }, lifetime);
  156. lifetime.add([=] {
  157. [block release];
  158. });
  159. return button;
  160. }
  161. NSButton *CreateTouchBarButtonWithTwoStates(
  162. const style::icon &icon1,
  163. const style::icon &icon2,
  164. rpl::lifetime &lifetime,
  165. Fn<void(bool)> callback,
  166. bool firstState,
  167. rpl::producer<bool> stateChanged) {
  168. return CreateTouchBarButtonWithTwoStates(
  169. Icon(icon1),
  170. Icon(icon2),
  171. lifetime,
  172. std::move(callback),
  173. firstState,
  174. std::move(stateChanged));
  175. }
  176. NSSliderTouchBarItem *CreateTouchBarSlider(
  177. NSString *itemId,
  178. rpl::lifetime &lifetime,
  179. Fn<void(bool, double, double)> callback,
  180. rpl::producer<Media::Player::TrackState> stateChanged) {
  181. const auto lastDurationMs = lifetime.make_state<crl::time>(0);
  182. auto *seekBar = [[NSSliderTouchBarItem alloc] initWithIdentifier:itemId];
  183. seekBar.slider.minValue = 0.0f;
  184. seekBar.slider.maxValue = 1.0f;
  185. seekBar.customizationLabel = @"Seek Bar";
  186. id block = [^{
  187. // https://stackoverflow.com/a/45891017
  188. auto *event = [[NSApplication sharedApplication] currentEvent];
  189. const auto touchUp = [event
  190. touchesMatchingPhase:NSTouchPhaseEnded
  191. inView:nil].count > 0;
  192. Core::Sandbox::Instance().customEnterFromEventLoop([=] {
  193. callback(touchUp, seekBar.slider.doubleValue, *lastDurationMs);
  194. });
  195. } copy];
  196. std::move(
  197. stateChanged
  198. ) | rpl::start_with_next([=](const Media::Player::TrackState &state) {
  199. const auto stop = Media::Player::IsStoppedOrStopping(state.state);
  200. const auto duration = double(stop ? 0 : state.length);
  201. auto slider = seekBar.slider;
  202. if (duration <= 0) {
  203. slider.enabled = false;
  204. slider.doubleValue = 0;
  205. } else {
  206. slider.enabled = true;
  207. if (!slider.highlighted) {
  208. const auto pos = stop
  209. ? 0
  210. : std::max(state.position, int64(0));
  211. slider.doubleValue = (pos / duration) * slider.maxValue;
  212. *lastDurationMs = duration;
  213. }
  214. }
  215. }, lifetime);
  216. seekBar.target = block;
  217. seekBar.action = @selector(invoke);
  218. lifetime.add([=] {
  219. [block release];
  220. });
  221. return seekBar;
  222. }
  223. NSCustomTouchBarItem *CreateTouchBarTrackPosition(
  224. NSString *itemId,
  225. rpl::producer<Media::Player::TrackState> stateChanged) {
  226. auto *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:itemId];
  227. auto *trackPosition = [[[TrackPosition alloc]
  228. init:std::move(stateChanged)] autorelease];
  229. item.view = trackPosition;
  230. item.customizationLabel = @"Track Position";
  231. return item;
  232. }
  233. } // namespace TouchBar