2019-04-29 13:41:51 +00:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
|
|
|
the official desktop application for the Telegram messaging service.
|
2019-05-27 12:39:19 +00:00
|
|
|
|
2019-04-29 13:41:51 +00:00
|
|
|
For license and copyright information please follow this link:
|
|
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
|
|
*/
|
|
|
|
|
2019-05-28 21:15:36 +00:00
|
|
|
#import "mac_touchbar.h"
|
2019-04-29 13:41:51 +00:00
|
|
|
#import <QuartzCore/QuartzCore.h>
|
|
|
|
|
2019-05-01 13:18:31 +00:00
|
|
|
#include "auth_session.h"
|
2019-05-04 10:22:21 +00:00
|
|
|
#include "core/application.h"
|
|
|
|
#include "core/sandbox.h"
|
|
|
|
#include "data/data_folder.h"
|
2019-05-01 13:18:31 +00:00
|
|
|
#include "data/data_session.h"
|
2019-05-27 17:55:53 +00:00
|
|
|
#include "dialogs/dialogs_layout.h"
|
2019-05-01 13:18:31 +00:00
|
|
|
#include "history/history.h"
|
2019-05-04 10:22:21 +00:00
|
|
|
#include "mainwidget.h"
|
|
|
|
#include "mainwindow.h"
|
2019-05-01 18:09:40 +00:00
|
|
|
#include "observer_peer.h"
|
2019-05-04 08:54:38 +00:00
|
|
|
#include "styles/style_media_player.h"
|
2019-05-27 14:15:22 +00:00
|
|
|
#include "window/themes/window_theme.h"
|
2019-05-08 12:15:36 +00:00
|
|
|
#include "window/window_controller.h"
|
2019-05-04 10:22:21 +00:00
|
|
|
#include "ui/empty_userpic.h"
|
2019-05-27 17:55:53 +00:00
|
|
|
#include "styles/style_dialogs.h"
|
2019-04-29 13:41:51 +00:00
|
|
|
|
2019-05-10 12:37:18 +00:00
|
|
|
NSImage *qt_mac_create_nsimage(const QPixmap &pm);
|
|
|
|
|
2019-04-29 13:41:51 +00:00
|
|
|
namespace {
|
2019-05-01 17:50:59 +00:00
|
|
|
//https://developer.apple.com/design/human-interface-guidelines/macos/touch-bar/touch-bar-icons-and-images/
|
|
|
|
constexpr auto kIdealIconSize = 36;
|
|
|
|
constexpr auto kMaximumIconSize = 44;
|
|
|
|
|
2019-05-10 12:37:18 +00:00
|
|
|
constexpr auto kCommandPlayPause = 0x002;
|
|
|
|
constexpr auto kCommandPlaylistPrevious = 0x003;
|
|
|
|
constexpr auto kCommandPlaylistNext = 0x004;
|
|
|
|
constexpr auto kCommandClosePlayer = 0x005;
|
|
|
|
|
2019-04-29 13:41:51 +00:00
|
|
|
constexpr auto kMs = 1000;
|
2019-05-10 12:37:18 +00:00
|
|
|
|
2019-04-29 13:41:51 +00:00
|
|
|
constexpr auto kSongType = AudioMsgId::Type::Song;
|
2019-05-10 12:37:18 +00:00
|
|
|
|
2019-05-01 17:50:59 +00:00
|
|
|
constexpr auto kSavedMessagesId = 0;
|
2019-05-01 19:10:09 +00:00
|
|
|
constexpr auto kArchiveId = -1;
|
2019-04-29 13:41:51 +00:00
|
|
|
|
2019-05-10 12:37:18 +00:00
|
|
|
const NSString *kCustomizationIdPlayer = @"telegram.touchbar";
|
|
|
|
const NSString *kCustomizationIdMain = @"telegram.touchbarMain";
|
|
|
|
const NSTouchBarItemIdentifier kSavedMessagesItemIdentifier = [NSString stringWithFormat:@"%@.savedMessages", kCustomizationIdMain];
|
|
|
|
const NSTouchBarItemIdentifier kArchiveFolderItemIdentifier = [NSString stringWithFormat:@"%@.archiveFolder", kCustomizationIdMain];
|
|
|
|
const NSTouchBarItemIdentifier kPinnedPanelItemIdentifier = [NSString stringWithFormat:@"%@.pinnedPanel", kCustomizationIdMain];
|
|
|
|
|
|
|
|
const NSTouchBarItemIdentifier kSeekBarItemIdentifier = [NSString stringWithFormat:@"%@.seekbar", kCustomizationIdPlayer];
|
|
|
|
const NSTouchBarItemIdentifier kPlayItemIdentifier = [NSString stringWithFormat:@"%@.play", kCustomizationIdPlayer];
|
|
|
|
const NSTouchBarItemIdentifier kNextItemIdentifier = [NSString stringWithFormat:@"%@.nextItem", kCustomizationIdPlayer];
|
|
|
|
const NSTouchBarItemIdentifier kPreviousItemIdentifier = [NSString stringWithFormat:@"%@.previousItem", kCustomizationIdPlayer];
|
|
|
|
const NSTouchBarItemIdentifier kCommandClosePlayerItemIdentifier = [NSString stringWithFormat:@"%@.closePlayer", kCustomizationIdPlayer];
|
|
|
|
const NSTouchBarItemIdentifier kCurrentPositionItemIdentifier = [NSString stringWithFormat:@"%@.currentPosition", kCustomizationIdPlayer];
|
|
|
|
|
|
|
|
NSImage *CreateNSImageFromStyleIcon(const style::icon &icon, int size = kIdealIconSize) {
|
|
|
|
const auto instance = icon.instance(QColor(255, 255, 255, 255), 100);
|
|
|
|
auto pixmap = QPixmap::fromImage(instance);
|
|
|
|
pixmap.setDevicePixelRatio(cRetinaFactor());
|
|
|
|
NSImage *image = [qt_mac_create_nsimage(pixmap) autorelease];
|
|
|
|
[image setSize:NSMakeSize(size, size)];
|
|
|
|
return image;
|
|
|
|
}
|
|
|
|
|
2019-05-27 14:15:22 +00:00
|
|
|
inline bool CurrentSongExists() {
|
2019-05-10 12:37:18 +00:00
|
|
|
return Media::Player::instance()->current(kSongType).audio() != nullptr;
|
|
|
|
}
|
|
|
|
|
2019-05-27 14:15:22 +00:00
|
|
|
inline bool UseEmptyUserpic(PeerData *peer) {
|
|
|
|
return (peer && (peer->useEmptyUserpic() || peer->isSelf()));
|
|
|
|
}
|
|
|
|
|
2019-05-27 17:55:53 +00:00
|
|
|
inline bool IsSelfPeer(PeerData *peer) {
|
|
|
|
return (peer && peer->id == Auth().userPeerId());
|
|
|
|
}
|
|
|
|
|
|
|
|
inline int UnreadCount(PeerData *peer) {
|
|
|
|
return (peer
|
|
|
|
&& AuthSession::Exists()
|
|
|
|
&& Auth().data().history(peer->id)->unreadCountForBadge());
|
|
|
|
}
|
|
|
|
|
2019-05-27 12:39:19 +00:00
|
|
|
NSString *FormatTime(int time) {
|
2019-05-10 12:37:18 +00:00
|
|
|
const auto seconds = time % 60;
|
|
|
|
const auto minutes = (time / 60) % 60;
|
|
|
|
const auto hours = time / (60 * 60);
|
|
|
|
|
|
|
|
NSString *stringTime = (hours > 0)
|
|
|
|
? [NSString stringWithFormat:@"%d:", hours]
|
|
|
|
: @"";
|
|
|
|
stringTime = [NSString stringWithFormat:@"%@%02d:",
|
|
|
|
(stringTime.length > 0 || minutes > 9)
|
|
|
|
? stringTime
|
|
|
|
: @"",
|
|
|
|
minutes];
|
|
|
|
stringTime = [NSString stringWithFormat:@"%@%02d", stringTime, seconds];
|
|
|
|
|
|
|
|
return stringTime;
|
|
|
|
}
|
|
|
|
|
2019-05-27 17:55:53 +00:00
|
|
|
void PaintUnreadBadge(Painter &p, PeerData *peer) {
|
|
|
|
const auto history = Auth().data().history(peer->id);
|
|
|
|
const auto count = history->unreadCountForBadge();
|
|
|
|
if (!count) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const auto unread = history->unreadMark()
|
|
|
|
? QString()
|
|
|
|
: QString::number(count);
|
|
|
|
Dialogs::Layout::UnreadBadgeStyle unreadSt;
|
|
|
|
unreadSt.sizeId = Dialogs::Layout::UnreadBadgeInTouchBar;
|
|
|
|
unreadSt.muted = history->mute();
|
|
|
|
// Use constant values to draw badge regardless of cConfigScale().
|
|
|
|
unreadSt.size = 19;
|
|
|
|
unreadSt.padding = 5;
|
|
|
|
unreadSt.font = style::font(
|
|
|
|
12,
|
|
|
|
unreadSt.font->flags(),
|
|
|
|
unreadSt.font->family());
|
|
|
|
Dialogs::Layout::paintUnreadCount(p, unread, kIdealIconSize, kIdealIconSize - unreadSt.size, unreadSt, nullptr, 2);
|
|
|
|
}
|
|
|
|
|
2019-05-10 12:37:18 +00:00
|
|
|
} // namespace
|
2019-05-01 13:18:31 +00:00
|
|
|
|
2019-05-27 14:21:29 +00:00
|
|
|
@interface PinnedDialogButton : NSCustomTouchBarItem
|
2019-05-01 13:18:31 +00:00
|
|
|
|
|
|
|
@property(nonatomic, assign) int number;
|
2019-05-27 12:39:19 +00:00
|
|
|
@property(nonatomic, assign) PeerData *peer;
|
2019-05-07 17:08:00 +00:00
|
|
|
@property(nonatomic, assign) bool isDeletedFromView;
|
2019-05-27 17:55:53 +00:00
|
|
|
@property(nonatomic, assign) QPixmap userpic;
|
2019-05-01 13:18:31 +00:00
|
|
|
|
|
|
|
- (id) init:(int)num;
|
|
|
|
- (void)buttonActionPin:(NSButton *)sender;
|
2019-05-27 14:15:22 +00:00
|
|
|
- (void)updateUserpic;
|
2019-05-01 13:18:31 +00:00
|
|
|
|
|
|
|
@end // @interface PinnedDialogButton
|
|
|
|
|
2019-05-27 14:21:29 +00:00
|
|
|
@implementation PinnedDialogButton {
|
|
|
|
rpl::lifetime _lifetime;
|
2019-05-27 17:55:53 +00:00
|
|
|
rpl::lifetime _peerChangedLifetime;
|
2019-05-27 14:21:29 +00:00
|
|
|
bool isWaitingUserpicLoad;
|
|
|
|
}
|
2019-05-01 13:18:31 +00:00
|
|
|
|
|
|
|
- (id) init:(int)num {
|
2019-05-01 17:50:59 +00:00
|
|
|
if (num == kSavedMessagesId) {
|
2019-05-10 12:37:18 +00:00
|
|
|
self = [super initWithIdentifier:kSavedMessagesItemIdentifier];
|
2019-05-27 12:39:19 +00:00
|
|
|
isWaitingUserpicLoad = false;
|
2019-05-08 09:29:38 +00:00
|
|
|
self.customizationLabel = [NSString stringWithFormat:@"Pinned Dialog %d", num];
|
2019-05-01 19:10:09 +00:00
|
|
|
} else if (num == kArchiveId) {
|
2019-05-10 12:37:18 +00:00
|
|
|
self = [super initWithIdentifier:kArchiveFolderItemIdentifier];
|
2019-05-27 12:39:19 +00:00
|
|
|
isWaitingUserpicLoad = false;
|
2019-05-08 09:29:38 +00:00
|
|
|
self.customizationLabel = @"Archive Folder";
|
|
|
|
} else {
|
2019-05-10 12:37:18 +00:00
|
|
|
NSString *identifier = [NSString stringWithFormat:@"%@.pinnedDialog%d", kCustomizationIdMain, num];
|
2019-05-08 09:29:38 +00:00
|
|
|
self = [super initWithIdentifier:identifier];
|
2019-05-27 12:39:19 +00:00
|
|
|
isWaitingUserpicLoad = true;
|
2019-05-08 09:29:38 +00:00
|
|
|
self.customizationLabel = @"Saved Messages";
|
2019-05-01 17:50:59 +00:00
|
|
|
}
|
2019-05-01 13:18:31 +00:00
|
|
|
if (!self) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
self.number = num;
|
2019-05-27 12:39:19 +00:00
|
|
|
|
2019-05-27 17:55:53 +00:00
|
|
|
NSButton *button = [NSButton buttonWithImage:[NSImage imageNamed:NSImageNameStopProgressTemplate] target:self action:@selector(buttonActionPin:)];
|
2019-05-01 13:18:31 +00:00
|
|
|
[button setBordered:NO];
|
|
|
|
[button sizeToFit];
|
|
|
|
self.view = button;
|
2019-05-08 09:29:38 +00:00
|
|
|
|
2019-05-27 14:15:22 +00:00
|
|
|
using Update = const Window::Theme::BackgroundUpdate;
|
2019-05-27 17:55:53 +00:00
|
|
|
auto themeChanged = base::ObservableViewer(
|
2019-05-27 14:15:22 +00:00
|
|
|
*Window::Theme::Background()
|
2019-05-27 17:55:53 +00:00
|
|
|
) | rpl::start_spawning(_lifetime);
|
|
|
|
|
|
|
|
rpl::duplicate(
|
|
|
|
themeChanged
|
2019-05-27 14:15:22 +00:00
|
|
|
) | rpl::filter([=](const Update &update) {
|
|
|
|
return update.paletteChanged()
|
|
|
|
&& (_number <= kSavedMessagesId || UseEmptyUserpic(_peer));
|
|
|
|
}) | rpl::start_with_next([=] {
|
|
|
|
[self updateUserpic];
|
|
|
|
}, _lifetime);
|
|
|
|
|
2019-05-27 17:55:53 +00:00
|
|
|
std::move(
|
|
|
|
themeChanged
|
|
|
|
) | rpl::filter([=](const Update &update) {
|
|
|
|
return update.type == Update::Type::ApplyingTheme
|
|
|
|
&& UnreadCount(_peer);
|
|
|
|
}) | rpl::start_with_next([=] {
|
|
|
|
[self updateBadge];
|
|
|
|
}, _lifetime);
|
|
|
|
|
2019-05-08 09:29:38 +00:00
|
|
|
if (num <= kSavedMessagesId) {
|
2019-05-27 17:55:53 +00:00
|
|
|
[self updateUserpic];
|
2019-05-08 09:29:38 +00:00
|
|
|
return self;
|
|
|
|
}
|
2019-05-27 12:39:19 +00:00
|
|
|
|
2019-05-01 13:18:31 +00:00
|
|
|
base::ObservableViewer(
|
2019-05-10 09:50:47 +00:00
|
|
|
Auth().downloaderTaskFinished()
|
2019-05-01 18:09:40 +00:00
|
|
|
) | rpl::start_with_next([=] {
|
2019-05-27 12:39:19 +00:00
|
|
|
if (isWaitingUserpicLoad) {
|
2019-05-27 14:15:22 +00:00
|
|
|
[self updateUserpic];
|
2019-05-01 13:18:31 +00:00
|
|
|
}
|
2019-05-27 12:39:19 +00:00
|
|
|
}, _lifetime);
|
|
|
|
|
2019-05-01 13:18:31 +00:00
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2019-05-10 09:50:47 +00:00
|
|
|
// Setter of peer.
|
|
|
|
- (void) setPeer:(PeerData *)newPeer {
|
|
|
|
if (_peer == newPeer) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_peer = newPeer;
|
2019-05-27 17:55:53 +00:00
|
|
|
_peerChangedLifetime.destroy();
|
2019-05-10 09:50:47 +00:00
|
|
|
if (!_peer) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Notify::PeerUpdateViewer(
|
|
|
|
_peer,
|
2019-05-27 17:55:53 +00:00
|
|
|
Notify::PeerUpdate::Flag::PhotoChanged
|
2019-05-10 09:50:47 +00:00
|
|
|
) | rpl::start_with_next([=] {
|
2019-05-27 12:39:19 +00:00
|
|
|
isWaitingUserpicLoad = true;
|
2019-05-27 14:15:22 +00:00
|
|
|
[self updateUserpic];
|
2019-05-27 17:55:53 +00:00
|
|
|
}, _peerChangedLifetime);
|
2019-05-10 09:50:47 +00:00
|
|
|
|
2019-05-27 17:55:53 +00:00
|
|
|
Notify::PeerUpdateViewer(
|
|
|
|
_peer,
|
|
|
|
Notify::PeerUpdate::Flag::UnreadViewChanged
|
|
|
|
) | rpl::start_with_next([=] {
|
|
|
|
[self updateBadge];
|
|
|
|
}, _peerChangedLifetime);
|
2019-05-02 13:26:47 +00:00
|
|
|
}
|
|
|
|
|
2019-05-02 10:25:05 +00:00
|
|
|
- (void) buttonActionPin:(NSButton *)sender {
|
2019-05-08 12:15:36 +00:00
|
|
|
const auto openFolder = [=] {
|
|
|
|
if (!App::wnd()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (const auto folder = Auth().data().folderLoaded(Data::Folder::kId)) {
|
|
|
|
App::wnd()->controller()->openFolder(folder);
|
|
|
|
}
|
|
|
|
};
|
2019-05-01 13:18:31 +00:00
|
|
|
Core::Sandbox::Instance().customEnterFromEventLoop([=] {
|
2019-05-08 12:15:36 +00:00
|
|
|
self.number == kArchiveId
|
|
|
|
? openFolder()
|
|
|
|
: App::main()->choosePeer(self.number == kSavedMessagesId
|
|
|
|
? Auth().userPeerId()
|
|
|
|
: self.peer->id, ShowAtUnreadMsgId);
|
2019-05-01 13:18:31 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-05-27 17:55:53 +00:00
|
|
|
- (void) updateUserpic {
|
2019-05-04 11:06:25 +00:00
|
|
|
// Don't draw self userpic if we pin Saved Messages.
|
2019-05-27 17:55:53 +00:00
|
|
|
if (self.number <= kSavedMessagesId || IsSelfPeer(_peer)) {
|
|
|
|
const auto s = kIdealIconSize * cIntRetinaFactor();
|
|
|
|
auto *pixmap = new QPixmap(s, s);
|
|
|
|
Painter paint(pixmap);
|
2019-05-01 17:50:59 +00:00
|
|
|
paint.fillRect(QRectF(0, 0, s, s), QColor(0, 0, 0, 255));
|
2019-05-27 12:39:19 +00:00
|
|
|
|
2019-05-01 19:10:09 +00:00
|
|
|
if (self.number == kArchiveId) {
|
|
|
|
if (const auto folder = Auth().data().folderLoaded(Data::Folder::kId)) {
|
|
|
|
folder->paintUserpic(paint, 0, 0, s);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Ui::EmptyUserpic::PaintSavedMessages(paint, 0, 0, s, s);
|
|
|
|
}
|
2019-05-27 17:55:53 +00:00
|
|
|
pixmap->setDevicePixelRatio(cRetinaFactor());
|
|
|
|
_userpic = *pixmap;
|
|
|
|
[self updateImage:_userpic];
|
|
|
|
return;
|
2019-05-01 17:50:59 +00:00
|
|
|
}
|
2019-05-01 13:18:31 +00:00
|
|
|
if (!self.peer) {
|
2019-05-27 17:55:53 +00:00
|
|
|
return;
|
2019-05-01 13:18:31 +00:00
|
|
|
}
|
2019-05-27 12:39:19 +00:00
|
|
|
isWaitingUserpicLoad = !self.peer->userpicLoaded();
|
2019-05-01 17:50:59 +00:00
|
|
|
auto pixmap = self.peer->genUserpic(kIdealIconSize);
|
2019-05-01 13:18:31 +00:00
|
|
|
pixmap.setDevicePixelRatio(cRetinaFactor());
|
2019-05-27 17:55:53 +00:00
|
|
|
_userpic = pixmap;
|
|
|
|
[self updateBadge];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) updateBadge {
|
|
|
|
auto pixmap = App::pixmapFromImageInPlace(_userpic.toImage());
|
|
|
|
Painter p(&pixmap);
|
|
|
|
PaintUnreadBadge(p, _peer);
|
|
|
|
[self updateImage:pixmap];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) updateImage:(QPixmap)pixmap {
|
|
|
|
NSButton *button = self.view;
|
|
|
|
button.image = [qt_mac_create_nsimage(pixmap) autorelease];
|
2019-05-01 13:18:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
|
@interface TouchBar()<NSTouchBarDelegate>
|
|
|
|
@end // @interface TouchBar
|
|
|
|
|
2019-05-27 14:21:29 +00:00
|
|
|
@implementation TouchBar {
|
|
|
|
NSView *_parentView;
|
|
|
|
NSMutableArray *_mainPinnedButtons;
|
|
|
|
|
|
|
|
NSTouchBar *_touchBarMain;
|
|
|
|
NSTouchBar *_touchBarAudioPlayer;
|
|
|
|
|
|
|
|
Platform::TouchBarType _touchBarType;
|
|
|
|
Platform::TouchBarType _touchBarTypeBeforeLock;
|
|
|
|
|
|
|
|
double _duration;
|
|
|
|
double _position;
|
|
|
|
|
|
|
|
rpl::lifetime _lifetime;
|
|
|
|
}
|
2019-04-29 13:41:51 +00:00
|
|
|
|
2019-05-02 10:25:05 +00:00
|
|
|
- (id) init:(NSView *)view {
|
2019-04-29 16:55:11 +00:00
|
|
|
self = [super init];
|
2019-05-27 12:39:19 +00:00
|
|
|
if (!self) {
|
|
|
|
return nil;
|
2019-04-29 16:55:11 +00:00
|
|
|
}
|
2019-05-27 12:39:19 +00:00
|
|
|
|
|
|
|
const auto iconSize = kIdealIconSize / 3;
|
|
|
|
_position = 0;
|
|
|
|
_duration = 0;
|
|
|
|
_parentView = view;
|
|
|
|
self.touchBarItems = @{
|
|
|
|
kPinnedPanelItemIdentifier: [NSMutableDictionary dictionaryWithDictionary:@{
|
|
|
|
@"type": @"pinned",
|
|
|
|
}],
|
|
|
|
kSeekBarItemIdentifier: [NSMutableDictionary dictionaryWithDictionary:@{
|
|
|
|
@"type": @"slider",
|
|
|
|
@"name": @"Seek Bar"
|
|
|
|
}],
|
|
|
|
kPlayItemIdentifier: [NSMutableDictionary dictionaryWithDictionary:@{
|
|
|
|
@"type": @"button",
|
|
|
|
@"name": @"Play Button",
|
|
|
|
@"cmd": [NSNumber numberWithInt:kCommandPlayPause],
|
|
|
|
@"image": CreateNSImageFromStyleIcon(st::touchBarIconPlayerPause, iconSize),
|
|
|
|
@"imageAlt": CreateNSImageFromStyleIcon(st::touchBarIconPlayerPlay, iconSize),
|
|
|
|
}],
|
|
|
|
kPreviousItemIdentifier: [NSMutableDictionary dictionaryWithDictionary:@{
|
|
|
|
@"type": @"button",
|
|
|
|
@"name": @"Previous Playlist Item",
|
|
|
|
@"cmd": [NSNumber numberWithInt:kCommandPlaylistPrevious],
|
|
|
|
@"image": CreateNSImageFromStyleIcon(st::touchBarIconPlayerPrevious, iconSize),
|
|
|
|
}],
|
|
|
|
kNextItemIdentifier: [NSMutableDictionary dictionaryWithDictionary:@{
|
|
|
|
@"type": @"button",
|
|
|
|
@"name": @"Next Playlist Item",
|
|
|
|
@"cmd": [NSNumber numberWithInt:kCommandPlaylistNext],
|
|
|
|
@"image": CreateNSImageFromStyleIcon(st::touchBarIconPlayerNext, iconSize),
|
|
|
|
}],
|
|
|
|
kCommandClosePlayerItemIdentifier: [NSMutableDictionary dictionaryWithDictionary:@{
|
|
|
|
@"type": @"button",
|
|
|
|
@"name": @"Close Player",
|
|
|
|
@"cmd": [NSNumber numberWithInt:kCommandClosePlayer],
|
|
|
|
@"image": CreateNSImageFromStyleIcon(st::touchBarIconPlayerClose, iconSize),
|
|
|
|
}],
|
|
|
|
kCurrentPositionItemIdentifier: [NSMutableDictionary dictionaryWithDictionary:@{
|
|
|
|
@"type": @"text",
|
|
|
|
@"name": @"Current Position"
|
|
|
|
}]
|
|
|
|
};
|
|
|
|
|
2019-05-01 13:18:31 +00:00
|
|
|
[self createTouchBar];
|
2019-05-27 12:39:19 +00:00
|
|
|
[self setTouchBar:Platform::TouchBarType::Main];
|
|
|
|
|
2019-05-02 10:00:05 +00:00
|
|
|
Media::Player::instance()->playerWidgetToggled(
|
|
|
|
) | rpl::start_with_next([=](bool toggled) {
|
|
|
|
if (!toggled) {
|
2019-05-27 12:39:19 +00:00
|
|
|
[self setTouchBar:Platform::TouchBarType::Main];
|
2019-05-02 10:00:05 +00:00
|
|
|
} else {
|
2019-05-27 12:39:19 +00:00
|
|
|
[self setTouchBar:Platform::TouchBarType::AudioPlayer];
|
2019-05-02 10:00:05 +00:00
|
|
|
}
|
2019-05-27 12:39:19 +00:00
|
|
|
}, _lifetime);
|
2019-05-11 10:46:04 +00:00
|
|
|
|
|
|
|
Media::Player::instance()->updatedNotifier(
|
|
|
|
) | rpl::start_with_next([=](const Media::Player::TrackState &state) {
|
|
|
|
[self handleTrackStateChange:state];
|
2019-05-27 12:39:19 +00:00
|
|
|
}, _lifetime);
|
|
|
|
|
2019-05-02 11:02:54 +00:00
|
|
|
Core::App().passcodeLockChanges(
|
|
|
|
) | rpl::start_with_next([=](bool locked) {
|
|
|
|
if (locked) {
|
2019-05-27 12:39:19 +00:00
|
|
|
_touchBarTypeBeforeLock = _touchBarType;
|
|
|
|
[self setTouchBar:Platform::TouchBarType::None];
|
2019-05-02 11:02:54 +00:00
|
|
|
} else {
|
2019-05-27 12:39:19 +00:00
|
|
|
[self setTouchBar:_touchBarTypeBeforeLock];
|
2019-05-02 11:02:54 +00:00
|
|
|
}
|
2019-05-27 12:39:19 +00:00
|
|
|
}, _lifetime);
|
|
|
|
|
2019-05-07 17:08:00 +00:00
|
|
|
Auth().data().pinnedDialogsOrderUpdated(
|
|
|
|
) | rpl::start_with_next([self] {
|
|
|
|
[self updatePinnedButtons];
|
2019-05-27 12:39:19 +00:00
|
|
|
}, _lifetime);
|
|
|
|
|
2019-05-08 12:15:36 +00:00
|
|
|
Auth().data().chatsListChanges(
|
|
|
|
) | rpl::filter([](Data::Folder *folder) {
|
2019-05-27 12:39:19 +00:00
|
|
|
return folder
|
|
|
|
&& folder->chatsList()
|
|
|
|
&& folder->id() == Data::Folder::kId;
|
2019-05-08 12:15:36 +00:00
|
|
|
}) | rpl::start_with_next([=](Data::Folder *folder) {
|
|
|
|
[self toggleArchiveButton:folder->chatsList()->empty()];
|
2019-05-27 12:39:19 +00:00
|
|
|
}, _lifetime);
|
|
|
|
|
2019-05-07 17:08:00 +00:00
|
|
|
[self updatePinnedButtons];
|
2019-05-27 12:39:19 +00:00
|
|
|
|
2019-04-29 16:55:11 +00:00
|
|
|
return self;
|
2019-04-29 13:41:51 +00:00
|
|
|
}
|
|
|
|
|
2019-05-02 10:25:05 +00:00
|
|
|
- (nullable NSTouchBarItem *) touchBar:(NSTouchBar *)touchBar
|
|
|
|
makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier {
|
2019-05-27 12:39:19 +00:00
|
|
|
const id dictionaryItem = self.touchBarItems[identifier];
|
|
|
|
const id type = dictionaryItem[@"type"];
|
|
|
|
if ([type isEqualToString:@"slider"]) {
|
2019-04-29 16:55:11 +00:00
|
|
|
NSSliderTouchBarItem *item = [[NSSliderTouchBarItem alloc] initWithIdentifier:identifier];
|
|
|
|
item.slider.minValue = 0.0f;
|
|
|
|
item.slider.maxValue = 1.0f;
|
|
|
|
item.target = self;
|
|
|
|
item.action = @selector(seekbarChanged:);
|
2019-05-27 12:39:19 +00:00
|
|
|
item.customizationLabel = dictionaryItem[@"name"];
|
|
|
|
[dictionaryItem setObject:item.slider forKey:@"view"];
|
2019-04-29 16:55:11 +00:00
|
|
|
return item;
|
2019-05-27 12:39:19 +00:00
|
|
|
} else if ([type isEqualToString:@"button"]) {
|
2019-04-29 16:55:11 +00:00
|
|
|
NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
|
2019-05-27 12:39:19 +00:00
|
|
|
NSImage *image = dictionaryItem[@"image"];
|
2019-04-29 16:55:11 +00:00
|
|
|
NSButton *button = [NSButton buttonWithImage:image target:self action:@selector(buttonAction:)];
|
2019-05-27 12:39:19 +00:00
|
|
|
button.tag = [dictionaryItem[@"cmd"] intValue];
|
2019-04-29 16:55:11 +00:00
|
|
|
item.view = button;
|
2019-05-27 12:39:19 +00:00
|
|
|
item.customizationLabel = dictionaryItem[@"name"];
|
|
|
|
[dictionaryItem setObject:button forKey:@"view"];
|
2019-04-29 16:55:11 +00:00
|
|
|
return item;
|
2019-05-27 12:39:19 +00:00
|
|
|
} else if ([type isEqualToString:@"text"]) {
|
2019-04-29 16:55:11 +00:00
|
|
|
NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
|
2019-05-02 10:25:05 +00:00
|
|
|
NSTextField *text = [NSTextField labelWithString:@"00:00 / 00:00"];
|
2019-04-29 16:55:11 +00:00
|
|
|
text.alignment = NSTextAlignmentCenter;
|
|
|
|
item.view = text;
|
2019-05-27 12:39:19 +00:00
|
|
|
item.customizationLabel = dictionaryItem[@"name"];
|
|
|
|
[dictionaryItem setObject:text forKey:@"view"];
|
2019-04-29 16:55:11 +00:00
|
|
|
return item;
|
2019-05-27 12:39:19 +00:00
|
|
|
} else if ([type isEqualToString:@"pinned"]) {
|
2019-05-01 17:50:59 +00:00
|
|
|
NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
|
2019-05-27 12:39:19 +00:00
|
|
|
_mainPinnedButtons = [[NSMutableArray alloc] init];
|
2019-05-07 17:08:00 +00:00
|
|
|
NSStackView *stackView = [[NSStackView alloc] init];
|
2019-05-27 12:39:19 +00:00
|
|
|
|
2019-05-08 12:15:36 +00:00
|
|
|
for (auto i = kArchiveId; i <= Global::PinnedDialogsCountMax(); i++) {
|
2019-05-27 22:28:49 +00:00
|
|
|
PinnedDialogButton *button =
|
|
|
|
[[[PinnedDialogButton alloc] init:i] autorelease];
|
2019-05-27 12:39:19 +00:00
|
|
|
[_mainPinnedButtons addObject:button];
|
2019-05-10 07:03:47 +00:00
|
|
|
if (i == kArchiveId) {
|
|
|
|
button.isDeletedFromView = true;
|
|
|
|
continue;
|
|
|
|
}
|
2019-05-07 17:08:00 +00:00
|
|
|
[stackView addView:button.view inGravity:NSStackViewGravityCenter];
|
2019-05-01 17:50:59 +00:00
|
|
|
}
|
2019-05-27 12:39:19 +00:00
|
|
|
|
2019-05-01 17:50:59 +00:00
|
|
|
[stackView setSpacing:-15];
|
|
|
|
item.view = stackView;
|
2019-05-27 12:39:19 +00:00
|
|
|
[dictionaryItem setObject:item.view forKey:@"view"];
|
2019-05-01 13:18:31 +00:00
|
|
|
return item;
|
2019-04-29 16:55:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil;
|
|
|
|
}
|
2019-04-29 13:41:51 +00:00
|
|
|
|
2019-05-10 12:37:18 +00:00
|
|
|
- (void) createTouchBar {
|
|
|
|
_touchBarMain = [[NSTouchBar alloc] init];
|
|
|
|
_touchBarMain.delegate = self;
|
|
|
|
_touchBarMain.defaultItemIdentifiers = @[kPinnedPanelItemIdentifier];
|
2019-05-27 12:39:19 +00:00
|
|
|
|
2019-05-10 12:37:18 +00:00
|
|
|
_touchBarAudioPlayer = [[NSTouchBar alloc] init];
|
|
|
|
_touchBarAudioPlayer.delegate = self;
|
|
|
|
_touchBarAudioPlayer.customizationIdentifier = kCustomizationIdPlayer.lowercaseString;
|
|
|
|
_touchBarAudioPlayer.defaultItemIdentifiers = @[
|
|
|
|
kPlayItemIdentifier,
|
|
|
|
kPreviousItemIdentifier,
|
|
|
|
kNextItemIdentifier,
|
|
|
|
kSeekBarItemIdentifier,
|
|
|
|
kCommandClosePlayerItemIdentifier];
|
|
|
|
_touchBarAudioPlayer.customizationAllowedItemIdentifiers = @[
|
|
|
|
kPlayItemIdentifier,
|
|
|
|
kPreviousItemIdentifier,
|
|
|
|
kNextItemIdentifier,
|
|
|
|
kCurrentPositionItemIdentifier,
|
|
|
|
kSeekBarItemIdentifier,
|
|
|
|
kCommandClosePlayerItemIdentifier];
|
|
|
|
}
|
|
|
|
|
2019-05-27 12:39:19 +00:00
|
|
|
- (void) setTouchBar:(Platform::TouchBarType)type {
|
|
|
|
if (_touchBarType == type) {
|
2019-04-29 16:55:11 +00:00
|
|
|
return;
|
|
|
|
}
|
2019-05-27 12:39:19 +00:00
|
|
|
if (type == Platform::TouchBarType::Main) {
|
|
|
|
[_parentView setTouchBar:_touchBarMain];
|
|
|
|
} else if (type == Platform::TouchBarType::AudioPlayer) {
|
2019-05-27 14:15:22 +00:00
|
|
|
if (!CurrentSongExists()
|
2019-05-04 09:43:22 +00:00
|
|
|
|| Media::Player::instance()->getActiveType() != kSongType) {
|
|
|
|
return;
|
|
|
|
}
|
2019-05-27 12:39:19 +00:00
|
|
|
[_parentView setTouchBar:_touchBarAudioPlayer];
|
|
|
|
} else if (type == Platform::TouchBarType::AudioPlayerForce) {
|
|
|
|
[_parentView setTouchBar:_touchBarAudioPlayer];
|
|
|
|
_touchBarType = Platform::TouchBarType::AudioPlayer;
|
2019-05-04 09:43:22 +00:00
|
|
|
return;
|
2019-05-27 12:39:19 +00:00
|
|
|
} else if (type == Platform::TouchBarType::None) {
|
|
|
|
[_parentView setTouchBar:nil];
|
2019-04-29 16:55:11 +00:00
|
|
|
}
|
2019-05-27 12:39:19 +00:00
|
|
|
_touchBarType = type;
|
2019-05-04 09:43:22 +00:00
|
|
|
}
|
|
|
|
|
2019-05-10 12:37:18 +00:00
|
|
|
// Main Touchbar.
|
|
|
|
|
|
|
|
- (void) toggleArchiveButton:(bool)hide {
|
2019-05-27 12:39:19 +00:00
|
|
|
for (PinnedDialogButton *button in _mainPinnedButtons) {
|
2019-05-10 12:37:18 +00:00
|
|
|
if (button.number == kArchiveId) {
|
2019-05-27 12:39:19 +00:00
|
|
|
NSCustomTouchBarItem *item = [_touchBarMain itemForIdentifier:kPinnedPanelItemIdentifier];
|
2019-05-10 12:37:18 +00:00
|
|
|
NSStackView *stack = item.view;
|
2019-05-27 14:15:22 +00:00
|
|
|
[button updateUserpic];
|
2019-05-10 12:37:18 +00:00
|
|
|
if (hide && !button.isDeletedFromView) {
|
|
|
|
button.isDeletedFromView = true;
|
|
|
|
[stack removeView:button.view];
|
|
|
|
}
|
|
|
|
if (!hide && button.isDeletedFromView) {
|
|
|
|
button.isDeletedFromView = false;
|
|
|
|
[stack insertView:button.view
|
|
|
|
atIndex:(button.number + 1)
|
|
|
|
inGravity:NSStackViewGravityLeading];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-04-29 13:41:51 +00:00
|
|
|
}
|
|
|
|
|
2019-05-10 12:37:18 +00:00
|
|
|
- (void) updatePinnedButtons {
|
|
|
|
const auto &order = Auth().data().pinnedChatsOrder(nullptr);
|
|
|
|
auto isSelfPeerPinned = false;
|
2019-05-11 11:40:17 +00:00
|
|
|
auto isArchivePinned = false;
|
2019-05-10 12:37:18 +00:00
|
|
|
PinnedDialogButton *selfChatButton;
|
2019-05-27 12:39:19 +00:00
|
|
|
NSCustomTouchBarItem *item = [_touchBarMain itemForIdentifier:kPinnedPanelItemIdentifier];
|
2019-05-10 12:37:18 +00:00
|
|
|
NSStackView *stack = item.view;
|
2019-05-27 12:39:19 +00:00
|
|
|
|
|
|
|
for (PinnedDialogButton *button in _mainPinnedButtons) {
|
2019-05-10 12:37:18 +00:00
|
|
|
const auto num = button.number;
|
|
|
|
if (num <= kSavedMessagesId) {
|
|
|
|
if (num == kSavedMessagesId) {
|
|
|
|
selfChatButton = button;
|
2019-05-11 11:40:17 +00:00
|
|
|
} else if (num == kArchiveId) {
|
|
|
|
isArchivePinned = !button.isDeletedFromView;
|
2019-05-10 12:37:18 +00:00
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const auto numIsTooLarge = num > order.size();
|
|
|
|
[button.view setHidden:numIsTooLarge];
|
|
|
|
if (numIsTooLarge) {
|
|
|
|
button.peer = nil;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const auto pinned = order.at(num - 1);
|
|
|
|
if (const auto history = pinned.history()) {
|
|
|
|
button.peer = history->peer;
|
2019-05-27 14:15:22 +00:00
|
|
|
[button updateUserpic];
|
2019-05-10 12:37:18 +00:00
|
|
|
if (history->peer->id == Auth().userPeerId()) {
|
|
|
|
isSelfPeerPinned = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If self chat is pinned, delete from view saved messages button.
|
|
|
|
if (isSelfPeerPinned && !selfChatButton.isDeletedFromView) {
|
|
|
|
selfChatButton.isDeletedFromView = true;
|
|
|
|
[stack removeView:selfChatButton.view];
|
|
|
|
} else if (!isSelfPeerPinned && selfChatButton.isDeletedFromView) {
|
|
|
|
selfChatButton.isDeletedFromView = false;
|
2019-05-11 11:40:17 +00:00
|
|
|
[stack insertView:selfChatButton.view
|
|
|
|
atIndex:(isArchivePinned ? 1 : 0)
|
|
|
|
inGravity:NSStackViewGravityLeading];
|
2019-05-10 12:37:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Audio Player Touchbar.
|
|
|
|
|
2019-05-27 12:39:19 +00:00
|
|
|
- (void) handleTrackStateChange:(Media::Player::TrackState)state {
|
|
|
|
if (state.id.type() == kSongType) {
|
|
|
|
[self setTouchBar:Platform::TouchBarType::AudioPlayerForce];
|
2019-05-04 09:43:22 +00:00
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
2019-05-27 12:39:19 +00:00
|
|
|
_position = state.position < 0 ? 0 : state.position;
|
|
|
|
_duration = state.length;
|
|
|
|
if (Media::Player::IsStoppedOrStopping(state.state)) {
|
|
|
|
_position = 0;
|
|
|
|
_duration = 0;
|
2019-05-02 10:00:05 +00:00
|
|
|
}
|
2019-05-10 12:37:18 +00:00
|
|
|
[self updateTouchBarTimeItem];
|
|
|
|
|
2019-05-27 12:39:19 +00:00
|
|
|
NSButton *playButton = self.touchBarItems[kPlayItemIdentifier][@"view"];
|
|
|
|
const auto imgButton = (state.state == Media::Player::State::Playing)
|
2019-05-10 12:37:18 +00:00
|
|
|
? @"image"
|
|
|
|
: @"imageAlt";
|
2019-05-27 12:39:19 +00:00
|
|
|
playButton.image = self.touchBarItems[kPlayItemIdentifier][imgButton];
|
|
|
|
|
|
|
|
[self.touchBarItems[kNextItemIdentifier][@"view"]
|
2019-04-29 16:55:11 +00:00
|
|
|
setEnabled:Media::Player::instance()->nextAvailable(kSongType)];
|
2019-05-27 12:39:19 +00:00
|
|
|
[self.touchBarItems[kPreviousItemIdentifier][@"view"]
|
2019-04-29 16:55:11 +00:00
|
|
|
setEnabled:Media::Player::instance()->previousAvailable(kSongType)];
|
2019-04-29 13:41:51 +00:00
|
|
|
}
|
|
|
|
|
2019-05-10 12:37:18 +00:00
|
|
|
- (void) updateTouchBarTimeItem {
|
2019-05-27 12:39:19 +00:00
|
|
|
const id item = self.touchBarItems[kCurrentPositionItemIdentifier];
|
|
|
|
NSSlider *seekSlider = self.touchBarItems[kSeekBarItemIdentifier][@"view"];
|
|
|
|
NSTextField *textField = item[@"view"];
|
2019-04-29 13:41:51 +00:00
|
|
|
|
2019-05-27 12:39:19 +00:00
|
|
|
if (_duration <= 0) {
|
2019-04-29 16:55:11 +00:00
|
|
|
seekSlider.enabled = NO;
|
|
|
|
seekSlider.doubleValue = 0;
|
|
|
|
} else {
|
|
|
|
seekSlider.enabled = YES;
|
|
|
|
if (!seekSlider.highlighted) {
|
2019-05-27 12:39:19 +00:00
|
|
|
seekSlider.doubleValue = (_position / _duration) * seekSlider.maxValue;
|
2019-04-29 16:55:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
const auto timeToString = [&](int t) {
|
2019-05-10 12:37:18 +00:00
|
|
|
return FormatTime((int)floor(t / kMs));
|
2019-04-29 16:55:11 +00:00
|
|
|
};
|
2019-05-27 12:39:19 +00:00
|
|
|
textField.stringValue = [NSString stringWithFormat:@"%@ / %@",
|
|
|
|
timeToString(_position),
|
|
|
|
timeToString(_duration)];
|
2019-04-29 13:41:51 +00:00
|
|
|
|
2019-05-27 12:39:19 +00:00
|
|
|
NSTextField *field = item[@"view"];
|
2019-05-10 12:37:18 +00:00
|
|
|
|
|
|
|
if (!field) {
|
|
|
|
return;
|
|
|
|
}
|
2019-04-29 13:41:51 +00:00
|
|
|
|
2019-05-27 12:39:19 +00:00
|
|
|
[field removeConstraint:item[@"constrain"]];
|
2019-05-10 12:37:18 +00:00
|
|
|
|
2019-05-27 12:39:19 +00:00
|
|
|
NSString *fString = [[textField.stringValue componentsSeparatedByCharactersInSet:
|
2019-05-10 12:37:18 +00:00
|
|
|
[NSCharacterSet decimalDigitCharacterSet]] componentsJoinedByString:@"0"];
|
2019-05-27 12:39:19 +00:00
|
|
|
NSSize size = [[NSTextField labelWithString:fString] frame].size;
|
2019-05-10 12:37:18 +00:00
|
|
|
|
|
|
|
NSLayoutConstraint *con =
|
|
|
|
[NSLayoutConstraint constraintWithItem:field
|
|
|
|
attribute:NSLayoutAttributeWidth
|
|
|
|
relatedBy:NSLayoutRelationEqual
|
|
|
|
toItem:nil
|
|
|
|
attribute:NSLayoutAttributeNotAnAttribute
|
|
|
|
multiplier:1.0
|
|
|
|
constant:(int)ceil(size.width) * 1.2];
|
|
|
|
[field addConstraint:con];
|
2019-05-27 12:39:19 +00:00
|
|
|
[item setObject:con forKey:@"constrain"];
|
2019-04-29 13:41:51 +00:00
|
|
|
}
|
|
|
|
|
2019-05-02 10:25:05 +00:00
|
|
|
- (void) buttonAction:(NSButton *)sender {
|
2019-05-10 12:37:18 +00:00
|
|
|
const auto command = sender.tag;
|
2019-04-29 16:55:11 +00:00
|
|
|
|
|
|
|
Core::Sandbox::Instance().customEnterFromEventLoop([=] {
|
2019-05-27 12:39:19 +00:00
|
|
|
switch (command) {
|
|
|
|
case kCommandPlayPause:
|
2019-05-04 09:43:22 +00:00
|
|
|
Media::Player::instance()->playPause(kSongType);
|
2019-05-27 12:39:19 +00:00
|
|
|
break;
|
|
|
|
case kCommandPlaylistPrevious:
|
2019-05-04 09:43:22 +00:00
|
|
|
Media::Player::instance()->previous(kSongType);
|
2019-05-27 12:39:19 +00:00
|
|
|
break;
|
|
|
|
case kCommandPlaylistNext:
|
2019-05-04 09:43:22 +00:00
|
|
|
Media::Player::instance()->next(kSongType);
|
2019-05-27 12:39:19 +00:00
|
|
|
break;
|
|
|
|
case kCommandClosePlayer:
|
2019-04-30 18:31:42 +00:00
|
|
|
App::main()->closeBothPlayers();
|
2019-05-27 12:39:19 +00:00
|
|
|
break;
|
2019-04-30 18:31:42 +00:00
|
|
|
}
|
2019-04-29 16:55:11 +00:00
|
|
|
});
|
2019-04-29 13:41:51 +00:00
|
|
|
}
|
|
|
|
|
2019-05-02 10:25:05 +00:00
|
|
|
- (void) seekbarChanged:(NSSliderTouchBarItem *)sender {
|
2019-05-04 07:01:10 +00:00
|
|
|
// https://stackoverflow.com/a/45891017
|
|
|
|
NSEvent *event = [[NSApplication sharedApplication] currentEvent];
|
2019-05-10 12:37:18 +00:00
|
|
|
const auto touchUp = [event touchesMatchingPhase:NSTouchPhaseEnded inView:nil].count > 0;
|
2019-05-04 07:01:10 +00:00
|
|
|
Core::Sandbox::Instance().customEnterFromEventLoop([=] {
|
|
|
|
if (touchUp) {
|
|
|
|
Media::Player::instance()->finishSeeking(kSongType, sender.slider.doubleValue);
|
|
|
|
} else {
|
|
|
|
Media::Player::instance()->startSeeking(kSongType);
|
|
|
|
}
|
2019-04-29 16:55:11 +00:00
|
|
|
});
|
2019-04-29 13:41:51 +00:00
|
|
|
}
|
|
|
|
|
2019-05-27 22:28:49 +00:00
|
|
|
-(void)dealloc {
|
|
|
|
for (PinnedDialogButton *button in _mainPinnedButtons) {
|
|
|
|
[button release];
|
|
|
|
}
|
|
|
|
[super dealloc];
|
|
|
|
}
|
|
|
|
|
2019-04-29 13:41:51 +00:00
|
|
|
@end
|